1// Copyright 2022 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "src/builtins/builtins-utils-gen.h"
6#include "src/builtins/builtins.h"
7#include "src/codegen/code-stub-assembler.h"
8#include "src/objects/descriptor-array.h"
9
10namespace v8 {
11namespace internal {
12
13class ShadowRealmBuiltinsAssembler : public CodeStubAssembler {
14 public:
15  explicit ShadowRealmBuiltinsAssembler(compiler::CodeAssemblerState* state)
16      : CodeStubAssembler(state) {}
17
18 protected:
19  TNode<JSObject> AllocateJSWrappedFunction(TNode<Context> context,
20                                            TNode<Object> target);
21  void CheckAccessor(TNode<DescriptorArray> array, TNode<IntPtrT> index,
22                     TNode<Name> name, Label* bailout);
23};
24
25TNode<JSObject> ShadowRealmBuiltinsAssembler::AllocateJSWrappedFunction(
26    TNode<Context> context, TNode<Object> target) {
27  TNode<NativeContext> native_context = LoadNativeContext(context);
28  TNode<Map> map = CAST(
29      LoadContextElement(native_context, Context::WRAPPED_FUNCTION_MAP_INDEX));
30  TNode<JSObject> wrapped = AllocateJSObjectFromMap(map);
31  StoreObjectFieldNoWriteBarrier(
32      wrapped, JSWrappedFunction::kWrappedTargetFunctionOffset, target);
33  StoreObjectFieldNoWriteBarrier(wrapped, JSWrappedFunction::kContextOffset,
34                                 context);
35  return wrapped;
36}
37
38void ShadowRealmBuiltinsAssembler::CheckAccessor(TNode<DescriptorArray> array,
39                                                 TNode<IntPtrT> index,
40                                                 TNode<Name> name,
41                                                 Label* bailout) {
42  TNode<Name> key = LoadKeyByDescriptorEntry(array, index);
43  GotoIfNot(TaggedEqual(key, name), bailout);
44  TNode<Object> value = LoadValueByDescriptorEntry(array, index);
45  GotoIfNot(IsAccessorInfo(CAST(value)), bailout);
46}
47
48// https://tc39.es/proposal-shadowrealm/#sec-getwrappedvalue
49TF_BUILTIN(ShadowRealmGetWrappedValue, ShadowRealmBuiltinsAssembler) {
50  auto context = Parameter<Context>(Descriptor::kContext);
51  auto creation_context = Parameter<Context>(Descriptor::kCreationContext);
52  auto target_context = Parameter<Context>(Descriptor::kTargetContext);
53  auto value = Parameter<Object>(Descriptor::kValue);
54
55  Label if_primitive(this), if_callable(this), unwrap(this), wrap(this),
56      slow_wrap(this, Label::kDeferred), bailout(this, Label::kDeferred);
57
58  // 2. Return value.
59  GotoIf(TaggedIsSmi(value), &if_primitive);
60  GotoIfNot(IsJSReceiver(CAST(value)), &if_primitive);
61
62  // 1. If Type(value) is Object, then
63  // 1a. If IsCallable(value) is false, throw a TypeError exception.
64  // 1b. Return ? WrappedFunctionCreate(callerRealm, value).
65  Branch(IsCallable(CAST(value)), &if_callable, &bailout);
66
67  BIND(&if_primitive);
68  Return(value);
69
70  BIND(&if_callable);
71  TVARIABLE(Object, target);
72  target = value;
73  // WrappedFunctionCreate
74  // https://tc39.es/proposal-shadowrealm/#sec-wrappedfunctioncreate
75  Branch(IsJSWrappedFunction(CAST(value)), &unwrap, &wrap);
76
77  BIND(&unwrap);
78  // The intermediate wrapped functions are not user-visible. And calling a
79  // wrapped function won't cause a side effect in the creation realm.
80  // Unwrap here to avoid nested unwrapping at the call site.
81  TNode<JSWrappedFunction> target_wrapped_function = CAST(value);
82  target = LoadObjectField(target_wrapped_function,
83                           JSWrappedFunction::kWrappedTargetFunctionOffset);
84  Goto(&wrap);
85
86  BIND(&wrap);
87  // Disallow wrapping of slow-mode functions. We need to figure out
88  // whether the length and name property are in the original state.
89  TNode<Map> map = LoadMap(CAST(target.value()));
90  GotoIf(IsDictionaryMap(map), &slow_wrap);
91
92  // Check whether the length and name properties are still present as
93  // AccessorInfo objects. If so, their value can be recomputed even if
94  // the actual value on the object changes.
95  TNode<Uint32T> bit_field3 = LoadMapBitField3(map);
96  TNode<IntPtrT> number_of_own_descriptors = Signed(
97      DecodeWordFromWord32<Map::Bits3::NumberOfOwnDescriptorsBits>(bit_field3));
98  GotoIf(IntPtrLessThan(
99             number_of_own_descriptors,
100             IntPtrConstant(JSFunction::kMinDescriptorsForFastBindAndWrap)),
101         &slow_wrap);
102
103  // We don't need to check the exact accessor here because the only case
104  // custom accessor arise is with function templates via API, and in that
105  // case the object is in dictionary mode
106  TNode<DescriptorArray> descriptors = LoadMapInstanceDescriptors(map);
107  CheckAccessor(
108      descriptors,
109      IntPtrConstant(
110          JSFunctionOrBoundFunctionOrWrappedFunction::kLengthDescriptorIndex),
111      LengthStringConstant(), &slow_wrap);
112  CheckAccessor(
113      descriptors,
114      IntPtrConstant(
115          JSFunctionOrBoundFunctionOrWrappedFunction::kNameDescriptorIndex),
116      NameStringConstant(), &slow_wrap);
117
118  // Verify that prototype matches the function prototype of the target
119  // context.
120  TNode<Object> prototype = LoadMapPrototype(map);
121  TNode<Object> function_map =
122      LoadContextElement(target_context, Context::WRAPPED_FUNCTION_MAP_INDEX);
123  TNode<Object> function_prototype = LoadMapPrototype(CAST(function_map));
124  GotoIf(TaggedNotEqual(prototype, function_prototype), &slow_wrap);
125
126  // 1. Let internalSlotsList be the internal slots listed in Table 2, plus
127  // [[Prototype]] and [[Extensible]].
128  // 2. Let wrapped be ! MakeBasicObject(internalSlotsList).
129  // 3. Set wrapped.[[Prototype]] to
130  // callerRealm.[[Intrinsics]].[[%Function.prototype%]].
131  // 4. Set wrapped.[[Call]] as described in 2.1.
132  // 5. Set wrapped.[[WrappedTargetFunction]] to Target.
133  // 6. Set wrapped.[[Realm]] to callerRealm.
134  // 7. Let result be CopyNameAndLength(wrapped, Target, "wrapped").
135  // 8. If result is an Abrupt Completion, throw a TypeError exception.
136  // Installed with default accessors.
137  TNode<JSObject> wrapped =
138      AllocateJSWrappedFunction(creation_context, target.value());
139
140  // 9. Return wrapped.
141  Return(wrapped);
142
143  BIND(&slow_wrap);
144  {
145    Return(CallRuntime(Runtime::kShadowRealmWrappedFunctionCreate, context,
146                       creation_context, target.value()));
147  }
148
149  BIND(&bailout);
150  ThrowTypeError(context, MessageTemplate::kNotCallable, value);
151}
152
153// https://tc39.es/proposal-shadowrealm/#sec-wrapped-function-exotic-objects-call-thisargument-argumentslist
154TF_BUILTIN(CallWrappedFunction, ShadowRealmBuiltinsAssembler) {
155  auto argc = UncheckedParameter<Int32T>(Descriptor::kActualArgumentsCount);
156  TNode<IntPtrT> argc_ptr = ChangeInt32ToIntPtr(argc);
157  auto wrapped_function = Parameter<JSWrappedFunction>(Descriptor::kFunction);
158  auto context = Parameter<Context>(Descriptor::kContext);
159
160  PerformStackCheck(context);
161
162  Label call_exception(this, Label::kDeferred),
163      target_not_callable(this, Label::kDeferred);
164
165  // 1. Let target be F.[[WrappedTargetFunction]].
166  TNode<JSReceiver> target = CAST(LoadObjectField(
167      wrapped_function, JSWrappedFunction::kWrappedTargetFunctionOffset));
168  // 2. Assert: IsCallable(target) is true.
169  CSA_DCHECK(this, IsCallable(target));
170
171  // 4. Let callerRealm be ? GetFunctionRealm(F).
172  TNode<Context> caller_context = LoadObjectField<Context>(
173      wrapped_function, JSWrappedFunction::kContextOffset);
174  // 3. Let targetRealm be ? GetFunctionRealm(target).
175  TNode<Context> target_context =
176      GetFunctionRealm(caller_context, target, &target_not_callable);
177  // 5. NOTE: Any exception objects produced after this point are associated
178  // with callerRealm.
179
180  CodeStubArguments args(this, argc_ptr);
181  TNode<Object> receiver = args.GetReceiver();
182
183  // 6. Let wrappedArgs be a new empty List.
184  TNode<FixedArray> wrapped_args =
185      CAST(AllocateFixedArray(ElementsKind::PACKED_ELEMENTS, argc_ptr));
186  // Fill the fixed array so that heap verifier doesn't complain about it.
187  FillFixedArrayWithValue(ElementsKind::PACKED_ELEMENTS, wrapped_args,
188                          IntPtrConstant(0), argc_ptr,
189                          RootIndex::kUndefinedValue);
190
191  // 8. Let wrappedThisArgument to ? GetWrappedValue(targetRealm, thisArgument).
192  // Create wrapped value in the target realm.
193  TNode<Object> wrapped_receiver =
194      CallBuiltin(Builtin::kShadowRealmGetWrappedValue, caller_context,
195                  target_context, caller_context, receiver);
196  StoreFixedArrayElement(wrapped_args, 0, wrapped_receiver);
197  // 7. For each element arg of argumentsList, do
198  BuildFastLoop<IntPtrT>(
199      IntPtrConstant(0), args.GetLengthWithoutReceiver(),
200      [&](TNode<IntPtrT> index) {
201        // 7a. Let wrappedValue be ? GetWrappedValue(targetRealm, arg).
202        // Create wrapped value in the target realm.
203        TNode<Object> wrapped_value =
204            CallBuiltin(Builtin::kShadowRealmGetWrappedValue, caller_context,
205                        target_context, caller_context, args.AtIndex(index));
206        // 7b. Append wrappedValue to wrappedArgs.
207        StoreFixedArrayElement(
208            wrapped_args, IntPtrAdd(index, IntPtrConstant(1)), wrapped_value);
209      },
210      1, IndexAdvanceMode::kPost);
211
212  TVARIABLE(Object, var_exception);
213  TNode<Object> result;
214  {
215    compiler::ScopedExceptionHandler handler(this, &call_exception,
216                                             &var_exception);
217    TNode<Int32T> args_count = Int32Constant(0);  // args already on the stack
218    Callable callable = CodeFactory::CallVarargs(isolate());
219
220    // 9. Let result be the Completion Record of Call(target,
221    // wrappedThisArgument, wrappedArgs).
222    result = CallStub(callable, target_context, target, args_count, argc,
223                      wrapped_args);
224  }
225
226  // 10. If result.[[Type]] is normal or result.[[Type]] is return, then
227  // 10a. Return ? GetWrappedValue(callerRealm, result.[[Value]]).
228  TNode<Object> wrapped_result =
229      CallBuiltin(Builtin::kShadowRealmGetWrappedValue, caller_context,
230                  caller_context, target_context, result);
231  args.PopAndReturn(wrapped_result);
232
233  // 11. Else,
234  BIND(&call_exception);
235  // 11a. Throw a TypeError exception.
236  // TODO(v8:11989): provide a non-observable inspection on the
237  // pending_exception to the newly created TypeError.
238  // https://github.com/tc39/proposal-shadowrealm/issues/353
239  ThrowTypeError(context, MessageTemplate::kCallShadowRealmFunctionThrown,
240                 var_exception.value());
241
242  BIND(&target_not_callable);
243  // A wrapped value should not be non-callable.
244  Unreachable();
245}
246
247}  // namespace internal
248}  // namespace v8
249