// Copyright 2016 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. #include "src/builtins/builtins-proxy-gen.h" #include "src/builtins/builtins-utils-gen.h" #include "src/builtins/builtins-utils.h" #include "src/builtins/builtins.h" #include "src/counters.h" #include "src/objects-inl.h" namespace v8 { namespace internal { Node* ProxiesCodeStubAssembler::AllocateProxy(Node* target, Node* handler, Node* context) { VARIABLE(map, MachineRepresentation::kTagged); Label callable_target(this), constructor_target(this), none_target(this), create_proxy(this); Node* nativeContext = LoadNativeContext(context); Branch(IsCallable(target), &callable_target, &none_target); BIND(&callable_target); { // Every object that is a constructor is implicitly callable // so it's okay to nest this check here GotoIf(IsConstructor(target), &constructor_target); map.Bind( LoadContextElement(nativeContext, Context::PROXY_CALLABLE_MAP_INDEX)); Goto(&create_proxy); } BIND(&constructor_target); { map.Bind(LoadContextElement(nativeContext, Context::PROXY_CONSTRUCTOR_MAP_INDEX)); Goto(&create_proxy); } BIND(&none_target); { map.Bind(LoadContextElement(nativeContext, Context::PROXY_MAP_INDEX)); Goto(&create_proxy); } BIND(&create_proxy); Node* proxy = Allocate(JSProxy::kSize); StoreMapNoWriteBarrier(proxy, map.value()); StoreObjectFieldRoot(proxy, JSProxy::kPropertiesOrHashOffset, RootIndex::kEmptyPropertyDictionary); StoreObjectFieldNoWriteBarrier(proxy, JSProxy::kTargetOffset, target); StoreObjectFieldNoWriteBarrier(proxy, JSProxy::kHandlerOffset, handler); return proxy; } Node* ProxiesCodeStubAssembler::AllocateJSArrayForCodeStubArguments( Node* context, CodeStubArguments& args, Node* argc, ParameterMode mode) { Comment("AllocateJSArrayForCodeStubArguments"); Label if_empty_array(this), allocate_js_array(this); // Do not use AllocateJSArray since {elements} might end up in LOS. VARIABLE(elements, MachineRepresentation::kTagged); TNode length = ParameterToTagged(argc, mode); GotoIf(SmiEqual(length, SmiConstant(0)), &if_empty_array); { Label if_large_object(this, Label::kDeferred); Node* allocated_elements = AllocateFixedArray(PACKED_ELEMENTS, argc, mode, kAllowLargeObjectAllocation); elements.Bind(allocated_elements); TVARIABLE(IntPtrT, offset, IntPtrConstant(FixedArrayBase::kHeaderSize - kHeapObjectTag)); VariableList list({&offset}, zone()); GotoIf(SmiGreaterThan(length, SmiConstant(FixedArray::kMaxRegularLength)), &if_large_object); args.ForEach(list, [=, &offset](Node* arg) { StoreNoWriteBarrier(MachineRepresentation::kTagged, allocated_elements, offset.value(), arg); Increment(&offset, kTaggedSize); }); Goto(&allocate_js_array); BIND(&if_large_object); { args.ForEach(list, [=, &offset](Node* arg) { Store(allocated_elements, offset.value(), arg); Increment(&offset, kTaggedSize); }); Goto(&allocate_js_array); } } BIND(&if_empty_array); { elements.Bind(EmptyFixedArrayConstant()); Goto(&allocate_js_array); } BIND(&allocate_js_array); // Allocate the result JSArray. Node* native_context = LoadNativeContext(context); TNode array_map = LoadJSArrayElementsMap(PACKED_ELEMENTS, native_context); TNode array = AllocateJSArray(array_map, CAST(elements.value()), length); return array; } Node* ProxiesCodeStubAssembler::CreateProxyRevokeFunctionContext( Node* proxy, Node* native_context) { Node* const context = Allocate(FixedArray::SizeFor(kProxyContextLength)); StoreMapNoWriteBarrier(context, RootIndex::kFunctionContextMap); InitializeFunctionContext(native_context, context, kProxyContextLength); StoreContextElementNoWriteBarrier(context, kProxySlot, proxy); return context; } Node* ProxiesCodeStubAssembler::AllocateProxyRevokeFunction(Node* proxy, Node* context) { Node* const native_context = LoadNativeContext(context); Node* const proxy_context = CreateProxyRevokeFunctionContext(proxy, native_context); Node* const revoke_map = LoadContextElement( native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX); Node* const revoke_info = LoadContextElement(native_context, Context::PROXY_REVOKE_SHARED_FUN); return AllocateFunctionWithMapAndContext(revoke_map, revoke_info, proxy_context); } Node* ProxiesCodeStubAssembler::GetProxyConstructorJSNewTarget() { return CodeAssembler::Parameter(static_cast( Builtin_ProxyConstructor_InterfaceDescriptor::kJSNewTarget)); } TF_BUILTIN(CallProxy, ProxiesCodeStubAssembler) { Node* argc = Parameter(Descriptor::kActualArgumentsCount); Node* argc_ptr = ChangeInt32ToIntPtr(argc); Node* proxy = Parameter(Descriptor::kFunction); Node* context = Parameter(Descriptor::kContext); CSA_ASSERT(this, IsJSProxy(proxy)); CSA_ASSERT(this, IsCallable(proxy)); PerformStackCheck(CAST(context)); Label throw_proxy_handler_revoked(this, Label::kDeferred), trap_undefined(this); // 1. Let handler be the value of the [[ProxyHandler]] internal slot of O. Node* handler = LoadObjectField(proxy, JSProxy::kHandlerOffset); // 2. If handler is null, throw a TypeError exception. CSA_ASSERT(this, IsNullOrJSReceiver(handler)); GotoIfNot(IsJSReceiver(handler), &throw_proxy_handler_revoked); // 3. Assert: Type(handler) is Object. CSA_ASSERT(this, IsJSReceiver(handler)); // 4. Let target be the value of the [[ProxyTarget]] internal slot of O. Node* target = LoadObjectField(proxy, JSProxy::kTargetOffset); // 5. Let trap be ? GetMethod(handler, "apply"). // 6. If trap is undefined, then Handle trap_name = factory()->apply_string(); Node* trap = GetMethod(context, handler, trap_name, &trap_undefined); CodeStubArguments args(this, argc_ptr); Node* receiver = args.GetReceiver(); // 7. Let argArray be CreateArrayFromList(argumentsList). Node* array = AllocateJSArrayForCodeStubArguments(context, args, argc_ptr, INTPTR_PARAMETERS); // 8. Return Call(trap, handler, «target, thisArgument, argArray»). Node* result = CallJS(CodeFactory::Call(isolate()), context, trap, handler, target, receiver, array); args.PopAndReturn(result); BIND(&trap_undefined); { // 6.a. Return Call(target, thisArgument, argumentsList). TailCallStub(CodeFactory::Call(isolate()), context, target, argc); } BIND(&throw_proxy_handler_revoked); { ThrowTypeError(context, MessageTemplate::kProxyRevoked, "apply"); } } TF_BUILTIN(ConstructProxy, ProxiesCodeStubAssembler) { Node* argc = Parameter(Descriptor::kActualArgumentsCount); Node* argc_ptr = ChangeInt32ToIntPtr(argc); Node* proxy = Parameter(Descriptor::kTarget); Node* new_target = Parameter(Descriptor::kNewTarget); Node* context = Parameter(Descriptor::kContext); CSA_ASSERT(this, IsJSProxy(proxy)); CSA_ASSERT(this, IsCallable(proxy)); Label throw_proxy_handler_revoked(this, Label::kDeferred), trap_undefined(this), not_an_object(this, Label::kDeferred); // 1. Let handler be the value of the [[ProxyHandler]] internal slot of O. Node* handler = LoadObjectField(proxy, JSProxy::kHandlerOffset); // 2. If handler is null, throw a TypeError exception. CSA_ASSERT(this, IsNullOrJSReceiver(handler)); GotoIfNot(IsJSReceiver(handler), &throw_proxy_handler_revoked); // 3. Assert: Type(handler) is Object. CSA_ASSERT(this, IsJSReceiver(handler)); // 4. Let target be the value of the [[ProxyTarget]] internal slot of O. Node* target = LoadObjectField(proxy, JSProxy::kTargetOffset); // 5. Let trap be ? GetMethod(handler, "construct"). // 6. If trap is undefined, then Handle trap_name = factory()->construct_string(); Node* trap = GetMethod(context, handler, trap_name, &trap_undefined); CodeStubArguments args(this, argc_ptr); // 7. Let argArray be CreateArrayFromList(argumentsList). Node* array = AllocateJSArrayForCodeStubArguments(context, args, argc_ptr, INTPTR_PARAMETERS); // 8. Let newObj be ? Call(trap, handler, « target, argArray, newTarget »). Node* new_obj = CallJS(CodeFactory::Call(isolate()), context, trap, handler, target, array, new_target); // 9. If Type(newObj) is not Object, throw a TypeError exception. GotoIf(TaggedIsSmi(new_obj), ¬_an_object); GotoIfNot(IsJSReceiver(new_obj), ¬_an_object); // 10. Return newObj. args.PopAndReturn(new_obj); BIND(¬_an_object); { ThrowTypeError(context, MessageTemplate::kProxyConstructNonObject, new_obj); } BIND(&trap_undefined); { // 6.a. Assert: target has a [[Construct]] internal method. CSA_ASSERT(this, IsConstructor(target)); // 6.b. Return ? Construct(target, argumentsList, newTarget). TailCallStub(CodeFactory::Construct(isolate()), context, target, new_target, argc); } BIND(&throw_proxy_handler_revoked); { ThrowTypeError(context, MessageTemplate::kProxyRevoked, "construct"); } } TF_BUILTIN(ProxyHasProperty, ProxiesCodeStubAssembler) { Node* context = Parameter(Descriptor::kContext); Node* proxy = Parameter(Descriptor::kProxy); Node* name = Parameter(Descriptor::kName); CSA_ASSERT(this, IsJSProxy(proxy)); PerformStackCheck(CAST(context)); // 1. Assert: IsPropertyKey(P) is true. CSA_ASSERT(this, IsName(name)); CSA_ASSERT(this, Word32Equal(IsPrivateSymbol(name), Int32Constant(0))); Label throw_proxy_handler_revoked(this, Label::kDeferred), trap_undefined(this), if_try_get_own_property_bailout(this, Label::kDeferred), trap_not_callable(this, Label::kDeferred), return_true(this), return_false(this), check_target_desc(this); // 2. Let handler be O.[[ProxyHandler]]. Node* handler = LoadObjectField(proxy, JSProxy::kHandlerOffset); // 3. If handler is null, throw a TypeError exception. // 4. Assert: Type(handler) is Object. GotoIfNot(IsJSReceiver(handler), &throw_proxy_handler_revoked); // 5. Let target be O.[[ProxyTarget]]. Node* target = LoadObjectField(proxy, JSProxy::kTargetOffset); // 6. Let trap be ? GetMethod(handler, "has"). // 7. If trap is undefined, then (see 7.a below). Handle trap_name = factory()->has_string(); Node* trap = GetMethod(context, handler, trap_name, &trap_undefined); GotoIf(TaggedIsSmi(trap), &trap_not_callable); GotoIfNot(IsCallable(trap), &trap_not_callable); // 8. Let booleanTrapResult be ToBoolean(? Call(trap, handler, « target, P // »)). BranchIfToBooleanIsTrue(CallJS(CodeFactory::Call(isolate()), context, trap, handler, target, name), &return_true, &check_target_desc); BIND(&check_target_desc); { // 9. If booleanTrapResult is false, then (see 9.a. in CheckHasTrapResult). CheckHasTrapResult(context, target, proxy, name, &return_false, &if_try_get_own_property_bailout); } BIND(&if_try_get_own_property_bailout); { CallRuntime(Runtime::kCheckProxyHasTrap, context, name, target); Return(FalseConstant()); } BIND(&trap_undefined); { // 7.a. Return ? target.[[HasProperty]](P). TailCallBuiltin(Builtins::kHasProperty, context, target, name); } BIND(&return_false); Return(FalseConstant()); BIND(&return_true); Return(TrueConstant()); BIND(&throw_proxy_handler_revoked); ThrowTypeError(context, MessageTemplate::kProxyRevoked, "has"); BIND(&trap_not_callable); ThrowTypeError(context, MessageTemplate::kPropertyNotFunction, trap, StringConstant("has"), proxy); } TF_BUILTIN(ProxyGetProperty, ProxiesCodeStubAssembler) { Node* context = Parameter(Descriptor::kContext); Node* proxy = Parameter(Descriptor::kProxy); Node* name = Parameter(Descriptor::kName); Node* receiver = Parameter(Descriptor::kReceiverValue); Node* on_non_existent = Parameter(Descriptor::kOnNonExistent); CSA_ASSERT(this, IsJSProxy(proxy)); // 1. Assert: IsPropertyKey(P) is true. CSA_ASSERT(this, TaggedIsNotSmi(name)); CSA_ASSERT(this, IsName(name)); CSA_ASSERT(this, Word32Equal(IsPrivateSymbol(name), Int32Constant(0))); Label throw_proxy_handler_revoked(this, Label::kDeferred), trap_undefined(this); // 2. Let handler be O.[[ProxyHandler]]. Node* handler = LoadObjectField(proxy, JSProxy::kHandlerOffset); // 3. If handler is null, throw a TypeError exception. GotoIf(IsNull(handler), &throw_proxy_handler_revoked); // 4. Assert: Type(handler) is Object. CSA_ASSERT(this, IsJSReceiver(handler)); // 5. Let target be O.[[ProxyTarget]]. Node* target = LoadObjectField(proxy, JSProxy::kTargetOffset); // 6. Let trap be ? GetMethod(handler, "get"). // 7. If trap is undefined, then (see 7.a below). Handle trap_name = factory()->get_string(); Node* trap = GetMethod(context, handler, trap_name, &trap_undefined); // 8. Let trapResult be ? Call(trap, handler, « target, P, Receiver »). Node* trap_result = CallJS( CodeFactory::Call(isolate(), ConvertReceiverMode::kNotNullOrUndefined), context, trap, handler, target, name, receiver); // 9. Let targetDesc be ? target.[[GetOwnProperty]](P). Label return_result(this); CheckGetSetTrapResult(context, target, proxy, name, trap_result, &return_result, JSProxy::kGet); BIND(&return_result); { // 11. Return trapResult. Return(trap_result); } BIND(&trap_undefined); { // 7.a. Return ? target.[[Get]](P, Receiver). // TODO(mslekova): Introduce GetPropertyWithReceiver stub Return(CallRuntime(Runtime::kGetPropertyWithReceiver, context, target, name, receiver, on_non_existent)); } BIND(&throw_proxy_handler_revoked); ThrowTypeError(context, MessageTemplate::kProxyRevoked, "get"); } TF_BUILTIN(ProxySetProperty, ProxiesCodeStubAssembler) { Node* context = Parameter(Descriptor::kContext); Node* proxy = Parameter(Descriptor::kProxy); Node* name = Parameter(Descriptor::kName); Node* value = Parameter(Descriptor::kValue); Node* receiver = Parameter(Descriptor::kReceiverValue); CSA_ASSERT(this, IsJSProxy(proxy)); // 1. Assert: IsPropertyKey(P) is true. CSA_ASSERT(this, TaggedIsNotSmi(name)); CSA_ASSERT(this, IsName(name)); Label throw_proxy_handler_revoked(this, Label::kDeferred), trap_undefined(this), failure(this, Label::kDeferred), continue_checks(this), success(this), private_symbol(this, Label::kDeferred); GotoIf(IsPrivateSymbol(name), &private_symbol); // 2. Let handler be O.[[ProxyHandler]]. Node* handler = LoadObjectField(proxy, JSProxy::kHandlerOffset); // 3. If handler is null, throw a TypeError exception. GotoIfNot(IsJSReceiver(handler), &throw_proxy_handler_revoked); // 4. Assert: Type(handler) is Object. CSA_ASSERT(this, IsJSReceiver(handler)); // 5. Let target be O.[[ProxyTarget]]. Node* target = LoadObjectField(proxy, JSProxy::kTargetOffset); // 6. Let trap be ? GetMethod(handler, "set"). // 7. If trap is undefined, then (see 7.a below). Handle set_string = factory()->set_string(); Node* trap = GetMethod(context, handler, set_string, &trap_undefined); // 8. Let booleanTrapResult be ToBoolean(? Call(trap, handler, // « target, P, V, Receiver »)). // 9. If booleanTrapResult is false, return false. BranchIfToBooleanIsTrue( CallJS(CodeFactory::Call(isolate(), ConvertReceiverMode::kNotNullOrUndefined), context, trap, handler, target, name, value, receiver), &continue_checks, &failure); BIND(&continue_checks); { // 9. Let targetDesc be ? target.[[GetOwnProperty]](P). Label return_result(this); CheckGetSetTrapResult(context, target, proxy, name, value, &success, JSProxy::kSet); } BIND(&failure); { CallRuntime(Runtime::kThrowTypeErrorIfStrict, context, SmiConstant(MessageTemplate::kProxyTrapReturnedFalsishFor), HeapConstant(set_string), name); Goto(&success); } // 12. Return true. BIND(&success); Return(value); BIND(&private_symbol); { Label failure(this); CallRuntime(Runtime::kThrowTypeErrorIfStrict, context, SmiConstant(MessageTemplate::kProxyPrivate)); Return(UndefinedConstant()); } BIND(&trap_undefined); { // 7.a. Return ? target.[[Set]](P, V, Receiver). CallRuntime(Runtime::kSetPropertyWithReceiver, context, target, name, value, receiver); Return(value); } BIND(&throw_proxy_handler_revoked); ThrowTypeError(context, MessageTemplate::kProxyRevoked, "set"); } void ProxiesCodeStubAssembler::CheckGetSetTrapResult( Node* context, Node* target, Node* proxy, Node* name, Node* trap_result, Label* check_passed, JSProxy::AccessKind access_kind) { Node* map = LoadMap(target); VARIABLE(var_value, MachineRepresentation::kTagged); VARIABLE(var_details, MachineRepresentation::kWord32); VARIABLE(var_raw_value, MachineRepresentation::kTagged); Label if_found_value(this), check_in_runtime(this, Label::kDeferred); GotoIfNot(IsUniqueNameNoIndex(CAST(name)), &check_in_runtime); Node* instance_type = LoadInstanceType(target); TryGetOwnProperty(context, target, target, map, instance_type, name, &if_found_value, &var_value, &var_details, &var_raw_value, check_passed, &check_in_runtime, kReturnAccessorPair); BIND(&if_found_value); { Label throw_non_configurable_data(this, Label::kDeferred), throw_non_configurable_accessor(this, Label::kDeferred), check_accessor(this), check_data(this); // If targetDesc is not undefined and targetDesc.[[Configurable]] is // false, then: GotoIfNot(IsSetWord32(var_details.value(), PropertyDetails::kAttributesDontDeleteMask), check_passed); // If IsDataDescriptor(targetDesc) is true and // targetDesc.[[Writable]] is false, then: BranchIfAccessorPair(var_raw_value.value(), &check_accessor, &check_data); BIND(&check_data); { Node* read_only = IsSetWord32(var_details.value(), PropertyDetails::kAttributesReadOnlyMask); GotoIfNot(read_only, check_passed); // If SameValue(trapResult, targetDesc.[[Value]]) is false, // throw a TypeError exception. BranchIfSameValue(trap_result, var_value.value(), check_passed, &throw_non_configurable_data); } BIND(&check_accessor); { Node* accessor_pair = var_raw_value.value(); if (access_kind == JSProxy::kGet) { Label continue_check(this, Label::kDeferred); // 10.b. If IsAccessorDescriptor(targetDesc) is true and // targetDesc.[[Get]] is undefined, then: Node* getter = LoadObjectField(accessor_pair, AccessorPair::kGetterOffset); // Here we check for null as well because if the getter was never // defined it's set as null. GotoIf(IsUndefined(getter), &continue_check); GotoIf(IsNull(getter), &continue_check); Goto(check_passed); // 10.b.i. If trapResult is not undefined, throw a TypeError exception. BIND(&continue_check); GotoIfNot(IsUndefined(trap_result), &throw_non_configurable_accessor); } else { // 11.b.i. If targetDesc.[[Set]] is undefined, throw a TypeError // exception. Node* setter = LoadObjectField(accessor_pair, AccessorPair::kSetterOffset); GotoIf(IsUndefined(setter), &throw_non_configurable_accessor); GotoIf(IsNull(setter), &throw_non_configurable_accessor); } Goto(check_passed); } BIND(&check_in_runtime); { CallRuntime(Runtime::kCheckProxyGetSetTrapResult, context, name, target, trap_result, SmiConstant(access_kind)); Return(trap_result); } BIND(&throw_non_configurable_data); { if (access_kind == JSProxy::kGet) { ThrowTypeError(context, MessageTemplate::kProxyGetNonConfigurableData, name, var_value.value(), trap_result); } else { ThrowTypeError(context, MessageTemplate::kProxySetFrozenData, name); } } BIND(&throw_non_configurable_accessor); { if (access_kind == JSProxy::kGet) { ThrowTypeError(context, MessageTemplate::kProxyGetNonConfigurableAccessor, name, trap_result); } else { ThrowTypeError(context, MessageTemplate::kProxySetFrozenAccessor, name); } } } } void ProxiesCodeStubAssembler::CheckHasTrapResult(Node* context, Node* target, Node* proxy, Node* name, Label* check_passed, Label* if_bailout) { Node* target_map = LoadMap(target); VARIABLE(var_value, MachineRepresentation::kTagged); VARIABLE(var_details, MachineRepresentation::kWord32); VARIABLE(var_raw_value, MachineRepresentation::kTagged); Label if_found_value(this, Label::kDeferred), throw_non_configurable(this, Label::kDeferred), throw_non_extensible(this, Label::kDeferred); // 9.a. Let targetDesc be ? target.[[GetOwnProperty]](P). GotoIfNot(IsUniqueNameNoIndex(CAST(name)), if_bailout); Node* instance_type = LoadInstanceType(target); TryGetOwnProperty(context, target, target, target_map, instance_type, name, &if_found_value, &var_value, &var_details, &var_raw_value, check_passed, if_bailout, kReturnAccessorPair); // 9.b. If targetDesc is not undefined, then (see 9.b.i. below). BIND(&if_found_value); { // 9.b.i. If targetDesc.[[Configurable]] is false, throw a TypeError // exception. Node* non_configurable = IsSetWord32( var_details.value(), PropertyDetails::kAttributesDontDeleteMask); GotoIf(non_configurable, &throw_non_configurable); // 9.b.ii. Let extensibleTarget be ? IsExtensible(target). Node* target_extensible = IsExtensibleMap(target_map); // 9.b.iii. If extensibleTarget is false, throw a TypeError exception. GotoIfNot(target_extensible, &throw_non_extensible); Goto(check_passed); } BIND(&throw_non_configurable); { ThrowTypeError(context, MessageTemplate::kProxyHasNonConfigurable, name); } BIND(&throw_non_extensible); { ThrowTypeError(context, MessageTemplate::kProxyHasNonExtensible, name); } } } // namespace internal } // namespace v8