前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Bug隐藏在简单背后

Bug隐藏在简单背后

作者头像
一猿小讲
发布2019-08-16 15:43:45
4190
发布2019-08-16 15:43:45
举报
文章被收录于专栏:一猿小讲一猿小讲

曾记得几年前做培训老师的时候,在辅导学员 Java 面试题过程中,总是提醒学员“越是简单的面试题,其中的玄机就越多,需要学员有相当深厚的功力去面对”。多年从事程序开发以来,现在回头想想,还真是那么回事儿。今天转变一下分享角度,就不聊技术架构啦,咱们一起聊聊那些简单的程序代码背后。

01. 刚入猿门(懵懂的小白)

多一点不行,少一点也不行。

从事金融的程序猿都知道,代码实现功能经常跟钱打交道,钱多一点不行,钱少一点也不行。如果你实现的付款功能,向客户少付了一分钱,客户是否能忍?另外信用卡还款,少了 1 分钱,算你违约,你是否能忍?向你女朋友转账 520 元红包,却到账 250 元,你女朋友是否能忍?闲话不多说,直接上代码。

double a = 1;

double b = 0.99;

System.out.println(a - b);

肯定你们中也有一部分,坚信这段代码运行结果很简单不是 0.01 么?!很久之前如果问我结果是什么,我也会毫不犹豫的答道 0.01,然而真实的结果却是:0.010000000000000009。如果该段逻辑实现的是提现功能,那会不会损失大发了。

说说原因:你们都知道,计算机进行的是二进制运算,然而问题在于转换为二进制的时候,有些数字不能完全转换,只能无限接近于原本的值,由于二进制无法准确表示 0.99 ,就像十进制无法准确表示 1/3 一样,所以必定会有精度损失。

讲讲正解:一般遇到这种,需要用到浮点数运算的地方,都可以使用 java.math.BigDecimal。

BigDecimal a = new BigDecimal(1);

BigDecimal b = new BigDecimal(0.99);

System.out.println(a.subtract(b));

程序跑起来看看效果,一看到结果会惊呆你们。又损失了一点,真实的结果输出变成了:

0.0100000000000000088817841970012523233890533447265625。

枉费你们激情满满,从一个坑又带到另一个坑,不靠谱啊。你们,莫着急,我们不妨把参数改成字符串试试。敲黑板,拨云见日水落石出的时刻到了。

BigDecimal a = new BigDecimal("1");

BigDecimal b = new BigDecimal("0.99");

System.out.println(a.subtract(b));

程序跑起来一窥究竟,期待良久的结果 0.01 终于正常算出来了。

我有话说:如果在程序中直接使用 double 进行计算,会造成精度损失,有可能会引起一些莫名奇妙的 bug;如果用 double 来构造 BigDecimal 依然会有精度损失;请你们铭记:直接使用字符串来构造 BigDecimal,是绝对没有精度损失的。

02. 久居猿门(经验丰富的码农)

吐血的 Bug,阴沟里翻船。

曾经带着兄弟做过一个日志归集的项目,用 elasticsearch 存储采集的日志。由于采集的日志会逐日增多,考虑到系统长期平稳运行,需要每天跑定时任务清理 60 天前的日志信息,用于释放磁盘内存空间。

日志归集项目上线没过多久,突然发现,2 周前的日志数据貌似丢失了,生产无小事,小事更不能忽视,于是就跟兄弟们一起排查、分析代码,但是没发现逻辑上的问题漏洞。但第二天同样的问题,又规律性的再次发生,于是,兄弟们的焦点便集中到了“定时清理的任务”上。左查右查依然没发现问题,只能一步一步的进行 Debug 跟踪调试。

令人发指的是问题就出现在一个常量定义上。

说说原因:

public static final long LOG_DATA_INVALID_DATE = 60 * 24 * 3600 * 1000;

按道理表示 60 天的时间 60 * 24 * 3600 * 1000 的值应该是 5184000000 的,但是它实际值却是 889032704,大约 10 天时间。坑爹啊,最后发现居然是 int 在计算过程中的溢出,太隐晦的 bug 了。排查问题过程很痛苦,解决问题的方式却很简单,任意一个常量上加 L,转成 long 型就好了。

讲讲正解:

public static final long LOG_DATA_INVALID_DATE = 60L * 24 * 3600 * 1000;

我有话说:现在想想,这种 Bug 确实是挺难查的。不过稍微细心一点或者借助 FindBugs 等一些工具来扫描一下,这样的 Bug 应该都可以避免。编码不易,且码且 Debug。

03.猿门起飞(装牛 X 的程序员)

一行代码,蒸发 6,447,277,680 元!

去年由于币圈的疯狂炒作,导致区块链概念深入到每个人的骨髓,就连跳广场舞的大妈、卖书的大爷都参与跟风,喜欢追新的我当然也不会放过。我用两天时间自学了 Solidity 语言,帮别人写了个发行代币的智能合约代码,人家借势赚了个盆满钵满,出于感激,我还赚了个大红包。

跑偏了,咱们今天不聊赚红包这事儿,还是分享区块链业界一个智能合约的普遍漏洞吧。

案例一:随着BEC智能合约的漏洞的爆出,被黑客利用,瞬间套现抛售大额BEC,60亿在瞬间归零。

案例二:距BEC现重大漏洞几近归零仅时隔三天,SMT等多个智能合约再曝漏洞,交易平台迅速停止重提币业务。

说说原因:摘取 BEC 部分智能合约代码进行分析。

稍微写过程序的都能对上图代码理解个八九不离十,就是批量给人转账,函数入参 _receivers 是转给哪些人,_value 是每个人转多少,然后计算一下:你要发送的总金额 = 发送的人数 * 发送的金额,最后从你账户余额中减去你要发送的总金额。

那么问题出现在哪儿呢?

分析一:

你要发送的总金额 = 发送的人数 * 发送的金额

uint256 amount = uint256(cnt) * _value;

当攻击者传入很大的 value 数值,使 uint256(cnt) * value 后超过 unit256 的最大值使其溢出,便可导致 amount 的值变为 0。

分析二:

你的账户剩余余额 = 你账户金额 - 你要发送的总金额。

balances[msg.sender] = balances[msg.sender].sub(amount);

那么当 amount 为0时,你的账户显然不会有任何变化。

我有话说:就一个简单的溢出漏洞,蒸发 6,447,277,680 元,导致 BEC 代币的市值接近归 0。而这一切,竟然是因为一个简单至极的程序Bug!

04. 写在最后

最后想分享的是,作为程序猿,诸多看似很简单的程序代码逻辑,跑起来却差强人意,匪夷所思。Coding 是个精细活,所以你们研发过程中一定要仔细,稍微细心一点会屏蔽很多 Bug,会避免很多损失。希望你们能够透过现象看本质、知其然并且知其所以然。

如果感觉稍微有点意思,不用赞赏,就点击右下角的“在看”,或者多多分享转发给你的朋友就很感激。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-06-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一猿小讲 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
区块链
云链聚未来,协同无边界。腾讯云区块链作为中国领先的区块链服务平台和技术提供商,致力于构建技术、数据、价值、产业互联互通的区块链基础设施,引领区块链底层技术及行业应用创新,助力传统产业转型升级,推动实体经济与数字经济深度融合。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档