Bug隐藏在简单背后

Stella981
• 阅读 552

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

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

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

从事金融的程序猿都知道,代码实现功能经常跟钱打交道,钱多一点不行,钱少一点也不行。如果你实现的付款功能,向客户少付了一分钱,客户是否能忍?另外信用卡还款,少了 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 部分智能合约代码进行分析。

Bug隐藏在简单背后

稍微写过程序的都能对上图代码理解个八九不离十,就是批量给人转账,函数入参 _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!

  1. 写在最后

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

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

Bug隐藏在简单背后

本文分享自微信公众号 - 一猿小讲(yiyuanxiaojiangV5)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
0基础前端开发需要学什么?
  0基础前端开发需要学什么?零基础学员入门前端需要了解前端行业的发展趋势、学习前端的方法、前端学习路线详解以及前端案例展示等内容;有老师指导的情况下,可以结合学员自身情况制定前端学习路线,明确学习前端的路径、未来发展趋势。提前为学员打好基础,避免小白学员走弯路。  小白学员面临的前端如何入门、如何开始学习前端、前端能够做什么等问题。因此入门前要明确学习
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这