首页
社区
课程
招聘
[原创] CVE-2023-4069:Type confusion in VisitFindNonDefaultConstructorOrConstruct of Maglev
发表于: 2024-4-13 21:07 11760

[原创] CVE-2023-4069:Type confusion in VisitFindNonDefaultConstructorOrConstruct of Maglev

2024-4-13 21:07
11760

@

最近在学习 Maglev 相关知识,然后看了一些与其相关的 CVE,感觉该漏洞比较容易复现,所以先打算复现一下,本文还是主要分析漏洞产生的原因,基础知识笔者会简单说一说,更多的还是需要读者自己去学习

这里说一下为什么笔者不愿意在漏洞分析中写过多的前置知识,因为笔者认为读者都已经开始复现漏洞了,那么对基础知识应当是有一定的了解了,并且笔者的基础也比较差,所以不希望误人子弟,网上的资料很多,自己学学就 OK 啦

new 关键字new func() 效果为:

这里给一个示例代码:

new.target 这里自行看资料

Reflect.construct(target, argument, new_target) 函数以 target 为构造函数创建对象,这里 new_target 提供原型,然后行为跟 new func() 类似

上面的知识都比较简单,所以也不想细说了,如果读者不是很清楚的话,请自行查阅下相关资料吧,这里主要关注 JS 引擎层面的实现

对于默认对象,其是通过 FastNewObject 函数创建的,其调用链如下:

先来看看 TF_BUILTIN(FastNewObject, ConstructorBuiltinsAssembler)

该函数比较简单,其主要就是调用了 ConstructorBuiltinsAssembler::FastNewObject 函数:

可以看到 ConstructorBuiltinsAssembler::FastNewObject 分为快速路径和慢速路径:

这里的快速路径可能有点奇怪?因为这里 target 才是构造函数,所以默认对象的 map 再怎么说也不应该与 new_targetinitial_map 相同,但这其实是一个优化,其会将 targetinitial_mapnew_targetprototype 缓存在 new_targetinitial_map 域,这个后面再说

然后可以看到走快速路径是存在两个条件的,不满足则会走慢速路径 Runtime::kNewObjec

可以看到其直接调用了 JSObject::New 函数:

【1】 处会调用 JSFunction::GetDerivedMap 函数,这里的 constructor 传入的是 target

可以看到其会调用 FastInitializeDerivedMapnew_target 创建 initial_map

可以看到在 【2】 处设置了 new_targetinitial_mapmap,但是修改了 prototypenew_targetprototypeconstructortarget。而该 map【1】 处是通过复制 constructor_initial_map 来的,看到这里可能就明白了之前快速路径的逻辑

所以在快速路径中,当 new_target.initial_map.constructor = target 时,则说明 new_target.initial_maptarget.initial_map 是相同的,所以这里就会直接使用 new_target.initial_map

OK,以上就是后面漏洞分析需要的一些基础知识

还是先从 patch 入手:

从补丁打的位置可以知道该漏洞应该发生在 Maglev 的图构建阶段,并且其主要打在了 MaglevGraphBuilder::VisitFindNonDefaultConstructorOrConstruct 函数中,根据函数名大概知道其主要就是处理 FindNonDefaultConstructorOrConstruct 字节码的,而该操作的功能为“寻找非默认构造函数”,这里结合 chatGPT 食用:

在 V8 引擎中,FindNonDefaultConstructorOrConstruct 字节码指令用于查找非默认构造函数或构造器函数。这个字节码指令在 JavaScript 代码中的类构造过程中使用。

当在 JavaScript 中创建一个类并调用 new 关键字来实例化对象时,V8 引擎会执行相应的字节码指令序列。其中,FindNonDefaultConstructorOrConstruct 字节码指令用于查找适当的构造函数或构造器函数。

具体而言,该指令会检查类的原型链以查找适合的构造函数。它首先尝试查找类自身的 constructor 属性,如果找到则使用该构造函数。否则,它会沿着原型链向上查找,直到找到一个非默认构造函数或构造器函数。

这个过程是为了确保在类继承链中正确地选择构造函数,以便在实例化对象时执行相应的初始化逻辑。

所以可以写出如下代码去生成目标字节码:

来看下 B 产生的字节码:

可以看到这里确实生成了 FindNonDefaultConstructorOrConstruct 字节码,其实可以把它看成一种优化,其会遍历 B 的原型链,尽量的忽略哪些默认构造函数,如果最后到达原型链顶层,则调用 FastNewObject 创建默认对象

现在我们回到主要的补丁代码:

可以看到这里的补丁仅仅对 new_targetinitial_map 进行了检查,而之前的漏洞代码并没有对 new_targetinitial_map 进行合法性检查,我们看下这个函数的关键逻辑:

可以看到如果最后到达顶层构造函数,并且 new_target 是一个 JSFunction 对象,则会调用 BuildAllocateFastObject 进行默认对象的创建,而不是调用之前分析的 TF_BUILTIN(FastNewObject, ConstructorBuiltinsAssembler) 函数进行创建,但是这里调用 BuildAllocateFastObject 时,没用对 new_targetinitial_map 进行合法性检查,然后这里第一个参数是通过 FastObject 构造函数创建的,跟进看看:

注意我们传入的参数是 new_target_function,所以可以看到这里的 map 就是 new_target_function.initial_map

然后可以跟进 BuildAllocateFastObject 函数看看:

该函数有多个实现,可以根据参数类型判断具体调用了那个函数

所以这里的关键点就是其把默认对象的 map 设置为了 new_target.initial_map,这便是漏洞之处,通过之前的分析我们知道,调用 BuildAllocateFastObject 函数之前没有对 new_target.initial_map 进行合法性检查,所以最后可以导致的效果为:

那么这时如果 new_targettargetinitial_map 不相同,则可能导致属性初始化错误,比如 new_targetinitial_mapJSArray,那么此时就会导致 target 忽略对默认对象 length 属性的初始化

漏洞触发

想要到达漏洞代码逻辑,得使以下关键判断成立:

先来看下 TryGetConstant 函数:

注:这里 TryGetConstant 存在多个实现,但没办法用参数进行判断调用了哪一个,所以这里参考参考文章的分析

由于还没开始审计 Maglev 源码,所以这里笔者不是很懂,简单来说就是这里有两个路径可以进行判断 node 是否是 constant

这里 【1】 路径行不通,所以这里利用【2】路径进行绕过,其主要就是插入一个 CheckValue 节点,而该节点会标记该 node 为一个 constant

这里直接看 poc

看下 Maglev IR
在这里插入图片描述
可以看到这里确实产生了 CheckValue 节点,并且这里可以看 Map 的值:
在这里插入图片描述
可以看到其直接赋值的 new_target.initial_map,而 new_target.initial_map 的类型为 JSArray
在这里插入图片描述
来看下初始化过程:
在这里插入图片描述
这里的 4/8 明显是 properties/element 的偏移,但是其却没有对 length 进行赋值,可以看到print(arr instanceof Array) 输出的是 true,即 arr 是一个数组:
在这里插入图片描述

这里主要利用的是 JSObjectJSArray 的类型混淆,JSObject 是不存在 length 属性的,而 JSArray 存在 length 属性,所以如果 targetJSObject,而 new_targetJSArray,那么触发漏洞后,target 就不会初始化创建对象的 length 属性,所以这里的 length 就是一个未初始化的值,如果这个未初始化 length 值比较大,就可以实现越界读写

有了越界读写,后面写利用就比较简单了,很多利用手法都大同小异,所以就不细说了,主要说下关键点:

虽然没有对 length 进行初始化,但是一般(没有 gc)时,length 就为 0,所以这里先提前触发 gc 去移动对象,这样有概率存在残留数据覆盖 length,如果 length 比较大,就可以实现越界读写

由于 JSObect 对象不使用 element 属性,所以这里 element 指向 FixedArray[0],其值在笔者机器上固定为 0x219,其属于的页权限为只读权限,但是这里问题不大,因为 0x219 基本就在堆的最低地址处了,只要被覆盖的 length 比较大,就可以实现越界读写

exploit 如下:

效果如下:
在这里插入图片描述

通过对该 CVE 的分析利用,对 Maglev 有了基本的了解,但是还有一些细节上的东西没有搞清楚,这个只能后面对 Mgalev 逐渐熟悉后再看看了

然后看了下腾讯玄武去年的 talk,发现 Maglev 是一个很不错的攻击面(玄武的大佬好像直接挖了7个洞),其与 trubofan 有部分相同的性质,而其又具有独特的攻击面,所以笔者感觉可以将 turbofan 的一些历史漏洞去套下 Maglev。当然了,随着 chrometurbofan 保护强度的逐步上升,目前想在 trubofan 中出一个洞可以说是非常难了,而 Maglev 应该是目前最能够出 JIT 洞的了,当然自己目前太菜了,也希望早日能够挖到自己的漏洞

说点别的,瞎扯一下:目前其实也复现了很多漏洞,但是对挖漏洞其实还是比较迷茫的,自己也花了一些时间总结了下,发现自己虽然在复现一些漏洞,但是很多漏洞都比较老,并且在复现漏洞的时候没有去总结可能的攻击面,看了很多大佬的 talk,发现选取攻击面是非常重要的,就 chrome 而言,其有很多组件,每个组件又有不同的功能分支,如何针对性的进行 fuzz 是非常重要的。也希望后面可以跟踪一些比较前沿的攻击手法和比较新的攻击面。目前在笔者看来,复现漏洞就两个目的,第一就是熟悉某个知识点,就比如笔者之所以复现该漏洞其实就是为了巩固下 Maglev 中的一些东西;第二就是总结攻击面,这个目前笔者做的比较失败。然后单纯的为了写利用去复现漏洞是没有意义的,漏洞那么多,是不能全部复现的。所以后面笔者复现漏洞也会有针对性的复现,会尽量选取一些比较新的漏洞进行复现

人一定要学会反思,不然就如同行尸走肉一般

Getting RCE in Chrome with incomplete object initialization in the Maglev compiler

git checkout 7f22404388ef0eb9383f189c1b0a85b5ea93b079
gclient sync -D
git checkout 7f22404388ef0eb9383f189c1b0a85b5ea93b079
gclient sync -D
class A {
        constructor() {
                this.x = 1;
        }
}
 
class B {
        constructor() {
                this.x = 1;
                return [1.1, 2.2];
        }
}
 
var a = new A();
var b = new B();
print(a); // [object Object]
print(b); // 1.1,2.2
class A {
        constructor() {
                this.x = 1;
        }
}
 
class B {
        constructor() {
                this.x = 1;
                return [1.1, 2.2];
        }
}
 
var a = new A();
var b = new B();
print(a); // [object Object]
print(b); // 1.1,2.2
TF_BUILTIN(FastNewObject, ConstructorBuiltinsAssembler)
TNode<JSObject> ConstructorBuiltinsAssembler::FastNewObject(
                                                TNode<Context> context,
                                                TNode<JSFunction> target,
                                                TNode<JSReceiver> new_target)
        ⇒ TNode<JSObject> ConstructorBuiltinsAssembler::FastNewObject(
                                                TNode<Context> context,
                                                TNode<JSFunction> target,
                                                TNode<JSReceiver> new_target,
                                                Label* call_runtime)
TF_BUILTIN(FastNewObject, ConstructorBuiltinsAssembler)
TNode<JSObject> ConstructorBuiltinsAssembler::FastNewObject(
                                                TNode<Context> context,
                                                TNode<JSFunction> target,
                                                TNode<JSReceiver> new_target)
        ⇒ TNode<JSObject> ConstructorBuiltinsAssembler::FastNewObject(
                                                TNode<Context> context,
                                                TNode<JSFunction> target,
                                                TNode<JSReceiver> new_target,
                                                Label* call_runtime)
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);
  // 先调用 FastNewObject
  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);
  // 先调用 FastNewObject
  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);
  // 检测 new_target 是否是 JSFunction
  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.
  // 加载 new_target_func 的 initial_map or proto
  TNode<Object> initial_map_or_proto = LoadJSFunctionPrototypeOrInitialMap(new_target_func);
  // 如果 initial_map_or_proto 是 Smi,则调用 call_runtime 运行时函数(相当于慢速路径了)
  GotoIf(TaggedIsSmi(initial_map_or_proto), call_runtime);
  // 检查  initial_map_or_proto  是否是 Map
  GotoIf(DoesntHaveInstanceType(CAST(initial_map_or_proto), MAP_TYPE), call_runtime);
  // initial_map 是一个 Map
  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.
  // 加载 initial_map 的构造函数 new_target_constructor
  TNode<Object> new_target_constructor = LoadObjectField(initial_map, Map::kConstructorOrBackPointerOrNativeContextOffset);
  // 如果 target != new_target_constructor,则走慢速路径
  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 (非字典模式)
    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);
  // 最后根据 initial_map 创建 JSObject
  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);
  // 检测 new_target 是否是 JSFunction
  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.
  // 加载 new_target_func 的 initial_map or proto
  TNode<Object> initial_map_or_proto = LoadJSFunctionPrototypeOrInitialMap(new_target_func);
  // 如果 initial_map_or_proto 是 Smi,则调用 call_runtime 运行时函数(相当于慢速路径了)
  GotoIf(TaggedIsSmi(initial_map_or_proto), call_runtime);
  // 检查  initial_map_or_proto  是否是 Map
  GotoIf(DoesntHaveInstanceType(CAST(initial_map_or_proto), MAP_TYPE), call_runtime);
  // initial_map 是一个 Map
  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.
  // 加载 initial_map 的构造函数 new_target_constructor
  TNode<Object> new_target_constructor = LoadObjectField(initial_map, Map::kConstructorOrBackPointerOrNativeContextOffset);
  // 如果 target != new_target_constructor,则走慢速路径
  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 (非字典模式)
    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);
  // 最后根据 initial_map 创建 JSObject
  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()));
}
MaybeHandle<JSObject> JSObject::New(Handle<JSFunction> constructor,
                                    Handle<JSReceiver> new_target,
                                    Handle<AllocationSite> site) {
  // 这里可以看下注释:其对 new / Reflect.construct 的 new.target 存在不同的要求
  // 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;
  //【1】
  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;
}
MaybeHandle<JSObject> JSObject::New(Handle<JSFunction> constructor,
                                    Handle<JSReceiver> new_target,
                                    Handle<AllocationSite> site) {
  // 这里可以看下注释:其对 new / Reflect.construct 的 new.target 存在不同的要求
  // 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;
  //【1】
  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;
}
MaybeHandle<Map> JSFunction::GetDerivedMap(Isolate* isolate,
                                           Handle<JSFunction> constructor,
                                           Handle<JSReceiver> new_target) {
  // 为 constructor 即 target 分配 initial_map
  EnsureHasInitialMap(constructor);
 
  Handle<Map> constructor_initial_map(constructor->initial_map(), isolate);
  // 如果 target == new_target,则直接返回
  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.
  // 否则为 new_target 创建 initial_map
  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);
    }
  }
MaybeHandle<Map> JSFunction::GetDerivedMap(Isolate* isolate,
                                           Handle<JSFunction> constructor,
                                           Handle<JSReceiver> new_target) {
  // 为 constructor 即 target 分配 initial_map
  EnsureHasInitialMap(constructor);
 
  Handle<Map> constructor_initial_map(constructor->initial_map(), isolate);
  // 如果 target == new_target,则直接返回
  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.
  // 否则为 new_target 创建 initial_map
  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.
  // new_target 不是一个 JSFunction,返回 false 表示失败
  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|.
  // 如果 new_target 存在 initial_map,并且 initial_map.constructor 就是 target
  //    则说明之前已经缓存过了,所以直接返回 true
  if (new_target->has_initial_map() &&
        new_target->initial_map().GetConstructor() == *constructor) {
    DCHECK(new_target->instance_prototype().IsJSReceiver());
    return true;
  }
  // 否则创建新的 map
......
  // 【1】
  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);
  // 【2】
  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.
  // new_target 不是一个 JSFunction,返回 false 表示失败
  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|.
  // 如果 new_target 存在 initial_map,并且 initial_map.constructor 就是 target
  //    则说明之前已经缓存过了,所以直接返回 true
  if (new_target->has_initial_map() &&
        new_target->initial_map().GetConstructor() == *constructor) {
    DCHECK(new_target->instance_prototype().IsJSReceiver());
    return true;
  }
  // 否则创建新的 map
......
  // 【1】
  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);
  // 【2】
  JSFunction::SetInitialMap(isolate, new_target, map, prototype, constructor);
  DCHECK(new_target->instance_prototype().IsJSReceiver());
  map->set_construction_counter(Map::kNoSlackTracking);
  map->StartInobjectSlackTracking();
  return true;
}
diff --git a/src/maglev/maglev-graph-builder.cc b/src/maglev/maglev-graph-builder.cc
index 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.h
index 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/src/maglev/maglev-graph-builder.cc b/src/maglev/maglev-graph-builder.cc
index 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.h
index 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);
class A {}
class B extends A {}
var b = new B();
class A {}
class B extends A {}
var b = new B();
CreateRestParameter
Star2
Mov <closure>, r1
FindNonDefaultConstructorOrConstruct r1, r0, r7-r8
Mov r2, r5
Ldar r7
Mov r1, r3
Mov r0, r6
Mov r8, r4
JumpIfTrue [12] (0x352f0019b2df @ 35) ----> |
ThrowIfNotSuperConstructor r4               |  t
Ldar r6                                     |  r
ConstructWithSpread r4, r5-r5, [0]          |  u
Star4                                       |  e
Ldar r4                             <--------
Return
CreateRestParameter
Star2
Mov <closure>, r1
FindNonDefaultConstructorOrConstruct r1, r0, r7-r8
Mov r2, r5
Ldar r7
Mov r1, r3
Mov r0, r6
Mov r8, r4
JumpIfTrue [12] (0x352f0019b2df @ 35) ----> |
ThrowIfNotSuperConstructor r4               |  t
Ldar r6                                     |  r
ConstructWithSpread r4, r5-r5, [0]          |  u
Star4                                       |  e
Ldar r4                             <--------
Return
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()),
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()),
void MaglevGraphBuilder::VisitFindNonDefaultConstructorOrConstruct() {
  ValueNode* this_function = LoadRegisterTagged(0); // target
  ValueNode* new_target = LoadRegisterTagged(1);  // new_target
 
  auto register_pair = iterator_.GetRegisterPairOperand(2);
  // 如果 TryGetConstant(this_function) 返回不为空 【1】
  if (compiler::OptionalHeapObjectRef constant = TryGetConstant(this_function)) {
    // function_map
    compiler::MapRef function_map = constant->map(broker());
    // 原型链,后面将进行遍历
    compiler::HeapObjectRef current = function_map.prototype(broker());
 
    if (broker()->dependencies()->DependOnArrayIteratorProtector()) {
      while (true) {
        // 不是 JSFunction,则 break,即遍历到头了,原型链的尽头是 NULL
        if (!current.IsJSFunction()) break;
        // 当前遍历的原型构造函数
        compiler::JSFunctionRef current_function = current.AsJSFunction();
      ......
        // 获取构造函数类型
        //  kDefaultDerivedConstructor 表示派生默认构造函数
        //  kDefaultBaseConstructor    表示顶层默认构造函数
        FunctionKind kind = current_function.shared(broker()).kind();
        // 如果是 kDefaultDerivedConstructor,表示派生默认构造函数,则直接跳过
        if (kind != FunctionKind::kDefaultDerivedConstructor) {
          broker()->dependencies()->DependOnStablePrototypeChain(
              function_map, WhereToStart::kStartAtReceiver, current_function);
 
          compiler::OptionalHeapObjectRef new_target_function = TryGetConstant(new_target);
          // 如果是 kDefaultBaseConstructor,表示已经到顶层了,则会进入该分支
          if (kind == FunctionKind::kDefaultBaseConstructor) {
            ValueNode* object;
            // 如果 new_target_function 是一个 constant 并且是 JSFunction
            //      则调用 BuildAllocateFastObject 创建默认对象
            if (new_target_function && new_target_function->IsJSFunction()) {
              object = BuildAllocateFastObject(
                  FastObject(new_target_function->AsJSFunction(), zone(), broker()),
                  AllocationType::kYoung);
            } else {
            // 调用 TF_BUILTIN(FastNewObject, ConstructorBuiltinsAssembler) 创建默认对象
              object = BuildCallBuiltin<Builtin::kFastNewObject>(
                  {GetConstant(current_function), new_target});
            }
            StoreRegister(register_pair.first, GetBooleanConstant(true));
            StoreRegister(register_pair.second, object);
            return;
          }
          break;
        }
        // Keep walking up the class tree.
        current = current_function.map(broker()).prototype(broker());
      }
    }
    StoreRegister(register_pair.first, GetBooleanConstant(false));
    StoreRegister(register_pair.second, GetConstant(current));
    return;
  }
......
}
void MaglevGraphBuilder::VisitFindNonDefaultConstructorOrConstruct() {
  ValueNode* this_function = LoadRegisterTagged(0); // target
  ValueNode* new_target = LoadRegisterTagged(1);  // new_target
 
  auto register_pair = iterator_.GetRegisterPairOperand(2);
  // 如果 TryGetConstant(this_function) 返回不为空 【1】
  if (compiler::OptionalHeapObjectRef constant = TryGetConstant(this_function)) {
    // function_map
    compiler::MapRef function_map = constant->map(broker());
    // 原型链,后面将进行遍历
    compiler::HeapObjectRef current = function_map.prototype(broker());
 
    if (broker()->dependencies()->DependOnArrayIteratorProtector()) {
      while (true) {
        // 不是 JSFunction,则 break,即遍历到头了,原型链的尽头是 NULL
        if (!current.IsJSFunction()) break;
        // 当前遍历的原型构造函数
        compiler::JSFunctionRef current_function = current.AsJSFunction();
      ......
        // 获取构造函数类型
        //  kDefaultDerivedConstructor 表示派生默认构造函数
        //  kDefaultBaseConstructor    表示顶层默认构造函数
        FunctionKind kind = current_function.shared(broker()).kind();
        // 如果是 kDefaultDerivedConstructor,表示派生默认构造函数,则直接跳过
        if (kind != FunctionKind::kDefaultDerivedConstructor) {
          broker()->dependencies()->DependOnStablePrototypeChain(
              function_map, WhereToStart::kStartAtReceiver, current_function);
 
          compiler::OptionalHeapObjectRef new_target_function = TryGetConstant(new_target);
          // 如果是 kDefaultBaseConstructor,表示已经到顶层了,则会进入该分支
          if (kind == FunctionKind::kDefaultBaseConstructor) {
            ValueNode* object;
            // 如果 new_target_function 是一个 constant 并且是 JSFunction
            //      则调用 BuildAllocateFastObject 创建默认对象
            if (new_target_function && new_target_function->IsJSFunction()) {
              object = BuildAllocateFastObject(
                  FastObject(new_target_function->AsJSFunction(), zone(), broker()),
                  AllocationType::kYoung);
            } else {
            // 调用 TF_BUILTIN(FastNewObject, ConstructorBuiltinsAssembler) 创建默认对象
              object = BuildCallBuiltin<Builtin::kFastNewObject>(
                  {GetConstant(current_function), new_target});
            }
            StoreRegister(register_pair.first, GetBooleanConstant(true));
            StoreRegister(register_pair.second, object);

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2024-4-13 21:22 被XiaozaYa编辑 ,原因:
收藏
免费 3
支持
分享
最新回复 (7)
雪    币: 3594
活跃值: (31031)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2024-4-14 22:06
1
雪    币: 214
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
感谢师傅的分享,最近也在研究Maglev,但我似乎不太能理解其SSA的表现性(区别于llvm的ssa,我感觉llvm的ir似乎更好理解),还有其phi节点的untag过程有所疑惑,也没有找到比较好的资料。希望师傅可以给一点启发,不胜感激。
2024-4-19 17:56
0
雪    币: 6010
活跃值: (2740)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
4
远岚沐秋 感谢师傅的分享,最近也在研究Maglev,但我似乎不太能理解其SSA的表现性(区别于llvm的ssa,我感觉llvm的ir似乎更好理解),还有其phi节点的untag过程有所疑惑,也没有找到比较好的资 ...
跟llvm的ir是类似的,你看llvm的ir其实也就够了,至于你说的表现性,我没理解是什么意思。对于 phi 节点的 untag 过程,你并没有说你疑惑的点是什么,所有笔者也不知道你疑惑的点是什么。个人理解:笔者认为是因为 V8 使用了指针标记,而有的操作需要 tag 值,有的操作需要 untag 值,而 untag 值相对而言是比较危险的,因为 untag 后,其无法再区分指针和Smi,所以在生成 IR 图时,所有的 phi 节点都进行了 tag,后面进行 untag 其实就是一种优化,其根据输入输出去除 tag 与 untag 之间的转换,对于使用 untag 的操作,其直接使用 untag 值
2024-4-19 21:28
0
雪    币: 6010
活跃值: (2740)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
5
XiaozaYa 跟llvm的ir是类似的,你看llvm的ir其实也就够了,至于你说的表现性,我没理解是什么意思。对于 phi 节点的 untag 过程,你并没有说你疑惑的点是什么,所有笔者也不知道你疑惑的点是什么。个 ...
你可以对着 Maglev IR 看看
2024-4-19 21:32
0
雪    币: 214
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
XiaozaYa 你可以对着 Maglev IR 看看
感谢师傅的解答,其实phi的tag和untag过程比较疑惑,或者说我其实没有理解phi的tag和untag本质上是一个什么操作,师傅好像解答了,我再深入看看,谢谢
2024-4-20 19:00
0
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
请问一下exp中的minor_gc为什么最后还要添加new ArrayBuffer(8),前面应该足够出发minor_gc了
2024-4-23 19:22
0
雪    币: 6010
活跃值: (2740)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
8
岚沐 请问一下exp中的minor_gc为什么最后还要添加new ArrayBuffer(8),前面应该足够出发minor_gc了
都行,问题不大
2024-8-15 11:53
0
游客
登录 | 注册 方可回帖
返回
//