-
-
[原创] CVE-2023-4069:Maglev图建立阶段的一个漏洞
-
发表于: 2025-7-7 16:56 3249
-
diff链接:
函数原型
参数
使用实例
FastNewObject的实现代码,位于src/builtins/builtins-constructor-gen.cc
首先获取上下文环境,其中包括target和new_target,接着定义一个call_runtime的label,如果快速路径分配失败,则会通过这个label跳转到慢速路径进行对象分配。
调用FastNewObject进入快速路径,如果快速路径分配失败,则会跳转到BIND(&call_runtime),然后执行Runtime::kNewObject,进入慢速路径的分配
对于慢速路径的runtime函数可以在vscode里全局搜索RUNTIME_FUNCTION(Runtime_xxxxx,这里的xxxxx代表函数名,上方的kNewObject,那么就用这里的函数名应该是NewObject,所以就可以搜索RUNTIME_FUNCTION(Runtime_NewObject)

这里是全部完整的代码,位于src/builtins/builtins-constructor-gen.cc
分开看,首先判断new_target_func是不是JSFunction,不是就进入call_runtime的逻辑,和前面定义的Label call_runtime(this)对应上了,这里对应的是慢速路径
接着先获取new_target的inital map,然后判断new_target_func的initial_map是否是smi,因为如果是map类型,那么必然不会是smi,所以如果是smi,就会进入call_runtime的逻辑,然后将initial_map_or_proto转换为HeapObject判断是否为map类型,如果不是,那么就进入call_runtime的逻辑,这里验证的这个initial map是否真的存在
GotoIf(TaggedIsSmi(initial_map_or_proto), call_runtime) 等价于if( TaggedIsSmi(initial_map_or_proto) ){call_runtime}
简化一下就是GotoIf(A,B);{C};等价于if(A){B}else{C};
从initial map上load constructor,然后判断new_target的constructor和target是否一致
TVARIABLE(HeapObject, properties),这里的T代表Turbofan ,后面VARIABLE表示变量声明,类型是HeapObject,变量名称是properties
下面的逻辑会根据map的不同类型进行跳转
如果map的类型是DictionaryMap,那么会直接跳转到BIND(&allocate_properties),然后V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL编译选项是否开启,为properties采用不同的内存分配,接着会跳转到BIND(&instantiate_map),为map进行内存分配
如果map的类型不是DictionaryMap,那么会直接为properties分配,接着跳转到BIND(&instantiate_map)处,为map进行内存分配
下面总结一下,成功进行快速对象分配的条件是
当快速路径分配失败的时候,会通过Label call_runtime(this)跳转到BIND(&call_runtime),也就是执行这个语句TailCallRuntime(Runtime::kNewObject, context, target, new_target);
Runtime::kNewObject 是一个枚举值,定义在 src/runtime/runtime.h 中。这个枚举值对应的是 Runtime_NewObject 函数,定义在 src/runtime/runtime-object.cc 中。
这个函数的流程比较简单,获取当前的隔离实例,检查参数个数,获取target和new_target,最后将参数传递并调用JSObject::New
JSObject::New() 的源码位于src/objects/js-objects.cc,函数体开始的注释是对于new_target的所有可能性的说明,接着是一些DCHECK,可以看到target被命名成了constructor,new_target还是没有变化
调用JSFunction::GetDerivedMap来获取initial_map,然后根据V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL编译选项设置initial_capacity,最后调用NewFastOrSlowJSObjectFromMap分配对象,同时设置分配类型是kYoung,意味着可以被gc回收
接着来看一下JSFunction::GetDerivedMap的实现,位于src>objects>js-function.cc
首先检查一下constructor的initial map是否存在,接着获取constructor的initial map,判断如果new_target和constructor一样,那么就直接返回constructor_initial_map,这里对应了直接new object的情况,也是最常用的情况
这里是说只有当满足三个条件的时候,才会进入的逻辑,分别是new.target为JSFunction、new.target为constructor的子类 、map可缓存时。接着确保new.target存在initial map(没有的话会尝试分配),然后直接就返回new.target的initial map
看一下FastInitializeDerivedMap的实现,位于src>objects>js-function.cc
这里先check了new_target是否有prototype,也就是在原本的JSCFuntcion的基础上继续判断
接着如果new_target存在initial map,并且对应的构造函数和target一致,那么就直接返回true,进行快速对象的分配
这里快速对象分配还遵守着target和new_target的constructor得一致
其实漏洞时出现在快速对象分配的,下面的与慢速分配相关,所以笔者就不接着分析了
demo
./d8 --allow-natives-syntax --maglev --print-maglev-graphs ./DebugMaglev.js 执行这个命令,会有下面的输出,从Bytecode age: 0到Constant pool (size = 2)之间,是这一段demo生成的字节码

大致解释下,见注释。简单的说明是最左侧是存放字节码的地址,右侧@后面的数字是相对于这一段字节码的偏移地址,: 后面的是内存里的字节码,然后最后是指令
也就是说下面的r0是new出来的array,然后索引是r3,r3通过常量池中的p赋值的到,在循环体中每次+smi[5],r1作为index,每次执行自加的操作,直到大于等于25跳出循环
对于后续更详细的流程,可以去看这个23年p4nda师傅在bh eu上的slide
查看下issue对应的diff,完整的diff内容如下,主要是在/src/maglev/maglev-graph-builder.cc下的修改
分析之前需要熟悉一下这个函数MaglevGraphBuilder::VisitFindNonDefaultConstructorOrConstruct(),从这个函数名不难看出,这个流程发生在Maglev的图建立阶段,用于处理派生类非默认构造函数的访问
这里需要配合一个说明的代码,看一下这个函数的触发流程
对于上面的代码,有两个类,分别是Base和Derived,Derived继承了Base。然后调用Reflect.construct创建了Derived对象
使用如下参数./d8 --allow-natives-syntax --maglev --print-bytecode ./poc.js执行这个脚本,会有这样的输出,重点看一下Derived函数
0x3f5c0019b0c1调用了FindNonDefaultConstructorOrConstruct 这个函数,后续将一些寄存器赋值之后,有一个JumpIfTrue 的判断,接着就是正常的返回操作
接着通过上面的代码去分析VisitFindNonDefaultConstructorOrConstruct函数的源码
首先就是获取参数,this_function对应了Derived,new_target对应了Base,这里的register_pair是存储返回结果
接着获取this_function的ref,调用TryGetConstant方法,如果调用成功的话,那么就获取this_function的map的ref,这里获取的map是Derived函数(class 本质上还是一个函数)的map,接着获取Derived的原型对象
下面是一个很大的循环,推动循环执行的是这个语句current = current_function.map(broker()).prototype(broker()),本质上是顺着Derived的map向上查找原型,也就是遍历原型链,接着可以继续看这个循环体的逻辑
首先这里会去判断当前前遍历到的 prototype 对象是不是 JSFunction,接着获取这个function的ref,然后如果当前构造函数有实例字段(或类似需要初始化的成员),就不能跳过这个requires_instance_members_initializer构造函数,必须执行它的初始化逻辑,最后判断当前 class是否 有 private 字段或方法,不存在则会强制执行初始化逻辑
然后通过SFI(SharedFunctionInfo)去获取当前函数的类型,这里可以补充一下一些函数类型,下面就先解释下会用到的,全部的位于这里src/objects/function-kind.h文件里的FunctionKind枚举类里
当获取到当前函数的类型之后,因为从Derived,所以这个判断kind == FunctionKind::kDefaultDerivedConstructor为true,先执行一个依赖保护,通常都是true。
但是这里我们修改了Derived的构造函数内容,所以这里会返回false,所以会进入else逻辑,但是因为也不是FunctionKind::kDefaultBaseConstructor类型,所以会接着会遍历到上层,会获取到Base的prototype,判断是否为JSFunction时会转化为基类的构造函数,类型对应的是kDefaultBaseConstructor。然后也是执行依赖保护,从function map开始到current_function,也就是Derived开始到base。使用TryGetConstant获取当前构造函数的ref。接着通过if的判断,判断new_target_func函数是否存在且类型是JSFunction,然后调用FastObject分配快速对象,接着调用BuildAllocateFastObject分配对象,类型是kYoung,可以被gc回收
如果说这里原型链没有遍历到基类,那么这里的类型就不会是kDefaultBaseConstructor,从而进入else的逻辑,这里会调用BuildCallBuiltin分配对象
当所有的遍历流程接触,会将结果load到寄存器,最后返回
接着看一下FastObject的实现,位于/src/maglev/maglev-graph-builder.cc
可以看到这里其实都没有check,根据constructor.initial_map来初始化对象的map,对应上面的就是Base的initial map,然后根据当前Base的构造函数预测该构造函数实例化对象的最终属性数量和大小,最后就是为这个对象分配内存
这里是初始化操作
BuildAllocateFastObject的实现,位于/src/maglev/maglev-graph-builder.cc
基本上也没有什么检查,除了一个DCHECK,可以留意的是这里直接就根据传进来的快速对象去分配了,这里通过object.map去使用快速对象的map,对应上面的就是Base的initial map
其实到这里细心的读者应该是发现问题了,这里快速对象的分配不遵循之前的三个条件了,这里只判断了是current是否存在,且为JSFunction
回顾一下,成功进行快速对象分配的条件是
那么其实这里意味着我们通过这个路径,去分配出target(Derived)和new_target(Base)的map不一致的情况,也就是使用new_target的map去分配Derived对象,这样就会造成类型混淆了
同时结合上面分配的flag是AllocationType::kYoung,意味着内存可以被gc回收,那么就可以主动触发gc,从而导致使用到未被初始化的内存
所以现在就需要看下这个执行路径,这里最需要注意的是TryGetConstant,其他的正常路径都是会达到的
TryGetConstant函数的实现,位于/src/maglev/maglev-graph-builder.cc
这里的存在两个方法,一个static方法,一个成员方法,然后成员方法会调用到static方法
存在有两个路径,第一个路径是用于直接常量,第二个路径是用于传播常量;
比如说这种
可以通过使用直接常量的方式绕过
结合上面的分析,我们可以发现一个正常的快速对象分配是存在很多的检测,但是当Maglev在图建立阶段,分配快速对象的时候产生了问题,就是不会检测new_target和target的map是否一致,同时分配快速对象的时候使用的是new_target的initial map,因此如果二者的map不一致,那么这个可能会导致类型混淆的问题,常见的类型混淆就是JSObject和JSArray进行类型混淆。
同时还需要思考的是如何让两个TryGetConstant成立,第一处对于this_funciton的判断,也就是对于target的判断,我们可以让这个变量一直不变,让Maglev判断这个值时恒定的,也就是属于上面分析的传播常量的情况;第二处对于new_target的判断,考虑到new_target的initial map还会作为快速对象的map,因此这里可以设置为Array,对应的也就是直接常量,这样就可以绕过第二处的TryGetConstant,同时实现类型混淆,也就是意图创建target的对象实例,但是使用了new_target的initial map分配。
因为JSObject和JSArray的结构并不相同,这里会导致JSObject的in-object[0]这个字段会变成JSArray的Length字段,详细的可以看下面的两个对象的结构图
这里是JSObject的对象结构,Elements后面是in-object[0]

示例代码
不难看出elements后面就是2和4,右移1位之后就是in_obj1和in_obj2。然后out1和out2存储在properties中
这里是JSArray对象的结构

示例代码
输出
因为使用Reflect.construct(Derived, [], Base);的时候无法创建in-object对象,所以这里混淆后的Length就只能为0,但是别忘了还有gc,我们可以主动触发gc,这样会使用到一些地址上残留的值,这样就会有一个oob了
因此下面是一个poc
因为chrome执行会默认带--maglev这个flag,所以这里d8执行的时候需要加上--maglev,下面是输出,可以看到length字段已经出来了
方便查阅
有了一个oob的原语,下面的思路比较固定,我构造了这样的一个结构,首先堆喷一个victim_array(用对象初始化),这样Elements的地址相较于oob_array会相对稳定(这里笔者的机器信息见下方,然后这里oob_array的Elements在笔者机器上的offset稳定为0x219)
接着利用oob_array的oob去修改victim_array的elements元素,布置伪造的map(根据版本动态修改)和对象,然后布置这个fake_obj_addr,便于后续伪造fake_object,也就是一步到位直接有fakeObject原语了
然后victim_array的Elements首部可以布置一个obj,这样方便写addressOf的原语,通过oob_array越界写

笔者机器使用的环境
遇到的一些问题
exp
笔者机器上,堆喷射后victim_array的elements地址只有这两个情况,0x2423cd和0x2423e9

d23K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2T1L8r3!0Y4i4K6u0r3M7$3g2U0N6i4u0A6N6s2W2Q4x3V1k6$3N6h3I4F1k6i4u0S2j5X3W2D9K9i4c8&6i4K6u0V1M7X3g2K6k6h3q4J5j5$3S2Q4x3V1k6Y4k6i4c8@1K9h3&6Y4i4K6u0V1M7X3y4W2i4K6u0V1K9h3&6Q4x3X3c8U0K9s2u0G2L8h3g2Q4x3X3c8%4K9i4c8Z5i4K6u0V1K9h3&6U0L8$3#2H3L8r3g2@1k6g2)9J5k6r3!0T1K9X3g2U0N6q4)9J5k6r3W2F1K9i4c8A6j5h3I4A6P5X3q4@1K9h3!0F1i4K6u0V1K9h3&6Q4x3X3c8@1K9r3g2Q4x3X3c8E0j5h3N6D9k6i4k6Q4x3X3c8U0L8$3#2H3K9h3I4W2M7W2)9J5c8R3`.`.
b3bK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3V1k6K6k6h3y4#2M7X3W2@1P5h3I4S2j5W2)9J5c8X3u0D9L8$3u0Q4x3V1k6E0j5h3W2F1i4K6u0r3f1$3g2U0N6i4u0A6N6s2W2q4P5s2m8D9L8$3W2@1M7#2)9J5c8V1y4Z5M7X3!0E0k6g2)9J5c8Y4j5^5i4K6u0r3b7#2k6q4i4K6g2X3x3U0l9J5x3#2)9#2k6U0b7H3y4U0W2Q4x3V1k6H3L8$3y4Q4x3X3g2B7M7H3`.`.
fe1K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3V1k6K6k6h3y4#2M7X3W2@1P5h3I4S2j5W2)9J5c8X3c8A6M7$3y4#2M7%4y4A6L8$3&6K6i4K6u0r3y4K6V1%4
aa5K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2E0j5i4c8@1k6h3!0E0j5h3I4$3K9h3y4S2i4K6u0W2j5$3!0E0i4K6u0r3j5X3I4G2k6#2)9J5c8U0t1H3x3U0c8Q4x3V1j5H3y4W2)9J5c8U0l9#2i4K6u0r3K9h3&6@1M7X3!0Q4x3X3c8$3z5q4)9J5k6r3g2^5M7r3I4G2K9i4c8S2N6r3W2G2L8W2)9J5k6r3#2S2k6$3I4W2N6W2)9J5c8R3`.`.
git checkout 5315f073233429c5f5c2c794594499debda307bdgclient sync -Dpython3 tools\dev\gm.py x64.releasegit checkout 5315f073233429c5f5c2c794594499debda307bdgclient sync -Dpython3 tools\dev\gm.py x64.releaseReflect.construct(target, argumentsList[, newTarget])Reflect.construct(target, argumentsList[, newTarget])function OneClass() { this.name = "one";}function OtherClass() { this.name = "other";}// 创建一个对象:var obj1 = Reflect.construct(OneClass, args, OtherClass);console.log(obj1.name); // 'one'console.log(obj1 instanceof OneClass); // falseconsole.log(obj1 instanceof OtherClass); // truefunction OneClass() { this.name = "one";}function OtherClass() { this.name = "other";}// 创建一个对象:var obj1 = Reflect.construct(OneClass, args, OtherClass);console.log(obj1.name); // 'one'console.log(obj1 instanceof OneClass); // falseconsole.log(obj1 instanceof OtherClass); // trueTF_BUILTIN(FastNewObject, ConstructorBuiltinsAssembler) { auto context = Parameter<Context>(Descriptor::kContext); auto target = Parameter<JSFunction>(Descriptor::kTarget); auto new_target = Parameter<JSReceiver>(Descriptor::kNewTarget); Label call_runtime(this); TNode<JSObject> result = FastNewObject(context, target, new_target, &call_runtime); Return(result); BIND(&call_runtime); TailCallRuntime(Runtime::kNewObject, context, target, new_target);}TF_BUILTIN(FastNewObject, ConstructorBuiltinsAssembler) { auto context = Parameter<Context>(Descriptor::kContext); auto target = Parameter<JSFunction>(Descriptor::kTarget); auto new_target = Parameter<JSReceiver>(Descriptor::kNewTarget); Label call_runtime(this); TNode<JSObject> result = FastNewObject(context, target, new_target, &call_runtime); Return(result); BIND(&call_runtime); TailCallRuntime(Runtime::kNewObject, context, target, new_target);}TNode<JSObject> ConstructorBuiltinsAssembler::FastNewObject( TNode<Context> context, TNode<JSFunction> target, TNode<JSReceiver> new_target, Label* call_runtime) { // Verify that the new target is a JSFunction. Label end(this); TNode<JSFunction> new_target_func = HeapObjectToJSFunctionWithPrototypeSlot(new_target, call_runtime); // Fast path. // Load the initial map and verify that it's in fact a map. TNode<Object> initial_map_or_proto = LoadJSFunctionPrototypeOrInitialMap(new_target_func); GotoIf(TaggedIsSmi(initial_map_or_proto), call_runtime); GotoIf(DoesntHaveInstanceType(CAST(initial_map_or_proto), MAP_TYPE), call_runtime); TNode<Map> initial_map = CAST(initial_map_or_proto); // Fall back to runtime if the target differs from the new target's // initial map constructor. TNode<Object> new_target_constructor = LoadObjectField( initial_map, Map::kConstructorOrBackPointerOrNativeContextOffset); GotoIf(TaggedNotEqual(target, new_target_constructor), call_runtime); TVARIABLE(HeapObject, properties); Label instantiate_map(this), allocate_properties(this); GotoIf(IsDictionaryMap(initial_map), &allocate_properties); { properties = EmptyFixedArrayConstant(); Goto(&instantiate_map); } BIND(&allocate_properties); { if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { properties = AllocateSwissNameDictionary(SwissNameDictionary::kInitialCapacity); } else { properties = AllocateNameDictionary(NameDictionary::kInitialCapacity); } Goto(&instantiate_map); } BIND(&instantiate_map); return AllocateJSObjectFromMap(initial_map, properties.value(), base::nullopt, AllocationFlag::kNone, kWithSlackTracking);}TNode<JSObject> ConstructorBuiltinsAssembler::FastNewObject( TNode<Context> context, TNode<JSFunction> target, TNode<JSReceiver> new_target, Label* call_runtime) { // Verify that the new target is a JSFunction. Label end(this); TNode<JSFunction> new_target_func = HeapObjectToJSFunctionWithPrototypeSlot(new_target, call_runtime); // Fast path. // Load the initial map and verify that it's in fact a map. TNode<Object> initial_map_or_proto = LoadJSFunctionPrototypeOrInitialMap(new_target_func); GotoIf(TaggedIsSmi(initial_map_or_proto), call_runtime); GotoIf(DoesntHaveInstanceType(CAST(initial_map_or_proto), MAP_TYPE), call_runtime); TNode<Map> initial_map = CAST(initial_map_or_proto); // Fall back to runtime if the target differs from the new target's // initial map constructor. TNode<Object> new_target_constructor = LoadObjectField( initial_map, Map::kConstructorOrBackPointerOrNativeContextOffset); GotoIf(TaggedNotEqual(target, new_target_constructor), call_runtime); TVARIABLE(HeapObject, properties); Label instantiate_map(this), allocate_properties(this); GotoIf(IsDictionaryMap(initial_map), &allocate_properties); { properties = EmptyFixedArrayConstant(); Goto(&instantiate_map); } BIND(&allocate_properties); { if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { properties = AllocateSwissNameDictionary(SwissNameDictionary::kInitialCapacity); } else { properties = AllocateNameDictionary(NameDictionary::kInitialCapacity); } Goto(&instantiate_map); } BIND(&instantiate_map); return AllocateJSObjectFromMap(initial_map, properties.value(), base::nullopt, AllocationFlag::kNone, kWithSlackTracking);}TNode<JSObject> ConstructorBuiltinsAssembler::FastNewObject( TNode<Context> context, TNode<JSFunction> target, TNode<JSReceiver> new_target, Label* call_runtime) { // Verify that the new target is a JSFunction. Label end(this); TNode<JSFunction> new_target_func = HeapObjectToJSFunctionWithPrototypeSlot(new_target, call_runtime);TNode<JSObject> ConstructorBuiltinsAssembler::FastNewObject( TNode<Context> context, TNode<JSFunction> target, TNode<JSReceiver> new_target, Label* call_runtime) { // Verify that the new target is a JSFunction. Label end(this); TNode<JSFunction> new_target_func = HeapObjectToJSFunctionWithPrototypeSlot(new_target, call_runtime);// Load the initial map and verify that it's in fact a map.TNode<Object> initial_map_or_proto = LoadJSFunctionPrototypeOrInitialMap(new_target_func);GotoIf(TaggedIsSmi(initial_map_or_proto), call_runtime);GotoIf(DoesntHaveInstanceType(CAST(initial_map_or_proto), MAP_TYPE), call_runtime);TNode<Map> initial_map = CAST(initial_map_or_proto);// Load the initial map and verify that it's in fact a map.TNode<Object> initial_map_or_proto = LoadJSFunctionPrototypeOrInitialMap(new_target_func);GotoIf(TaggedIsSmi(initial_map_or_proto), call_runtime);GotoIf(DoesntHaveInstanceType(CAST(initial_map_or_proto), MAP_TYPE), call_runtime);TNode<Map> initial_map = CAST(initial_map_or_proto);// Fall back to runtime if the target differs from the new target's// initial map constructor.TNode<Object> new_target_constructor = LoadObjectField( initial_map, Map::kConstructorOrBackPointerOrNativeContextOffset);GotoIf(TaggedNotEqual(target, new_target_constructor), call_runtime);// Fall back to runtime if the target differs from the new target's// initial map constructor.TNode<Object> new_target_constructor = LoadObjectField( initial_map, Map::kConstructorOrBackPointerOrNativeContextOffset);GotoIf(TaggedNotEqual(target, new_target_constructor), call_runtime); TVARIABLE(HeapObject, properties); Label instantiate_map(this), allocate_properties(this); GotoIf(IsDictionaryMap(initial_map), &allocate_properties); { properties = EmptyFixedArrayConstant(); Goto(&instantiate_map); } BIND(&allocate_properties); { if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { properties = AllocateSwissNameDictionary(SwissNameDictionary::kInitialCapacity); } else { properties = AllocateNameDictionary(NameDictionary::kInitialCapacity); } Goto(&instantiate_map); } BIND(&instantiate_map); return AllocateJSObjectFromMap(initial_map, properties.value(), base::nullopt, AllocationFlag::kNone, kWithSlackTracking);} TVARIABLE(HeapObject, properties); Label instantiate_map(this), allocate_properties(this); GotoIf(IsDictionaryMap(initial_map), &allocate_properties); { properties = EmptyFixedArrayConstant(); Goto(&instantiate_map); } BIND(&allocate_properties); { if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { properties = AllocateSwissNameDictionary(SwissNameDictionary::kInitialCapacity); } else { properties = AllocateNameDictionary(NameDictionary::kInitialCapacity); } Goto(&instantiate_map); } BIND(&instantiate_map); return AllocateJSObjectFromMap(initial_map, properties.value(), base::nullopt, AllocationFlag::kNone, kWithSlackTracking);}RUNTIME_FUNCTION(Runtime_NewObject) { HandleScope scope(isolate); DCHECK_EQ(2, args.length()); Handle<JSFunction> target = args.at<JSFunction>(0); Handle<JSReceiver> new_target = args.at<JSReceiver>(1); RETURN_RESULT_OR_FAILURE( isolate, JSObject::New(target, new_target, Handle<AllocationSite>::null()));}RUNTIME_FUNCTION(Runtime_NewObject) { HandleScope scope(isolate); DCHECK_EQ(2, args.length()); Handle<JSFunction> target = args.at<JSFunction>(0); Handle<JSReceiver> new_target = args.at<JSReceiver>(1); RETURN_RESULT_OR_FAILURE( isolate, JSObject::New(target, new_target, Handle<AllocationSite>::null()));}// staticMaybeHandle<JSObject> JSObject::New(Handle<JSFunction> constructor, Handle<JSReceiver> new_target, Handle<AllocationSite> site) { // If called through new, new.target can be: // - a subclass of constructor, // - a proxy wrapper around constructor, or // - the constructor itself. // If called through Reflect.construct, it's guaranteed to be a constructor. Isolate* const isolate = constructor->GetIsolate(); DCHECK(constructor->IsConstructor()); DCHECK(new_target->IsConstructor()); DCHECK(!constructor->has_initial_map() || !InstanceTypeChecker::IsJSFunction( constructor->initial_map().instance_type()));// staticMaybeHandle<JSObject> JSObject::New(Handle<JSFunction> constructor, Handle<JSReceiver> new_target, Handle<AllocationSite> site) { // If called through new, new.target can be: // - a subclass of constructor, // - a proxy wrapper around constructor, or // - the constructor itself. // If called through Reflect.construct, it's guaranteed to be a constructor. Isolate* const isolate = constructor->GetIsolate(); DCHECK(constructor->IsConstructor()); DCHECK(new_target->IsConstructor()); DCHECK(!constructor->has_initial_map() || !InstanceTypeChecker::IsJSFunction( constructor->initial_map().instance_type())); Handle<Map> initial_map; ASSIGN_RETURN_ON_EXCEPTION( isolate, initial_map, JSFunction::GetDerivedMap(isolate, constructor, new_target), JSObject); constexpr int initial_capacity = V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL ? SwissNameDictionary::kInitialCapacity : NameDictionary::kInitialCapacity; Handle<JSObject> result = isolate->factory()->NewFastOrSlowJSObjectFromMap( initial_map, initial_capacity, AllocationType::kYoung, site); return result;} Handle<Map> initial_map; ASSIGN_RETURN_ON_EXCEPTION( isolate, initial_map, JSFunction::GetDerivedMap(isolate, constructor, new_target), JSObject); constexpr int initial_capacity = V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL ? SwissNameDictionary::kInitialCapacity : NameDictionary::kInitialCapacity; Handle<JSObject> result = isolate->factory()->NewFastOrSlowJSObjectFromMap( initial_map, initial_capacity, AllocationType::kYoung, site); return result;}// staticMaybeHandle<Map> JSFunction::GetDerivedMap(Isolate* isolate, Handle<JSFunction> constructor, Handle<JSReceiver> new_target) { EnsureHasInitialMap(constructor); Handle<Map> constructor_initial_map(constructor->initial_map(), isolate); if (*new_target == *constructor) return constructor_initial_map; Handle<Map> result_map; // Fast case, new.target is a subclass of constructor. The map is cacheable // (and may already have been cached). new.target.prototype is guaranteed to // be a JSReceiver. if (new_target->IsJSFunction()) { Handle<JSFunction> function = Handle<JSFunction>::cast(new_target); if (FastInitializeDerivedMap(isolate, function, constructor, constructor_initial_map)) { return handle(function->initial_map(), isolate); } } // Slow path, new.target is either a proxy or can't cache the map. // new.target.prototype is not guaranteed to be a JSReceiver, and may need to // fall back to the intrinsicDefaultProto. Handle<Object> prototype; if (new_target->IsJSFunction()) { Handle<JSFunction> function = Handle<JSFunction>::cast(new_target); if (function->has_prototype_slot()) { // Make sure the new.target.prototype is cached. EnsureHasInitialMap(function); prototype = handle(function->prototype(), isolate); } else { // No prototype property, use the intrinsict default proto further down. prototype = isolate->factory()->undefined_value(); } } else { Handle<String> prototype_string = isolate->factory()->prototype_string(); ASSIGN_RETURN_ON_EXCEPTION( isolate, prototype, JSReceiver::GetProperty(isolate, new_target, prototype_string), Map); // The above prototype lookup might change the constructor and its // prototype, hence we have to reload the initial map. EnsureHasInitialMap(constructor); constructor_initial_map = handle(constructor->initial_map(), isolate); } // If prototype is not a JSReceiver, fetch the intrinsicDefaultProto from the // correct realm. Rather than directly fetching the .prototype, we fetch the // constructor that points to the .prototype. This relies on // constructor.prototype being FROZEN for those constructors. if (!prototype->IsJSReceiver()) { Handle<Context> context; ASSIGN_RETURN_ON_EXCEPTION(isolate, context, JSReceiver::GetFunctionRealm(new_target), Map); DCHECK(context->IsNativeContext()); Handle<Object> maybe_index = JSReceiver::GetDataProperty( isolate, constructor, isolate->factory()->native_context_index_symbol()); int index = maybe_index->IsSmi() ? Smi::ToInt(*maybe_index) : Context::OBJECT_FUNCTION_INDEX; Handle<JSFunction> realm_constructor(JSFunction::cast(context->get(index)), isolate); prototype = handle(realm_constructor->prototype(), isolate); } Handle<Map> map = Map::CopyInitialMap(isolate, constructor_initial_map); map->set_new_target_is_base(false); CHECK(prototype->IsJSReceiver()); if (map->prototype() != *prototype) Map::SetPrototype(isolate, map, Handle<HeapObject>::cast(prototype)); map->SetConstructor(*constructor); return map;}// staticMaybeHandle<Map> JSFunction::GetDerivedMap(Isolate* isolate, Handle<JSFunction> constructor, Handle<JSReceiver> new_target) { EnsureHasInitialMap(constructor); Handle<Map> constructor_initial_map(constructor->initial_map(), isolate); if (*new_target == *constructor) return constructor_initial_map; Handle<Map> result_map; // Fast case, new.target is a subclass of constructor. The map is cacheable // (and may already have been cached). new.target.prototype is guaranteed to // be a JSReceiver. if (new_target->IsJSFunction()) { Handle<JSFunction> function = Handle<JSFunction>::cast(new_target); if (FastInitializeDerivedMap(isolate, function, constructor, constructor_initial_map)) { return handle(function->initial_map(), isolate); } } // Slow path, new.target is either a proxy or can't cache the map. // new.target.prototype is not guaranteed to be a JSReceiver, and may need to // fall back to the intrinsicDefaultProto. Handle<Object> prototype; if (new_target->IsJSFunction()) { Handle<JSFunction> function = Handle<JSFunction>::cast(new_target); if (function->has_prototype_slot()) { // Make sure the new.target.prototype is cached. EnsureHasInitialMap(function); prototype = handle(function->prototype(), isolate); } else { // No prototype property, use the intrinsict default proto further down. prototype = isolate->factory()->undefined_value(); } } else { Handle<String> prototype_string = isolate->factory()->prototype_string(); ASSIGN_RETURN_ON_EXCEPTION( isolate, prototype, JSReceiver::GetProperty(isolate, new_target, prototype_string), Map); // The above prototype lookup might change the constructor and its // prototype, hence we have to reload the initial map. EnsureHasInitialMap(constructor); constructor_initial_map = handle(constructor->initial_map(), isolate); } // If prototype is not a JSReceiver, fetch the intrinsicDefaultProto from the // correct realm. Rather than directly fetching the .prototype, we fetch the // constructor that points to the .prototype. This relies on // constructor.prototype being FROZEN for those constructors. if (!prototype->IsJSReceiver()) { Handle<Context> context; ASSIGN_RETURN_ON_EXCEPTION(isolate, context, JSReceiver::GetFunctionRealm(new_target), Map); DCHECK(context->IsNativeContext()); Handle<Object> maybe_index = JSReceiver::GetDataProperty( isolate, constructor, isolate->factory()->native_context_index_symbol()); int index = maybe_index->IsSmi() ? Smi::ToInt(*maybe_index) : Context::OBJECT_FUNCTION_INDEX; Handle<JSFunction> realm_constructor(JSFunction::cast(context->get(index)), isolate); prototype = handle(realm_constructor->prototype(), isolate); } Handle<Map> map = Map::CopyInitialMap(isolate, constructor_initial_map); map->set_new_target_is_base(false); CHECK(prototype->IsJSReceiver()); if (map->prototype() != *prototype) Map::SetPrototype(isolate, map, Handle<HeapObject>::cast(prototype)); map->SetConstructor(*constructor); return map;}// staticMaybeHandle<Map> JSFunction::GetDerivedMap(Isolate* isolate, Handle<JSFunction> constructor, Handle<JSReceiver> new_target) { EnsureHasInitialMap(constructor); Handle<Map> constructor_initial_map(constructor->initial_map(), isolate); if (*new_target == *constructor) return constructor_initial_map;// staticMaybeHandle<Map> JSFunction::GetDerivedMap(Isolate* isolate, Handle<JSFunction> constructor, Handle<JSReceiver> new_target) { EnsureHasInitialMap(constructor); Handle<Map> constructor_initial_map(constructor->initial_map(), isolate); if (*new_target == *constructor) return constructor_initial_map;Handle<Map> result_map;// Fast case, new.target is a subclass of constructor. The map is cacheable// (and may already have been cached). new.target.prototype is guaranteed to// be a JSReceiver.if (new_target->IsJSFunction()) { Handle<JSFunction> function = Handle<JSFunction>::cast(new_target); if (FastInitializeDerivedMap(isolate, function, constructor, constructor_initial_map)) { return handle(function->initial_map(), isolate); }}Handle<Map> result_map;// Fast case, new.target is a subclass of constructor. The map is cacheable// (and may already have been cached). new.target.prototype is guaranteed to// be a JSReceiver.if (new_target->IsJSFunction()) { Handle<JSFunction> function = Handle<JSFunction>::cast(new_target); if (FastInitializeDerivedMap(isolate, function, constructor, constructor_initial_map)) { return handle(function->initial_map(), isolate); }}bool FastInitializeDerivedMap(Isolate* isolate, Handle<JSFunction> new_target, Handle<JSFunction> constructor, Handle<Map> constructor_initial_map) { // Use the default intrinsic prototype instead. if (!new_target->has_prototype_slot()) return false; // Check that |function|'s initial map still in sync with the |constructor|, // otherwise we must create a new initial map for |function|. if (new_target->has_initial_map() && new_target->initial_map().GetConstructor() == *constructor) { DCHECK(new_target->instance_prototype().IsJSReceiver()); return true; } InstanceType instance_type = constructor_initial_map->instance_type(); DCHECK(CanSubclassHaveInobjectProperties(instance_type)); // Create a new map with the size and number of in-object properties // suggested by |function|. // Link initial map and constructor function if the new.target is actually a // subclass constructor. if (!IsDerivedConstructor(new_target->shared().kind())) return false; int instance_size; int in_object_properties; int embedder_fields = JSObject::GetEmbedderFieldCount(*constructor_initial_map); // Constructor expects certain number of in-object properties to be in the // object. However, CalculateExpectedNofProperties() may return smaller value // if 1) the constructor is not in the prototype chain of new_target, or // 2) the prototype chain is modified during iteration, or 3) compilation // failure occur during prototype chain iteration. // So we take the maximum of two values. int expected_nof_properties = std::max( static_cast<int>(constructor->shared().expected_nof_properties()), JSFunction::CalculateExpectedNofProperties(isolate, new_target)); JSFunction::CalculateInstanceSizeHelper( instance_type, constructor_initial_map->has_prototype_slot(), embedder_fields, expected_nof_properties, &instance_size, &in_object_properties); int pre_allocated = constructor_initial_map->GetInObjectProperties() - constructor_initial_map->UnusedPropertyFields(); CHECK_LE(constructor_initial_map->UsedInstanceSize(), instance_size); int unused_property_fields = in_object_properties - pre_allocated; Handle<Map> map = Map::CopyInitialMap(isolate, constructor_initial_map, instance_size, in_object_properties, unused_property_fields); map->set_new_target_is_base(false); Handle<HeapObject> prototype(new_target->instance_prototype(), isolate); JSFunction::SetInitialMap(isolate, new_target, map, prototype, constructor); DCHECK(new_target->instance_prototype().IsJSReceiver()); map->set_construction_counter(Map::kNoSlackTracking); map->StartInobjectSlackTracking(); return true;}bool FastInitializeDerivedMap(Isolate* isolate, Handle<JSFunction> new_target, Handle<JSFunction> constructor, Handle<Map> constructor_initial_map) { // Use the default intrinsic prototype instead. if (!new_target->has_prototype_slot()) return false; // Check that |function|'s initial map still in sync with the |constructor|, // otherwise we must create a new initial map for |function|. if (new_target->has_initial_map() && new_target->initial_map().GetConstructor() == *constructor) { DCHECK(new_target->instance_prototype().IsJSReceiver()); return true; } InstanceType instance_type = constructor_initial_map->instance_type(); DCHECK(CanSubclassHaveInobjectProperties(instance_type)); // Create a new map with the size and number of in-object properties // suggested by |function|. // Link initial map and constructor function if the new.target is actually a // subclass constructor. if (!IsDerivedConstructor(new_target->shared().kind())) return false; int instance_size; int in_object_properties; int embedder_fields = JSObject::GetEmbedderFieldCount(*constructor_initial_map); // Constructor expects certain number of in-object properties to be in the // object. However, CalculateExpectedNofProperties() may return smaller value // if 1) the constructor is not in the prototype chain of new_target, or // 2) the prototype chain is modified during iteration, or 3) compilation // failure occur during prototype chain iteration. // So we take the maximum of two values. int expected_nof_properties = std::max( static_cast<int>(constructor->shared().expected_nof_properties()), JSFunction::CalculateExpectedNofProperties(isolate, new_target)); JSFunction::CalculateInstanceSizeHelper( instance_type, constructor_initial_map->has_prototype_slot(), embedder_fields, expected_nof_properties, &instance_size, &in_object_properties); int pre_allocated = constructor_initial_map->GetInObjectProperties() - constructor_initial_map->UnusedPropertyFields(); CHECK_LE(constructor_initial_map->UsedInstanceSize(), instance_size); int unused_property_fields = in_object_properties - pre_allocated; Handle<Map> map = Map::CopyInitialMap(isolate, constructor_initial_map, instance_size, in_object_properties, unused_property_fields); map->set_new_target_is_base(false); Handle<HeapObject> prototype(new_target->instance_prototype(), isolate); JSFunction::SetInitialMap(isolate, new_target, map, prototype, constructor); DCHECK(new_target->instance_prototype().IsJSReceiver()); map->set_construction_counter(Map::kNoSlackTracking); map->StartInobjectSlackTracking(); return true;}bool FastInitializeDerivedMap(Isolate* isolate, Handle<JSFunction> new_target, Handle<JSFunction> constructor, Handle<Map> constructor_initial_map) { // Use the default intrinsic prototype instead. if (!new_target->has_prototype_slot()) return false; // Check that |function|'s initial map still in sync with the |constructor|, // otherwise we must create a new initial map for |function|. if (new_target->has_initial_map() && new_target->initial_map().GetConstructor() == *constructor) { DCHECK(new_target->instance_prototype().IsJSReceiver()); return true; }bool FastInitializeDerivedMap(Isolate* isolate, Handle<JSFunction> new_target, Handle<JSFunction> constructor, Handle<Map> constructor_initial_map) { // Use the default intrinsic prototype instead. if (!new_target->has_prototype_slot()) return false; // Check that |function|'s initial map still in sync with the |constructor|, // otherwise we must create a new initial map for |function|. if (new_target->has_initial_map() && new_target->initial_map().GetConstructor() == *constructor) { DCHECK(new_target->instance_prototype().IsJSReceiver()); return true; }// Slow path, new.target is either a proxy or can't cache the map.// new.target.prototype is not guaranteed to be a JSReceiver, and may need to// fall back to the intrinsicDefaultProto.Handle<Object> prototype;if (new_target->IsJSFunction()) { Handle<JSFunction> function = Handle<JSFunction>::cast(new_target); if (function->has_prototype_slot()) { // Make sure the new.target.prototype is cached. EnsureHasInitialMap(function); prototype = handle(function->prototype(), isolate); } else { // No prototype property, use the intrinsict default proto further down. prototype = isolate->factory()->undefined_value(); }} else { Handle<String> prototype_string = isolate->factory()->prototype_string(); ASSIGN_RETURN_ON_EXCEPTION( isolate, prototype, JSReceiver::GetProperty(isolate, new_target, prototype_string), Map); // The above prototype lookup might change the constructor and its // prototype, hence we have to reload the initial map. EnsureHasInitialMap(constructor); constructor_initial_map = handle(constructor->initial_map(), isolate);}// Slow path, new.target is either a proxy or can't cache the map.// new.target.prototype is not guaranteed to be a JSReceiver, and may need to// fall back to the intrinsicDefaultProto.Handle<Object> prototype;if (new_target->IsJSFunction()) { Handle<JSFunction> function = Handle<JSFunction>::cast(new_target); if (function->has_prototype_slot()) { // Make sure the new.target.prototype is cached. EnsureHasInitialMap(function); prototype = handle(function->prototype(), isolate); } else { // No prototype property, use the intrinsict default proto further down. prototype = isolate->factory()->undefined_value(); }} else { Handle<String> prototype_string = isolate->factory()->prototype_string(); ASSIGN_RETURN_ON_EXCEPTION( isolate, prototype, JSReceiver::GetProperty(isolate, new_target, prototype_string), Map); // The above prototype lookup might change the constructor and its // prototype, hence we have to reload the initial map. EnsureHasInitialMap(constructor); constructor_initial_map = handle(constructor->initial_map(), isolate);}function f0(a1){ const v5 = new Array(7); for (let v6 = 0; v6 < 25; v6++) { v5["p" + v6] = v6; }}f0(f0());%OptimizeMaglevOnNextCall(f0);f0(f0());function f0(a1){ const v5 = new Array(7); for (let v6 = 0; v6 < 25; v6++) { v5["p" + v6] = v6; }}f0(f0());%OptimizeMaglevOnNextCall(f0);f0(f0());0x363c0019adf2 @ 0 : 21 00 00 LdaGlobal [0], [0] ;load global “Array”,从下面的常量池取出来0x363c0019adf5 @ 3 : c3 Star2 ;将值 store 到 r20x363c0019adf6 @ 4 : 0d 07 LdaSmi [7] ;将smi 7 load 到 累加器中0x363c0019adf8 @ 6 : c2 Star3 ;将累加器中的值 store 到 r30x363c0019adf9 @ 7 : 0b f8 Ldar r2 ;将r2的值 load 到累加器中0x363c0019adfb @ 9 : 69 f8 f7 01 02 Construct r2, r3-r3, [2];r2是Array r3是7 => new Array(7)0x363c0019ae00 @ 14 : c5 Star0 ;值存到r00x363c0019ae01 @ 15 : 0c LdaZero ;0存到累加器0x363c0019ae02 @ 16 : c4 Star1 ;累加器的值存到r10x363c0019ae03 @ 17 : 0d 19 LdaSmi [25] ;smi 25存到 累加器0x363c0019ae05 @ 19 : 6d f9 04 TestLessThan r1, [4] ;比较r1和smi 4,对应的是for循环的判断0x363c0019ae08 @ 22 : 9a 1a JumpIfFalse [26] (0x363c0019ae22 @ 48);如果返回值为false 则跳转到偏移为48的地方,对应后方的LdaUndefined指令0x363c0019ae0a @ 24 : 13 01 LdaConstant [1] ;常量池[1],也就是p,load到累加器0x363c0019ae0c @ 26 : c2 Star3 ;再存到r30x363c0019ae0d @ 27 : 0b f9 Ldar r1 ;r1存到累加器0x363c0019ae0f @ 29 : 38 f7 05 Add r3, [5] ;r3+smi[5],对应"p" + v60x363c0019ae12 @ 32 : c2 Star3 ;值存到r30x363c0019ae13 @ 33 : 0b f9 Ldar r1 ;r1存到累加器0x363c0019ae15 @ 35 : 34 fa f7 06 SetKeyedProperty r0, r3, [6];r0是上面创建的Array,然后r3是设置的val,索引赋值为smi[6]0x363c0019ae19 @ 39 : 0b f9 Ldar r1 ;r1存到累加器0x363c0019ae1b @ 41 : 50 08 Inc [8] ;r1+8,也就是v6++0x363c0019ae1d @ 43 : c4 Star1 ;累加器的值存到r10x363c0019ae1e @ 44 : 8a 1b 00 09 JumpLoop [27], [0], [9] (0x363c0019ae03 @ 17);跳转到偏移为17的地方,也就是这个指令的位置LdaSmi [25] 0x363c0019ae22 @ 48 : 0e LdaUndefined0x363c0019ae23 @ 49 : aa Return0x363c0019adf2 @ 0 : 21 00 00 LdaGlobal [0], [0] ;load global “Array”,从下面的常量池取出来0x363c0019adf5 @ 3 : c3 Star2 ;将值 store 到 r20x363c0019adf6 @ 4 : 0d 07 LdaSmi [7] ;将smi 7 load 到 累加器中0x363c0019adf8 @ 6 : c2 Star3 ;将累加器中的值 store 到 r30x363c0019adf9 @ 7 : 0b f8 Ldar r2 ;将r2的值 load 到累加器中0x363c0019adfb @ 9 : 69 f8 f7 01 02 Construct r2, r3-r3, [2];r2是Array r3是7 => new Array(7)0x363c0019ae00 @ 14 : c5 Star0 ;值存到r00x363c0019ae01 @ 15 : 0c LdaZero ;0存到累加器0x363c0019ae02 @ 16 : c4 Star1 ;累加器的值存到r10x363c0019ae03 @ 17 : 0d 19 LdaSmi [25] ;smi 25存到 累加器0x363c0019ae05 @ 19 : 6d f9 04 TestLessThan r1, [4] ;比较r1和smi 4,对应的是for循环的判断0x363c0019ae08 @ 22 : 9a 1a JumpIfFalse [26] (0x363c0019ae22 @ 48);如果返回值为false 则跳转到偏移为48的地方,对应后方的LdaUndefined指令0x363c0019ae0a @ 24 : 13 01 LdaConstant [1] ;常量池[1],也就是p,load到累加器0x363c0019ae0c @ 26 : c2 Star3 ;再存到r30x363c0019ae0d @ 27 : 0b f9 Ldar r1 ;r1存到累加器0x363c0019ae0f @ 29 : 38 f7 05 Add r3, [5] ;r3+smi[5],对应"p" + v60x363c0019ae12 @ 32 : c2 Star3 ;值存到r30x363c0019ae13 @ 33 : 0b f9 Ldar r1 ;r1存到累加器0x363c0019ae15 @ 35 : 34 fa f7 06 SetKeyedProperty r0, r3, [6];r0是上面创建的Array,然后r3是设置的val,索引赋值为smi[6]0x363c0019ae19 @ 39 : 0b f9 Ldar r1 ;r1存到累加器0x363c0019ae1b @ 41 : 50 08 Inc [8] ;r1+8,也就是v6++0x363c0019ae1d @ 43 : c4 Star1 ;累加器的值存到r10x363c0019ae1e @ 44 : 8a 1b 00 09 JumpLoop [27], [0], [9] (0x363c0019ae03 @ 17);跳转到偏移为17的地方,也就是这个指令的位置LdaSmi [25] 0x363c0019ae22 @ 48 : 0e LdaUndefined0x363c0019ae23 @ 49 : aa Returndiff --git a/src/maglev/maglev-graph-builder.cc b/src/maglev/maglev-graph-builder.ccindex d5f6128..2c5227e 100644--- a/src/maglev/maglev-graph-builder.cc+++ b/src/maglev/maglev-graph-builder.cc@@ -5347,6 +5347,14 @@ StoreRegister(iterator_.GetRegisterOperand(0), map_proto); } +bool MaglevGraphBuilder::HasValidInitialMap(+ compiler::JSFunctionRef new_target, compiler::JSFunctionRef constructor) {+ if (!new_target.map(broker()).has_prototype_slot()) return false;+ if (!new_target.has_initial_map(broker())) return false;+ compiler::MapRef initial_map = new_target.initial_map(broker());+ return initial_map.GetConstructor(broker()).equals(constructor);+}+ void MaglevGraphBuilder::VisitFindNonDefaultConstructorOrConstruct() { ValueNode* this_function = LoadRegisterTagged(0); ValueNode* new_target = LoadRegisterTagged(1);@@ -5380,7 +5388,9 @@ TryGetConstant(new_target); if (kind == FunctionKind::kDefaultBaseConstructor) { ValueNode* object;- if (new_target_function && new_target_function->IsJSFunction()) {+ if (new_target_function && new_target_function->IsJSFunction() &&+ HasValidInitialMap(new_target_function->AsJSFunction(),+ current_function)) { object = BuildAllocateFastObject( FastObject(new_target_function->AsJSFunction(), zone(), broker()),diff --git a/src/maglev/maglev-graph-builder.h b/src/maglev/maglev-graph-builder.hindex 0abb4a8..d92354c 100644--- a/src/maglev/maglev-graph-builder.h+++ b/src/maglev/maglev-graph-builder.h@@ -1884,6 +1884,9 @@ void MergeDeadLoopIntoFrameState(int target); void MergeIntoInlinedReturnFrameState(BasicBlock* block); + bool HasValidInitialMap(compiler::JSFunctionRef new_target,+ compiler::JSFunctionRef constructor);+ enum JumpType { kJumpIfTrue, kJumpIfFalse }; enum class BranchSpecializationMode { kDefault, kAlwaysBoolean }; JumpType NegateJumpType(JumpType jump_type);diff --git a/test/mjsunit/maglev/regress/regress-crbug-1465326.js b/test/mjsunit/maglev/regress/regress-crbug-1465326.jsnew file mode 100644index 0000000..6e01c1e--- /dev/null+++ b/test/mjsunit/maglev/regress/regress-crbug-1465326.js@@ -0,0 +1,25 @@+// Copyright 2023 the V8 project authors. All rights reserved.+// Use of this source code is governed by a BSD-style license that can be+// found in the LICENSE file.++// Flags: --maglev --allow-natives-syntax++class A {}++var x = Function;++class B extends A {+ constructor() {+ x = new.target;+ super();+ }+}+function construct() {+ return Reflect.construct(B, [], Function);+}+%PrepareFunctionForOptimization(B);+construct();+construct();+%OptimizeMaglevOnNextCall(B);+var arr = construct();+console.log(arr.prototype);diff --git a/src/maglev/maglev-graph-builder.cc b/src/maglev/maglev-graph-builder.ccindex d5f6128..2c5227e 100644--- a/src/maglev/maglev-graph-builder.cc+++ b/src/maglev/maglev-graph-builder.cc@@ -5347,6 +5347,14 @@ StoreRegister(iterator_.GetRegisterOperand(0), map_proto); } +bool MaglevGraphBuilder::HasValidInitialMap(+ compiler::JSFunctionRef new_target, compiler::JSFunctionRef constructor) {+ if (!new_target.map(broker()).has_prototype_slot()) return false;+ if (!new_target.has_initial_map(broker())) return false;+ compiler::MapRef initial_map = new_target.initial_map(broker());+ return initial_map.GetConstructor(broker()).equals(constructor);+}+ void MaglevGraphBuilder::VisitFindNonDefaultConstructorOrConstruct() { ValueNode* this_function = LoadRegisterTagged(0); ValueNode* new_target = LoadRegisterTagged(1);@@ -5380,7 +5388,9 @@ TryGetConstant(new_target); if (kind == FunctionKind::kDefaultBaseConstructor) { ValueNode* object;- if (new_target_function && new_target_function->IsJSFunction()) {+ if (new_target_function && new_target_function->IsJSFunction() &&+ HasValidInitialMap(new_target_function->AsJSFunction(),+ current_function)) { object = BuildAllocateFastObject( FastObject(new_target_function->AsJSFunction(), zone(), broker()),diff --git a/src/maglev/maglev-graph-builder.h b/src/maglev/maglev-graph-builder.hindex 0abb4a8..d92354c 100644--- a/src/maglev/maglev-graph-builder.h+++ b/src/maglev/maglev-graph-builder.h@@ -1884,6 +1884,9 @@ void MergeDeadLoopIntoFrameState(int target); void MergeIntoInlinedReturnFrameState(BasicBlock* block); + bool HasValidInitialMap(compiler::JSFunctionRef new_target,+ compiler::JSFunctionRef constructor);+ enum JumpType { kJumpIfTrue, kJumpIfFalse }; enum class BranchSpecializationMode { kDefault, kAlwaysBoolean }; JumpType NegateJumpType(JumpType jump_type);diff --git a/test/mjsunit/maglev/regress/regress-crbug-1465326.js b/test/mjsunit/maglev/regress/regress-crbug-1465326.jsnew file mode 100644index 0000000..6e01c1e--- /dev/null+++ b/test/mjsunit/maglev/regress/regress-crbug-1465326.js@@ -0,0 +1,25 @@+// Copyright 2023 the V8 project authors. All rights reserved.+// Use of this source code is governed by a BSD-style license that can be+// found in the LICENSE file.++// Flags: --maglev --allow-natives-syntax++class A {}++var x = Function;++class B extends A {+ constructor() {+ x = new.target;+ super();+ }+}+function construct() {+ return Reflect.construct(B, [], Function);+}+%PrepareFunctionForOptimization(B);+construct();+construct();+%OptimizeMaglevOnNextCall(B);+var arr = construct();+console.log(arr.prototype);[generated bytecode for function: Derived (0x3f5c0019ac6d <SharedFunctionInfo Derived>)]Bytecode length: 39Parameter count 1Register count 7Frame size 56Bytecode age: 0 0x3f5c0019b0be @ 0 : 19 fe f9 Mov <closure>, r1 0x3f5c0019b0c1 @ 3 : 5a f9 fa f5 FindNonDefaultConstructorOrConstruct r1, r0, r5-r6 0x3f5c0019b0c5 @ 7 : 0b f5 Ldar r5 0x3f5c0019b0c7 @ 9 : 19 f9 f8 Mov r1, r2 0x3f5c0019b0ca @ 12 : 19 fa f6 Mov r0, r4 0x3f5c0019b0cd @ 15 : 19 f4 f7 Mov r6, r3 0x3f5c0019b0d0 @ 18 : 99 0c JumpIfTrue [12] (0x3f5c0019b0dc @ 30) 0x3f5c0019b0d2 @ 20 : ae f7 ThrowIfNotSuperConstructor r3 0x3f5c0019b0d4 @ 22 : 0b f6 Ldar r4 0x3f5c0019b0d6 @ 24 : 69 f7 fa 00 00 Construct r3, r0-r0, [0] 0x3f5c0019b0db @ 29 : c2 Star3 0x3f5c0019b0dc @ 30 : 0b 02 Ldar <this> 0x3f5c0019b0de @ 32 : ad ThrowSuperAlreadyCalledIfNotHole 0x3f5c0019b0df @ 33 : 19 f7 02 Mov r3, <this> 0x3f5c0019b0e2 @ 36 : 0b 02 Ldar <this> 0x3f5c0019b0e4 @ 38 : aa ReturnConstant pool (size = 0)Handler Table (size = 0)Source Position Table (size = 0)[generated bytecode for function: Derived (0x3f5c0019ac6d <SharedFunctionInfo Derived>)]Bytecode length: 39