text: // 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/logging/counters.h"
  #include "src/objects/js-proxy.h"
  #include "src/objects/objects-inl.h"
  
  namespace v8 {
  namespace internal {
  
  TNode<JSProxy> ProxiesCodeStubAssembler::AllocateProxy(
      TNode<Context> context, TNode<JSReceiver> target,
      TNode<JSReceiver> handler) {
    VARIABLE(map, MachineRepresentation::kTagged);
  
    Label callable_target(this), constructor_target(this), none_target(this),
        create_proxy(this);
  
    TNode<NativeContext> 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);
    TNode<HeapObject> 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 CAST(proxy);
  }
  
  Node* ProxiesCodeStubAssembler::AllocateJSArrayForCodeStubArguments(
      Node* context, const 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<Smi> length = ParameterToTagged(argc, mode);
    GotoIf(SmiEqual(length, SmiConstant(0)), &if_empty_array);
    {
      Label if_large_object(this, Label::kDeferred);
      TNode<FixedArrayBase> 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, [&](TNode<Object> arg) {
        StoreNoWriteBarrier(MachineRepresentation::kTagged, allocated_elements,
                            offset.value(), arg);
        Increment(&offset, kTaggedSize);
      });
      Goto(&allocate_js_array);
  
      BIND(&if_large_object);
      {
        args.ForEach(list, [&](TNode<Object> 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.
    TNode<NativeContext> native_context = LoadNativeContext(context);
    TNode<Map> array_map =
        LoadJSArrayElementsMap(PACKED_ELEMENTS, native_context);
    TNode<JSArray> array =
        AllocateJSArray(array_map, CAST(elements.value()), length);
  
    return array;
  }
  
  Node* ProxiesCodeStubAssembler::CreateProxyRevokeFunctionContext(
      Node* proxy, Node* native_context) {
    TNode<HeapObject> const context =
        Allocate(FixedArray::SizeFor(kProxyContextLength));
    StoreMapNoWriteBarrier(context, RootIndex::kFunctionContextMap);
    InitializeFunctionContext(native_context, context, kProxyContextLength);
    StoreContextElementNoWriteBarrier(CAST(context), kProxySlot, proxy);
    return context;
  }
  
  TNode<JSFunction> ProxiesCodeStubAssembler::AllocateProxyRevokeFunction(
      TNode<Context> context, TNode<JSProxy> proxy) {
    TNode<NativeContext> const native_context = LoadNativeContext(context);
  
    const TNode<Context> proxy_context =
        CAST(CreateProxyRevokeFunctionContext(proxy, native_context));
    const TNode<Map> revoke_map = CAST(LoadContextElement(
        native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX));
    const TNode<SharedFunctionInfo> revoke_info = CAST(
        LoadContextElement(native_context, Context::PROXY_REVOKE_SHARED_FUN));
  
    return AllocateFunctionWithMapAndContext(revoke_map, revoke_info,
                                             proxy_context);
  }
  
  TF_BUILTIN(CallProxy, ProxiesCodeStubAssembler) {
    Node* argc = Parameter(Descriptor::kActualArgumentsCount);
    TNode<IntPtrT> argc_ptr = ChangeInt32ToIntPtr(argc);
    TNode<JSProxy> proxy = CAST(Parameter(Descriptor::kFunction));
    Node* context = Parameter(Descriptor::kContext);
  
    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.
    TNode<HeapObject> handler =
        CAST(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.
    TNode<Object> target = LoadObjectField(proxy, JSProxy::kTargetOffset);
  
    // 5. Let trap be ? GetMethod(handler, "apply").
    // 6. If trap is undefined, then
    Handle<Name> trap_name = factory()->apply_string();
    Node* trap = GetMethod(context, handler, trap_name, &trap_undefined);
  
    CodeStubArguments args(this, argc_ptr);
    TNode<Object> 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);
    TNode<IntPtrT> 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.
    TNode<HeapObject> handler =
        CAST(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.
    TNode<Object> target = LoadObjectField(proxy, JSProxy::kTargetOffset);
  
    // 5. Let trap be ? GetMethod(handler, "construct").
    // 6. If trap is undefined, then
    Handle<Name> 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), &not_an_object);
    GotoIfNot(IsJSReceiver(new_obj), &not_an_object);
  
    // 10. Return newObj.
    args.PopAndReturn(new_obj);
  
    BIND(&not_an_object);
    {
      ThrowTypeError(context, MessageTemplate::kProxyConstructNonObject, new_obj);
    }
  
    BIND(&trap_undefined);
    {
      // 6.a. Assert: target has a [[Construct]] internal method.
      CSA_ASSERT(this, IsConstructor(CAST(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"); }
  }
  
  void ProxiesCodeStubAssembler::CheckGetSetTrapResult(
      TNode<Context> context, TNode<JSReceiver> target, TNode<JSProxy> proxy,
      TNode<Name> name, TNode<Object> trap_result,
      JSProxy::AccessKind access_kind) {
    // TODO(mslekova): Think of a better name for the trap_result param.
    TNode<Map> 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),
        check_passed(this);
  
    GotoIfNot(IsUniqueNameNoIndex(name), &check_in_runtime);
    TNode<Uint16T> 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);
      {
        TNode<BoolT> 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:
          TNode<Object> 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.
          TNode<Object> setter =
              LoadObjectField(accessor_pair, AccessorPair::kSetterOffset);
          GotoIf(IsUndefined(setter), &throw_non_configurable_accessor);
          GotoIf(IsNull(setter), &throw_non_configurable_accessor);
        }
        Goto(&check_passed);
      }
  
      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);
        }
      }
  
      BIND(&check_in_runtime);
      {
        CallRuntime(Runtime::kCheckProxyGetSetTrapResult, context, name, target,
                    trap_result, SmiConstant(access_kind));
        Goto(&check_passed);
      }
  
      BIND(&check_passed);
    }
  }
  
  void ProxiesCodeStubAssembler::CheckHasTrapResult(TNode<Context> context,
                                                    TNode<JSReceiver> target,
                                                    TNode<JSProxy> proxy,
                                                    TNode<Name> name) {
    TNode<Map> 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), check_passed(this),
        check_in_runtime(this, Label::kDeferred);
  
    // 9.a. Let targetDesc be ? target.[[GetOwnProperty]](P).
    GotoIfNot(IsUniqueNameNoIndex(name), &check_in_runtime);
    TNode<Uint16T> 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, &check_in_runtime, 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.
      TNode<BoolT> non_configurable = IsSetWord32(
          var_details.value(), PropertyDetails::kAttributesDontDeleteMask);
      GotoIf(non_configurable, &throw_non_configurable);
  
      // 9.b.ii. Let extensibleTarget be ? IsExtensible(target).
      TNode<BoolT> 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); }
  
    BIND(&check_in_runtime);
    {
      CallRuntime(Runtime::kCheckProxyHasTrapResult, context, name, target);
      Goto(&check_passed);
    }
  
    BIND(&check_passed);
  }
  
  void ProxiesCodeStubAssembler::CheckDeleteTrapResult(TNode<Context> context,
                                                       TNode<JSReceiver> target,
                                                       TNode<JSProxy> proxy,
                                                       TNode<Name> name) {
    TNode<Map> target_map = LoadMap(target);
    TVARIABLE(Object, var_value);
    TVARIABLE(Uint32T, var_details);
    TVARIABLE(Object, var_raw_value);
  
    Label if_found_value(this, Label::kDeferred),
        throw_non_configurable(this, Label::kDeferred),
        throw_non_extensible(this, Label::kDeferred), check_passed(this),
        check_in_runtime(this, Label::kDeferred);
  
    // 10. Let targetDesc be ? target.[[GetOwnProperty]](P).
    GotoIfNot(IsUniqueNameNoIndex(name), &check_in_runtime);
    TNode<Uint16T> 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, &check_in_runtime, kReturnAccessorPair);
  
    // 11. If targetDesc is undefined, return true.
    BIND(&if_found_value);
    {
      // 12. If targetDesc.[[Configurable]] is false, throw a TypeError exception.
      TNode<BoolT> non_configurable = IsSetWord32(
          var_details.value(), PropertyDetails::kAttributesDontDeleteMask);
      GotoIf(non_configurable, &throw_non_configurable);
  
      // 13. Let extensibleTarget be ? IsExtensible(target).
      TNode<BoolT> target_extensible = IsExtensibleMap(target_map);
  
      // 14. If extensibleTarget is false, throw a TypeError exception.
      GotoIfNot(target_extensible, &throw_non_extensible);
      Goto(&check_passed);
    }
  
    BIND(&throw_non_configurable);
    {
      ThrowTypeError(context,
                     MessageTemplate::kProxyDeletePropertyNonConfigurable, name);
    }
  
    BIND(&throw_non_extensible);
    {
      ThrowTypeError(context, MessageTemplate::kProxyDeletePropertyNonExtensible,
                     name);
    }
  
    BIND(&check_in_runtime);
    {
      CallRuntime(Runtime::kCheckProxyDeleteTrapResult, context, name, target);
      Goto(&check_passed);
    }
  
    BIND(&check_passed);
  }
  
  }  // namespace internal
  }  // namespace v8
  