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
10 namespace v8 {
11 namespace internal {
12
13 class ShadowRealmBuiltinsAssembler : public CodeStubAssembler {
14 public:
ShadowRealmBuiltinsAssembler(compiler::CodeAssemblerState * state)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
AllocateJSWrappedFunction(TNode<Context> context,TNode<Object> target)25 TNode<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
CheckAccessor(TNode<DescriptorArray> array,TNode<IntPtrT> index,TNode<Name> name,Label * bailout)38 void 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
TF_BUILTIN(ShadowRealmGetWrappedValue,ShadowRealmBuiltinsAssembler)49 TF_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
TF_BUILTIN(CallWrappedFunction,ShadowRealmBuiltinsAssembler)154 TF_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