Commit 970a7ad7 authored by verwaest's avatar verwaest Committed by Commit bot

Fix Reflect.construct wrt proxy, generator, and non-subclass new.target

This makes sure that proxy + Function/Array works
Makes sure that new.target can be a generator
Makes sure that if new.target is not a subclass, but does not have a prototype, that we'll get that same prototype back the next time we look at new.target.prototype.

BUG=v8:1543, v8:3330, v8:3931
LOG=n

Review URL: https://codereview.chromium.org/1484473002

Cr-Commit-Position: refs/heads/master@{#32382}
parent 47502a23
...@@ -12206,6 +12206,10 @@ void JSFunction::EnsureHasInitialMap(Handle<JSFunction> function) { ...@@ -12206,6 +12206,10 @@ void JSFunction::EnsureHasInitialMap(Handle<JSFunction> function) {
if (function->has_initial_map()) return; if (function->has_initial_map()) return;
Isolate* isolate = function->GetIsolate(); Isolate* isolate = function->GetIsolate();
// The constructor should be compiled for the optimization hints to be
// available.
Compiler::Compile(function, CLEAR_EXCEPTION);
// First create a new map with the size and number of in-object properties // First create a new map with the size and number of in-object properties
// suggested by the function. // suggested by the function.
InstanceType instance_type; InstanceType instance_type;
...@@ -12245,39 +12249,59 @@ void JSFunction::EnsureHasInitialMap(Handle<JSFunction> function) { ...@@ -12245,39 +12249,59 @@ void JSFunction::EnsureHasInitialMap(Handle<JSFunction> function) {
} }
Handle<Map> JSFunction::EnsureDerivedHasInitialMap( // static
Handle<JSFunction> new_target, Handle<JSFunction> constructor) { MaybeHandle<Map> JSFunction::GetDerivedMap(Isolate* isolate,
DCHECK(constructor->has_initial_map()); Handle<JSFunction> constructor,
Isolate* isolate = constructor->GetIsolate(); Handle<JSReceiver> new_target) {
EnsureHasInitialMap(constructor);
Handle<Map> constructor_initial_map(constructor->initial_map(), isolate); Handle<Map> constructor_initial_map(constructor->initial_map(), isolate);
if (*new_target == *constructor) return constructor_initial_map; if (*new_target == *constructor) return constructor_initial_map;
if (new_target->has_initial_map()) {
// Check that |new_target|'s initial map still in sync with if (new_target->IsJSProxy()) {
// the |constructor|, otherwise we must create a new initial map for Handle<JSProxy> new_target_proxy = Handle<JSProxy>::cast(new_target);
// |new_target|. Handle<Object> prototype;
if (new_target->initial_map()->GetConstructor() == *constructor) { Handle<String> prototype_string = isolate->factory()->prototype_string();
return handle(new_target->initial_map(), isolate); ASSIGN_RETURN_ON_EXCEPTION(
isolate, prototype,
JSReceiver::GetProperty(new_target_proxy, prototype_string), Map);
Handle<Map> map = Map::CopyInitialMap(constructor_initial_map);
if (!prototype->IsJSReceiver()) {
Handle<Context> context;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, context, JSProxy::GetFunctionRealm(new_target_proxy), Map);
DCHECK(context->IsNativeContext());
// TODO(verwaest): Use the intrinsicDefaultProto instead.
prototype = handle(context->initial_object_prototype(), isolate);
}
if (map->prototype() != *prototype) {
Map::SetPrototype(map, prototype, FAST_PROTOTYPE);
} }
map->SetConstructor(*constructor);
return map;
} }
// First create a new map with the size and number of in-object properties Handle<JSFunction> new_target_function = Handle<JSFunction>::cast(new_target);
// suggested by the function.
DCHECK(!new_target->shared()->is_generator());
DCHECK(!constructor->shared()->is_generator());
// Fetch or allocate prototype. // Check that |new_target_function|'s initial map still in sync with
// TODO(verwaest): In case of non-instance prototype, use the // the |constructor|, otherwise we must create a new initial map for
// intrinsicDefaultProto instead. // |new_target_function|.
Handle<Object> prototype; if (new_target_function->has_initial_map() &&
if (new_target->has_instance_prototype()) { new_target_function->initial_map()->GetConstructor() == *constructor) {
prototype = handle(new_target->instance_prototype(), isolate); return handle(new_target_function->initial_map(), isolate);
} else {
prototype = isolate->factory()->NewFunctionPrototype(new_target);
} }
// Finally link initial map and constructor function if the original // Create a new map with the size and number of in-object properties suggested
// constructor is actually a subclass constructor. // by the function.
if (IsSubclassConstructor(new_target->shared()->kind())) {
// Link initial map and constructor function if the original constructor is
// actually a subclass constructor.
if (IsSubclassConstructor(new_target_function->shared()->kind())) {
Handle<Object> prototype(new_target_function->instance_prototype(),
isolate);
InstanceType instance_type = constructor_initial_map->instance_type(); InstanceType instance_type = constructor_initial_map->instance_type();
DCHECK(CanSubclassHaveInobjectProperties(instance_type)); DCHECK(CanSubclassHaveInobjectProperties(instance_type));
int internal_fields = int internal_fields =
...@@ -12286,7 +12310,7 @@ Handle<Map> JSFunction::EnsureDerivedHasInitialMap( ...@@ -12286,7 +12310,7 @@ Handle<Map> JSFunction::EnsureDerivedHasInitialMap(
constructor_initial_map->unused_property_fields(); constructor_initial_map->unused_property_fields();
int instance_size; int instance_size;
int in_object_properties; int in_object_properties;
new_target->CalculateInstanceSizeForDerivedClass( new_target_function->CalculateInstanceSizeForDerivedClass(
instance_type, internal_fields, &instance_size, &in_object_properties); instance_type, internal_fields, &instance_size, &in_object_properties);
int unused_property_fields = in_object_properties - pre_allocated; int unused_property_fields = in_object_properties - pre_allocated;
...@@ -12294,20 +12318,33 @@ Handle<Map> JSFunction::EnsureDerivedHasInitialMap( ...@@ -12294,20 +12318,33 @@ Handle<Map> JSFunction::EnsureDerivedHasInitialMap(
Map::CopyInitialMap(constructor_initial_map, instance_size, Map::CopyInitialMap(constructor_initial_map, instance_size,
in_object_properties, unused_property_fields); in_object_properties, unused_property_fields);
JSFunction::SetInitialMap(new_target, map, prototype); JSFunction::SetInitialMap(new_target_function, map, prototype);
map->SetConstructor(*constructor); map->SetConstructor(*constructor);
new_target->StartInobjectSlackTracking(); new_target_function->StartInobjectSlackTracking();
return map; return map;
}
// Fetch the prototype.
Handle<Object> prototype;
if (new_target_function->map()->has_non_instance_prototype()) {
// TODO(verwaest): In case of non-instance prototype, use the
// intrinsicDefaultProto instead.
prototype = handle(new_target_function->context()
->native_context()
->initial_object_prototype());
} else { } else {
Handle<Map> map = Map::CopyInitialMap(constructor_initial_map); // Make sure the prototype is cached on new_target_function.
DCHECK(prototype->IsJSReceiver()); EnsureHasInitialMap(new_target_function);
if (map->prototype() != *prototype) { prototype = handle(new_target_function->instance_prototype(), isolate);
Map::SetPrototype(map, prototype, FAST_PROTOTYPE);
}
map->SetConstructor(*constructor);
return map;
} }
Handle<Map> map = Map::CopyInitialMap(constructor_initial_map);
DCHECK(prototype->IsJSReceiver());
if (map->prototype() != *prototype) {
Map::SetPrototype(map, prototype, FAST_PROTOTYPE);
}
map->SetConstructor(*constructor);
return map;
} }
......
...@@ -7298,11 +7298,13 @@ class JSFunction: public JSObject { ...@@ -7298,11 +7298,13 @@ class JSFunction: public JSObject {
Handle<Object> prototype); Handle<Object> prototype);
inline bool has_initial_map(); inline bool has_initial_map();
static void EnsureHasInitialMap(Handle<JSFunction> function); static void EnsureHasInitialMap(Handle<JSFunction> function);
// Ensures that the |new_target| has correct initial map and
// returns it. If the |new_target| is not a subclass constructor // Creates a map that matches the constructor's initial map, but with
// its initial map is left unmodified. // [[prototype]] being new.target.prototype. Because new.target can be a
static Handle<Map> EnsureDerivedHasInitialMap(Handle<JSFunction> new_target, // JSProxy, this can call back into JavaScript.
Handle<JSFunction> constructor); static MUST_USE_RESULT MaybeHandle<Map> GetDerivedMap(
Isolate* isolate, Handle<JSFunction> constructor,
Handle<JSReceiver> new_target);
// Get and set the prototype property on a JSFunction. If the // Get and set the prototype property on a JSFunction. If the
// function has an initial map the prototype is set on the initial // function has an initial map the prototype is set on the initial
......
...@@ -272,16 +272,10 @@ Object* ArrayConstructorCommon(Isolate* isolate, Handle<JSFunction> constructor, ...@@ -272,16 +272,10 @@ Object* ArrayConstructorCommon(Isolate* isolate, Handle<JSFunction> constructor,
} }
} }
// TODO(verwaest): new_target could be a proxy. Read new.target.prototype in Handle<Map> initial_map;
// that case. ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
Handle<JSFunction> original_function = Handle<JSFunction>::cast(new_target); isolate, initial_map,
JSFunction::GetDerivedMap(isolate, constructor, new_target));
JSFunction::EnsureHasInitialMap(constructor);
// TODO(verwaest): original_function could have non-instance-prototype
// (non-JSReceiver), requiring fallback to the intrinsicDefaultProto.
Handle<Map> initial_map =
JSFunction::EnsureDerivedHasInitialMap(original_function, constructor);
ElementsKind to_kind = can_use_type_feedback ? site->GetElementsKind() ElementsKind to_kind = can_use_type_feedback ? site->GetElementsKind()
: initial_map->elements_kind(); : initial_map->elements_kind();
......
...@@ -55,25 +55,26 @@ RUNTIME_FUNCTION(Runtime_CompleteFunctionConstruction) { ...@@ -55,25 +55,26 @@ RUNTIME_FUNCTION(Runtime_CompleteFunctionConstruction) {
CONVERT_ARG_HANDLE_CHECKED(Object, unchecked_new_target, 2); CONVERT_ARG_HANDLE_CHECKED(Object, unchecked_new_target, 2);
func->shared()->set_name_should_print_as_anonymous(true); func->shared()->set_name_should_print_as_anonymous(true);
if (unchecked_new_target->IsUndefined()) return *func;
Handle<JSReceiver> new_target =
Handle<JSReceiver>::cast(unchecked_new_target);
// If new.target is equal to |constructor| then the function |func| created // If new.target is equal to |constructor| then the function |func| created
// is already correctly setup and nothing else should be done here. // is already correctly setup and nothing else should be done here.
// But if new.target is not equal to |constructor| then we are have a // But if new.target is not equal to |constructor| then we are have a
// Function builtin subclassing case and therefore the function |func| // Function builtin subclassing case and therefore the function |func|
// has wrong initial map. To fix that we create a new function object with // has wrong initial map. To fix that we create a new function object with
// correct initial map. // correct initial map.
if (unchecked_new_target->IsUndefined() || if (*constructor == *new_target) return *func;
*constructor == *unchecked_new_target) {
return *func;
}
// Create a new JSFunction object with correct initial map. // Create a new JSFunction object with correct initial map.
HandleScope handle_scope(isolate); HandleScope handle_scope(isolate);
Handle<JSFunction> new_target =
Handle<JSFunction>::cast(unchecked_new_target);
DCHECK(constructor->has_initial_map()); DCHECK(constructor->has_initial_map());
Handle<Map> initial_map = Handle<Map> initial_map;
JSFunction::EnsureDerivedHasInitialMap(new_target, constructor); ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, initial_map,
JSFunction::GetDerivedMap(isolate, constructor, new_target));
Handle<SharedFunctionInfo> shared_info(func->shared(), isolate); Handle<SharedFunctionInfo> shared_info(func->shared(), isolate);
Handle<Context> context(func->context(), isolate); Handle<Context> context(func->context(), isolate);
......
...@@ -1007,55 +1007,17 @@ RUNTIME_FUNCTION(Runtime_AllocateHeapNumber) { ...@@ -1007,55 +1007,17 @@ RUNTIME_FUNCTION(Runtime_AllocateHeapNumber) {
} }
static MaybeHandle<Map> GetDerivedMap(Isolate* isolate,
Handle<JSFunction> constructor,
Handle<JSReceiver> new_target) {
JSFunction::EnsureHasInitialMap(constructor);
DCHECK_NE(JS_FUNCTION_TYPE, constructor->initial_map()->instance_type());
if (new_target->IsJSProxy()) {
Handle<JSProxy> new_target_proxy = Handle<JSProxy>::cast(new_target);
Handle<Object> prototype;
Handle<String> prototype_string = isolate->factory()->prototype_string();
ASSIGN_RETURN_ON_EXCEPTION(
isolate, prototype,
JSReceiver::GetProperty(new_target_proxy, prototype_string), Map);
Handle<Map> constructor_initial_map(constructor->initial_map());
Handle<Map> map = Map::CopyInitialMap(constructor_initial_map);
if (!prototype->IsJSReceiver()) {
Handle<Context> context;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, context, JSProxy::GetFunctionRealm(new_target_proxy), Map);
DCHECK(context->IsNativeContext());
// TODO(verwaest): Use the intrinsicDefaultProto instead.
prototype = handle(context->initial_object_prototype(), isolate);
}
if (map->prototype() != *prototype) {
Map::SetPrototype(map, prototype, FAST_PROTOTYPE);
}
map->SetConstructor(*constructor);
return map;
}
return JSFunction::EnsureDerivedHasInitialMap(
Handle<JSFunction>::cast(new_target), constructor);
}
static Object* Runtime_NewObjectHelper(Isolate* isolate, static Object* Runtime_NewObjectHelper(Isolate* isolate,
Handle<JSFunction> constructor, Handle<JSFunction> constructor,
Handle<JSReceiver> new_target, Handle<JSReceiver> new_target,
Handle<AllocationSite> site) { Handle<AllocationSite> site) {
// The constructor should be compiled for the optimization hints to be DCHECK(!constructor->has_initial_map() ||
// available. constructor->initial_map()->instance_type() != JS_FUNCTION_TYPE);
Compiler::Compile(constructor, CLEAR_EXCEPTION);
Handle<Map> initial_map; Handle<Map> initial_map;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION( ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, initial_map, GetDerivedMap(isolate, constructor, new_target)); isolate, initial_map,
JSFunction::GetDerivedMap(isolate, constructor, new_target));
Handle<JSObject> result = Handle<JSObject> result =
isolate->factory()->NewJSObjectFromMap(initial_map, NOT_TENURED, site); isolate->factory()->NewJSObjectFromMap(initial_map, NOT_TENURED, site);
......
...@@ -39,3 +39,35 @@ function CreateConstructableProxy(handler) { ...@@ -39,3 +39,35 @@ function CreateConstructableProxy(handler) {
assertTrue(Object.getPrototypeOf(o) === Object.prototype); assertTrue(Object.getPrototypeOf(o) === Object.prototype);
assertEquals(100, Number.prototype.valueOf.call(o)); assertEquals(100, Number.prototype.valueOf.call(o));
})(); })();
(function() {
var prototype = { x: 1 };
var log = [];
var proxy = CreateConstructableProxy({
get(k) {
log.push("get trap");
return prototype;
}});
var o = Reflect.construct(Function, ["return 1000"], proxy);
assertEquals(["get trap"], log);
assertTrue(Object.getPrototypeOf(o) === prototype);
assertEquals(1000, o());
})();
(function() {
var prototype = { x: 1 };
var log = [];
var proxy = CreateConstructableProxy({
get(k) {
log.push("get trap");
return prototype;
}});
var o = Reflect.construct(Array, [1, 2, 3], proxy);
assertEquals(["get trap"], log);
assertTrue(Object.getPrototypeOf(o) === prototype);
assertEquals([1, 2, 3], o);
})();
...@@ -275,3 +275,12 @@ ...@@ -275,3 +275,12 @@
assertEquals(10, Reflect.construct(sumSloppy, assertEquals(10, Reflect.construct(sumSloppy,
{ 0: 1, 1: 2, 2: 3, 3: 4, length: 4 }).a); { 0: 1, 1: 2, 2: 3, 3: 4, length: 4 }).a);
})(); })();
(function() {
function* f() { yield 1; yield 2; }
function* g() { yield 3; yield 4; }
var o = Reflect.construct(f, [], g);
assertEquals([1, 2], [...o]);
assertTrue(o.__proto__ === g.prototype);
assertTrue(o.__proto__ !== f.prototype);
})();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment