代码精进之路
范学雷
Oracle首席软件工程师,Java SE安全组成员,OpenJDK评审成员
立即订阅
6309 人已学习
课程目录
已完结 47 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 你写的每一行代码,都是你的名片
免费
第一模块:代码“规范”篇 (16讲)
01 | 从条件运算符说起,反思什么是好代码
02 | 把错误关在笼子里的五道关卡
03 | 优秀程序员的六个关键特质
04 | 代码规范的价值:复盘苹果公司的GoToFail漏洞
05 | 经验总结:如何给你的代码起好名字?
06 | 代码整理的关键逻辑和最佳案例
07 | 写好注释,真的是小菜一碟吗?
08 | 写好声明的“八项纪律”
09 | 怎么用好Java注解?
10 | 异常处理都有哪些陷阱?
11 | 组织好代码段,让人对它“一见钟情”
12丨组织好代码文件,要有“用户思维”
13 | 接口规范,是协作的合约
14 | 怎么写好用户指南?
15 | 编写规范代码的检查清单
16丨代码“规范”篇用户答疑
第二模块:代码“经济”篇 (14讲)
17 | 为什么需要经济的代码?
18丨思考框架:什么样的代码才是高效的代码?
19 | 怎么避免过度设计?
20 | 简单和直观,是永恒的解决方案
21 | 怎么设计一个简单又直观的接口?
22丨高效率,从超越线程同步开始!
23 | 怎么减少内存使用,减轻内存管理负担?
24 | 黑白灰,理解延迟分配的两面性
25 | 使用有序的代码,调动异步的事件
26 | 有哪些招惹麻烦的性能陷阱?
27 | 怎么编写可持续发展的代码?
28 | 怎么尽量“不写”代码?
29 | 编写经济代码的检查清单
30丨“代码经济篇”答疑汇总
第三模块:代码“安全”篇 (14讲)
31 | 为什么安全的代码这么重要?
32 | 如何评估代码的安全缺陷?
33 | 整数的运算有哪些安全威胁?
34 | 数组和集合,可变量的安全陷阱
35 | 怎么处理敏感信息?
36 | 继承有什么安全缺陷?
37 | 边界,信任的分水岭
38 | 对象序列化的危害有多大?
39 | 怎么控制好代码的权力?
40 | 规范,代码长治久安的基础
41 | 预案,代码的主动风险管理
42 | 纵深,代码安全的深度防御
43 | 编写安全代码的最佳实践清单
44 | “代码安全篇”答疑汇总
加餐 (1讲)
Q&A加餐丨关于代码质量,你关心的那些事儿
结束语 (1讲)
结束语|如何成为一个编程好手?
代码精进之路
登录|注册

34 | 数组和集合,可变量的安全陷阱

范学雷 2019-03-22
在前面的章节里,我们讨论了不少不可变量的好处。在代码安全中,不可变量也减少了很多纠葛的发生,可变量则是一个非常难缠的麻烦。

评审案例

我们一起看下这段 JavaScript 代码。
var mutableArray = [0, {
toString : function() {
mutableArray.length = 0;
}
}, 2];
console.log("Array before join(): ", mutableArray);
mutableArray.join('');
console.log("Array after join(): ", mutableArray);
调用 mutableArray.join() 前后,你知道数组 mutableArray 的变化吗?调用 join() 前,数组 mutableArray 包含两个数字,一个函数 ({10, {}, 20})。调用 join() 后,数组 mutableArray 就变成一个空数组了。这其中的秘密就在于 join() 的实现,执行了数组中 toString() 函数。而 toString() 函数的实现,把数组mutableArray 设置为空数组。
下面的代码,就是 JavaScript 引擎实现数组 join() 方法的一段内部 C 代码。
static JSBool
array_toString_sub(JSContext *cx, JSObject *obj, JSBool locale,
JSString *sepstr, CallArgs &args) {
// snipped
size_t seplen;
// snipped
StringBuffer sb(cx);
if (!locale && !seplen && obj->isDenseArray() &&
!js_PrototypeHasIndexedProperties(cx, obj)) {
// Elements beyond the initialized length are
// 'undefined' and thus can be ignored.
const Value *beg = obj->getDenseArrayElements();
const Value *end =
beg + Min(length, obj->getDenseArrayInitializedLength());
for (const Value *vp = beg; vp != end; ++vp) {
if (!JS_CHECK_OPERATION_LIMIT(cx))
return false;
if (!vp->isMagic(JS_ARRAY_HOLE) &&
!vp->isNullOrUndefined()) {
if (!ValueToStringBuffer(cx, *vp, sb))
return false;
}
}
}
// snipped
}
这段代码,把数组的起始地址记录在 beg 变量里,把数组的结束地址记录在 end 变量里。然后,从 beg 变量开始,通过调用 ValueToStringBuffer() 函数,把数组里的每一个变量,转换成字符串。
我们一起来看看第一段代码,是怎么在这段 join() 实现的 for 循环代码里执行的。
vp 指针初始化后,指向数组的起始地址;
如果 vp 的地址不等于数组的结束地址 end,就把数组变量转换成字符串,然后变换 vp 指针到下一个地址 。我们一起来看看这段代码是如何操作数组 mutableArray 的:
a. 数组的第一个变量是 0。0 被转换成字符,vp 指针换到下一个地址;
b. 数组的第二个变量是 toString() 函数。toString() 函数被调用后,就会把 mutableArray 这个数组设置为空数组,vp 指针换到下一个地址;
c. 数组的第三个变量本来应该是 2。但是,由于数组在上一步被置为空数组,数组的第三个变量的指针指向数组外地址。
由于数组已经被设置为空数组,原数组的地址可能已经被其他数据占用,访问空数组外的地址就会造成内存泄漏或者程序崩溃。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《代码精进之路》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(2)

  • 彩色的沙漠
    老师您好,调用 join() 前,数组 mutableArray 包含两个数字,一个函数 ({10, {}, 20})。对这个(10,{},20)不理解,变化前数组mutableArray应该是包含两个数字,一个对象(0,{},2)?

    作者回复: 👍,这是我的失误。原来的例子我用的是(10, {}, 20),后来觉得{0, {}, 2}更直观些,就换成了{0, {}, 2}。 描述部分漏掉了,没改过来。 多谢多谢!

    2019-05-31
    2
  • 天佑
    toctou不能用线程同步解决,线程同步解决的是有序执行的问题,解决可变量的根本问题是变量局部化,隔离可变因素,老师我理解的对否。
    实际场景中,可变类应该很多,动不动就拷贝,好像不现实,是不是只要传递的可变量都要局部化啊?单线程环境下应该不用考虑吧。

    作者回复: 这样理解没问题,不过有时候线程同步也可以起到阻断变化的使用。

    后面我们还会讲代码的边界,什么时候拷贝,什么时候不拷贝,我们稍后讨论。

    2019-03-22
    2
收起评论
2
返回
顶部