/src/hermes/lib/VM/JSProxy.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) Meta Platforms, Inc. and affiliates. |
3 | | * |
4 | | * This source code is licensed under the MIT license found in the |
5 | | * LICENSE file in the root directory of this source tree. |
6 | | */ |
7 | | |
8 | | #include "hermes/VM/JSProxy.h" |
9 | | |
10 | | #include "hermes/VM/ArrayLike.h" |
11 | | #include "hermes/VM/Callable.h" |
12 | | #include "hermes/VM/JSArray.h" |
13 | | #include "hermes/VM/JSCallableProxy.h" |
14 | | #include "hermes/VM/OrderedHashMap.h" |
15 | | #include "hermes/VM/PropertyAccessor.h" |
16 | | |
17 | | #include "llvh/ADT/SmallSet.h" |
18 | | |
19 | | #pragma GCC diagnostic push |
20 | | |
21 | | #ifdef HERMES_COMPILER_SUPPORTS_WSHORTEN_64_TO_32 |
22 | | #pragma GCC diagnostic ignored "-Wshorten-64-to-32" |
23 | | #endif |
24 | | namespace hermes { |
25 | | namespace vm { |
26 | | |
27 | | namespace detail { |
28 | | |
29 | 0 | ProxySlots &slots(JSObject *self) { |
30 | 0 | if (auto *proxy = dyn_vmcast<JSProxy>(self)) { |
31 | 0 | return proxy->slots_; |
32 | 0 | } else { |
33 | 0 | auto *cproxy = dyn_vmcast<JSCallableProxy>(self); |
34 | 0 | assert( |
35 | 0 | cproxy && "JSProxy methods must be passed JSProxy or JSCallableProxy"); |
36 | 0 | return cproxy->slots_; |
37 | 0 | } |
38 | 0 | } |
39 | | |
40 | | CallResult<Handle<Callable>> |
41 | 0 | findTrap(Handle<JSObject> selfHandle, Runtime &runtime, Predefined::Str name) { |
42 | | // 2. Let handler be O.[[ProxyHandler]]. |
43 | | // 3. If handler is null, throw a TypeError exception. |
44 | 0 | JSObject *handlerPtr = detail::slots(*selfHandle).handler.get(runtime); |
45 | 0 | if (!handlerPtr) { |
46 | 0 | return runtime.raiseTypeError("Proxy handler is null"); |
47 | 0 | } |
48 | | // 4. Assert: Type(handler) is Object. |
49 | | // 5. Let target be O.[[ProxyTarget]]. |
50 | | // 6. Let trap be ? GetMethod(handler, « name »). |
51 | 0 | CallResult<PseudoHandle<>> trapVal = [&]() { |
52 | 0 | GCScope gcScope(runtime); |
53 | 0 | Handle<JSObject> handler = runtime.makeHandle(handlerPtr); |
54 | 0 | return JSObject::getNamed_RJS( |
55 | 0 | handler, runtime, Predefined::getSymbolID(name)); |
56 | 0 | }(); |
57 | |
|
58 | 0 | if (trapVal == ExecutionStatus::EXCEPTION) { |
59 | 0 | return ExecutionStatus::EXCEPTION; |
60 | 0 | } |
61 | 0 | if ((*trapVal)->isUndefined() || (*trapVal)->isNull()) { |
62 | 0 | return Runtime::makeNullHandle<Callable>(); |
63 | 0 | } |
64 | | |
65 | 0 | if (!vmisa<Callable>(trapVal->get())) { |
66 | 0 | return runtime.raiseTypeErrorForValue( |
67 | 0 | runtime.makeHandle(std::move(*trapVal)), |
68 | 0 | " is not a Proxy trap function"); |
69 | 0 | } |
70 | 0 | return runtime.makeHandle<Callable>(trapVal->get()); |
71 | 0 | } |
72 | | |
73 | | } // namespace detail |
74 | | |
75 | | //===----------------------------------------------------------------------===// |
76 | | // class JSProxy |
77 | | |
78 | | const ObjectVTable JSProxy::vt{ |
79 | | VTable(CellKind::JSProxyKind, cellSize<JSProxy>()), |
80 | | JSProxy::_getOwnIndexedRangeImpl, |
81 | | JSProxy::_haveOwnIndexedImpl, |
82 | | JSProxy::_getOwnIndexedPropertyFlagsImpl, |
83 | | JSProxy::_getOwnIndexedImpl, |
84 | | JSProxy::_setOwnIndexedImpl, |
85 | | JSProxy::_deleteOwnIndexedImpl, |
86 | | JSProxy::_checkAllOwnIndexedImpl, |
87 | | }; |
88 | | |
89 | 1 | void JSProxyBuildMeta(const GCCell *cell, Metadata::Builder &mb) { |
90 | 1 | mb.addJSObjectOverlapSlots(JSObject::numOverlapSlots<JSProxy>()); |
91 | 1 | JSObjectBuildMeta(cell, mb); |
92 | 1 | const auto *self = static_cast<const JSProxy *>(cell); |
93 | 1 | mb.setVTable(&JSProxy::vt); |
94 | 1 | mb.addField("@target", &self->slots_.target); |
95 | 1 | mb.addField("@handler", &self->slots_.handler); |
96 | 1 | } |
97 | | |
98 | 0 | PseudoHandle<JSProxy> JSProxy::create(Runtime &runtime) { |
99 | 0 | JSProxy *proxy = runtime.makeAFixed<JSProxy>( |
100 | 0 | runtime, |
101 | | // Proxy should not have an observable prototype, so we just set it to |
102 | | // null. |
103 | 0 | Runtime::makeNullHandle<JSObject>(), |
104 | 0 | runtime.getHiddenClassForPrototype( |
105 | 0 | nullptr, JSObject::numOverlapSlots<JSProxy>())); |
106 | |
|
107 | 0 | proxy->flags_.proxyObject = true; |
108 | |
|
109 | 0 | return JSObjectInit::initToPseudoHandle(runtime, proxy); |
110 | 0 | } |
111 | | |
112 | | void JSProxy::setTargetAndHandler( |
113 | | Handle<JSObject> selfHandle, |
114 | | Runtime &runtime, |
115 | | Handle<JSObject> target, |
116 | 0 | Handle<JSObject> handler) { |
117 | 0 | auto &slots = detail::slots(*selfHandle); |
118 | 0 | slots.target.set(runtime, target.get(), runtime.getHeap()); |
119 | 0 | slots.handler.set(runtime, handler.get(), runtime.getHeap()); |
120 | 0 | } |
121 | | |
122 | | namespace { |
123 | | |
124 | 0 | void completePropertyDescriptor(DefinePropertyFlags &desc) { |
125 | 0 | if ((desc.setValue || desc.setWritable) || |
126 | 0 | (!desc.setGetter && !desc.setSetter)) { |
127 | 0 | if (!desc.setWritable) { |
128 | 0 | desc.writable = false; |
129 | 0 | } |
130 | 0 | } |
131 | 0 | if (!desc.setEnumerable) { |
132 | 0 | desc.enumerable = false; |
133 | 0 | } |
134 | 0 | if (!desc.setConfigurable) { |
135 | 0 | desc.configurable = false; |
136 | 0 | } |
137 | 0 | } |
138 | | |
139 | | // ES9 9.1.6.2 IsCompatiblePropertyDescriptor |
140 | | // prereq: step 2 is already done externally. |
141 | | // The abstract definition returns a boolean; this returns EXCEPTION |
142 | | // (and sets an exception) or RETURNED instead of false or true, so |
143 | | // the exception messages can be more specific. |
144 | | ExecutionStatus isCompatiblePropertyDescriptor( |
145 | | Runtime &runtime, |
146 | | const DefinePropertyFlags &desc, |
147 | | Handle<> descValueOrAccessor, |
148 | | const ComputedPropertyDescriptor ¤t, |
149 | 0 | Handle<> currentValueOrAccessor) { |
150 | | // 4. If current.[[Configurable]] is false, then |
151 | 0 | if (!current.flags.configurable) { |
152 | | // a. If Desc.[[Configurable]] is present and its value is true, return |
153 | | // false. |
154 | 0 | if (desc.setConfigurable && desc.configurable) { |
155 | 0 | return runtime.raiseTypeError( |
156 | 0 | "trap result is configurable but target property is non-configurable"); |
157 | 0 | } |
158 | | // b. If Desc.[[Enumerable]] is present and the [[Enumerable]] |
159 | | // fields of current and Desc are the Boolean negation of each |
160 | | // other, return false. |
161 | 0 | if (desc.setEnumerable && desc.enumerable != current.flags.enumerable) { |
162 | 0 | return runtime.raiseTypeError( |
163 | 0 | TwineChar16("trap result is ") + (desc.enumerable ? "" : "not ") + |
164 | 0 | "enumerable but target property is " + |
165 | 0 | (current.flags.enumerable ? "" : "not ") + "enumerable"); |
166 | 0 | } |
167 | 0 | } |
168 | | |
169 | | // 5. If IsGenericDescriptor(Desc) is true, no further validation is required. |
170 | 0 | bool descIsAccessor = desc.setSetter || desc.setGetter; |
171 | 0 | bool descIsData = desc.setValue || desc.setWritable; |
172 | 0 | assert( |
173 | 0 | (!descIsData || !descIsAccessor) && |
174 | 0 | "descriptor cannot be both Data and Accessor"); |
175 | 0 | if (!descIsData && !descIsAccessor) { |
176 | 0 | return ExecutionStatus::RETURNED; |
177 | 0 | } |
178 | | // 6. Else if IsDataDescriptor(current) and IsDataDescriptor(Desc) |
179 | | // have different results, then |
180 | 0 | bool currentIsAccessor = current.flags.accessor; |
181 | 0 | bool currentIsData = !currentIsAccessor; |
182 | 0 | if (currentIsData != descIsData) { |
183 | | // a. If current.[[Configurable]] is false, return false. |
184 | 0 | if (!current.flags.configurable) { |
185 | 0 | return runtime.raiseTypeError( |
186 | 0 | TwineChar16("trap result is ") + |
187 | 0 | (currentIsData ? "data " : "accessor ") + "but target property is " + |
188 | 0 | (descIsData ? "data " : "accessor ") + "and non-configurable"); |
189 | 0 | } |
190 | 0 | } |
191 | | // 7. Else if IsDataDescriptor(current) and IsDataDescriptor(Desc) are both |
192 | | // true, then |
193 | | // a. If current.[[Configurable]] is false and current.[[Writable]] is |
194 | | // false, then |
195 | 0 | if (currentIsData && descIsData && !current.flags.configurable && |
196 | 0 | !current.flags.writable) { |
197 | | // i. If Desc.[[Writable]] is present and Desc.[[Writable]] is true, |
198 | | // return false. |
199 | 0 | if (desc.setWritable && desc.writable) { |
200 | 0 | return runtime.raiseTypeError( |
201 | 0 | "trap result is writable but " |
202 | 0 | "target property is non-configurable and non-writable"); |
203 | 0 | } |
204 | | // ii. If Desc.[[Value]] is present and SameValue(Desc.[[Value]], |
205 | | // current.[[Value]]) is false, return false. |
206 | 0 | if (desc.setValue && |
207 | 0 | !isSameValue( |
208 | 0 | descValueOrAccessor.getHermesValue(), |
209 | 0 | currentValueOrAccessor.getHermesValue())) { |
210 | 0 | return runtime.raiseTypeError( |
211 | 0 | "trap result has different value than target property but " |
212 | 0 | "target property is non-configurable and non-writable"); |
213 | 0 | } |
214 | | // iii. Return true. |
215 | 0 | return ExecutionStatus::RETURNED; |
216 | 0 | } |
217 | | // 8. Else IsAccessorDescriptor(current) and IsAccessorDescriptor(Desc) are |
218 | | // both true, |
219 | | // a. If current.[[Configurable]] is false, then |
220 | 0 | if (currentIsAccessor && descIsAccessor && !current.flags.configurable) { |
221 | 0 | PropertyAccessor *descAccessor = |
222 | 0 | vmcast<PropertyAccessor>(descValueOrAccessor.get()); |
223 | 0 | PropertyAccessor *currentAccessor = |
224 | 0 | vmcast<PropertyAccessor>(currentValueOrAccessor.get()); |
225 | | // i. If Desc.[[Set]] is present and SameValue(Desc.[[Set]], |
226 | | // current.[[Set]]) is false, return false. |
227 | 0 | if (descAccessor->setter && |
228 | 0 | descAccessor->setter != currentAccessor->setter) { |
229 | 0 | return runtime.raiseTypeError( |
230 | 0 | "trap result has different setter than target property but " |
231 | 0 | "target property is non-configurable"); |
232 | 0 | } |
233 | | // ii. If Desc.[[Get]] is present and SameValue(Desc.[[Get]], |
234 | | // current.[[Get]]) is false, return false. |
235 | 0 | if (descAccessor->getter && |
236 | 0 | descAccessor->getter != currentAccessor->getter) { |
237 | 0 | return runtime.raiseTypeError( |
238 | 0 | "trap result has different getter than target property but " |
239 | 0 | "target property is non-configurable"); |
240 | 0 | } |
241 | | // iii. Return true. |
242 | 0 | } |
243 | 0 | return ExecutionStatus::RETURNED; |
244 | 0 | } |
245 | | |
246 | | } // namespace |
247 | | |
248 | | CallResult<PseudoHandle<JSObject>> JSProxy::getPrototypeOf( |
249 | | Handle<JSObject> selfHandle, |
250 | 0 | Runtime &runtime) { |
251 | | // Proxies are complex, and various parts of the logic (finding |
252 | | // traps, undefined traps handling, calling traps, etc) are all |
253 | | // potentially recursive. Therefore, every entry point creates a |
254 | | // scope and a ScopedNativeDepthTracker, as it's possible to use up |
255 | | // arbitrary native stack depth with nested proxies. |
256 | 0 | GCScope gcScope(runtime); |
257 | 0 | ScopedNativeDepthTracker depthTracker(runtime); |
258 | 0 | if (LLVM_UNLIKELY(depthTracker.overflowed())) { |
259 | 0 | return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); |
260 | 0 | } |
261 | | // Make sure to retrieve the target and handler before any JS can execute. |
262 | 0 | auto &slots = detail::slots(*selfHandle); |
263 | 0 | Handle<JSObject> target = runtime.makeHandle(slots.target); |
264 | 0 | Handle<JSObject> handler = runtime.makeHandle(slots.handler); |
265 | 0 | CallResult<Handle<Callable>> trapRes = |
266 | 0 | detail::findTrap(selfHandle, runtime, Predefined::getPrototypeOf); |
267 | 0 | if (LLVM_UNLIKELY(trapRes == ExecutionStatus::EXCEPTION)) { |
268 | 0 | return ExecutionStatus::EXCEPTION; |
269 | 0 | } |
270 | | // 6. If trap is undefined, then |
271 | 0 | if (!*trapRes) { |
272 | | // a. Return ? target.[[GetPrototypeOf]](P). |
273 | 0 | return JSObject::getPrototypeOf(target, runtime); |
274 | 0 | } |
275 | | // 7. Let handlerProto be ? Call(trap, handler, « target »). |
276 | 0 | CallResult<PseudoHandle<>> handlerProtoRes = Callable::executeCall1( |
277 | 0 | *trapRes, runtime, handler, target.getHermesValue()); |
278 | 0 | if (handlerProtoRes == ExecutionStatus::EXCEPTION) { |
279 | 0 | return ExecutionStatus::EXCEPTION; |
280 | 0 | } |
281 | | // 8. If Type(handlerProto) is neither Object nor Null, throw a TypeError |
282 | | // exception. |
283 | 0 | if (!(*handlerProtoRes)->isObject() && !(*handlerProtoRes)->isNull()) { |
284 | 0 | return runtime.raiseTypeError( |
285 | 0 | "getPrototypeOf trap result is neither Object nor Null"); |
286 | 0 | } |
287 | 0 | Handle<JSObject> handlerProto = |
288 | 0 | runtime.makeHandle(dyn_vmcast<JSObject>(handlerProtoRes->get())); |
289 | | |
290 | | // 9. Let extensibleTarget be ? IsExtensible(target). |
291 | 0 | CallResult<bool> extensibleRes = JSObject::isExtensible(target, runtime); |
292 | 0 | if (extensibleRes == ExecutionStatus::EXCEPTION) { |
293 | 0 | return ExecutionStatus::EXCEPTION; |
294 | 0 | } |
295 | | // 10. If extensibleTarget is true, return handlerProto. |
296 | 0 | if (*extensibleRes) { |
297 | 0 | return createPseudoHandle(*handlerProto); |
298 | 0 | } |
299 | | // 11. Let targetProto be ? target.[[GetPrototypeOf]](). |
300 | 0 | CallResult<PseudoHandle<JSObject>> targetProtoRes = |
301 | 0 | JSObject::getPrototypeOf(target, runtime); |
302 | 0 | if (targetProtoRes == ExecutionStatus::EXCEPTION) { |
303 | 0 | return ExecutionStatus::EXCEPTION; |
304 | 0 | } |
305 | | // 12. If SameValue(handlerProto, targetProto) is false, throw a TypeError |
306 | | // exception. |
307 | 0 | if (handlerProto.get() != targetProtoRes->get()) { |
308 | 0 | return runtime.raiseTypeError( |
309 | 0 | "getPrototypeOf trap result is not the same as non-extensible target getPrototypeOf"); |
310 | 0 | } |
311 | | // 13. Return handlerProto. |
312 | 0 | return std::move(*targetProtoRes); |
313 | 0 | } |
314 | | |
315 | | CallResult<bool> JSProxy::setPrototypeOf( |
316 | | Handle<JSObject> selfHandle, |
317 | | Runtime &runtime, |
318 | 0 | Handle<JSObject> parent) { |
319 | 0 | GCScope gcScope{runtime}; |
320 | 0 | ScopedNativeDepthTracker depthTracker(runtime); |
321 | 0 | if (LLVM_UNLIKELY(depthTracker.overflowed())) { |
322 | 0 | return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); |
323 | 0 | } |
324 | | // Make sure to retrieve the target and handler before any JS can execute. |
325 | 0 | auto &slots = detail::slots(*selfHandle); |
326 | 0 | Handle<JSObject> target = runtime.makeHandle(slots.target); |
327 | 0 | Handle<JSObject> handler = runtime.makeHandle(slots.handler); |
328 | 0 | CallResult<Handle<Callable>> trapRes = |
329 | 0 | detail::findTrap(selfHandle, runtime, Predefined::setPrototypeOf); |
330 | 0 | if (trapRes == ExecutionStatus::EXCEPTION) { |
331 | 0 | return ExecutionStatus::EXCEPTION; |
332 | 0 | } |
333 | | // 7. If trap is undefined, then |
334 | 0 | if (!*trapRes) { |
335 | | // a. Return ? target.[[SetPrototypeOf]](V). |
336 | 0 | return JSObject::setParent(*target, runtime, *parent); |
337 | 0 | } |
338 | | // 8. Let booleanTrapResult be ToBoolean(? Call(trap, handler, « target, V |
339 | | // »)). |
340 | 0 | CallResult<PseudoHandle<>> booleanTrapRes = Callable::executeCall2( |
341 | 0 | *trapRes, |
342 | 0 | runtime, |
343 | 0 | handler, |
344 | 0 | target.getHermesValue(), |
345 | 0 | *parent ? parent.getHermesValue() : HermesValue::encodeNullValue()); |
346 | 0 | if (booleanTrapRes == ExecutionStatus::EXCEPTION) { |
347 | 0 | return ExecutionStatus::EXCEPTION; |
348 | 0 | } |
349 | | // 9. If booleanTrapResult is false, return false. |
350 | 0 | if (!toBoolean(booleanTrapRes->get())) { |
351 | 0 | return false; |
352 | 0 | } |
353 | | // 10. Let extensibleTarget be ? IsExtensible(target). |
354 | 0 | CallResult<bool> extensibleRes = JSObject::isExtensible(target, runtime); |
355 | 0 | if (extensibleRes == ExecutionStatus::EXCEPTION) { |
356 | 0 | return ExecutionStatus::EXCEPTION; |
357 | 0 | } |
358 | | // 11. If extensibleTarget is true, return true. |
359 | 0 | if (*extensibleRes) { |
360 | 0 | return true; |
361 | 0 | } |
362 | | // 12. Let targetProto be ? target.[[GetPrototypeOf]](). |
363 | 0 | CallResult<PseudoHandle<JSObject>> targetProtoRes = |
364 | 0 | JSObject::getPrototypeOf(target, runtime); |
365 | 0 | if (targetProtoRes == ExecutionStatus::EXCEPTION) { |
366 | 0 | return ExecutionStatus::EXCEPTION; |
367 | 0 | } |
368 | | // 13. If SameValue(V, targetProto) is false, throw a TypeError exception. |
369 | 0 | if (parent.get() != targetProtoRes->get()) { |
370 | 0 | return runtime.raiseTypeError( |
371 | 0 | "setPrototypeOf trap changed prototype on non-extensible target"); |
372 | 0 | } |
373 | | // 14. Return true. |
374 | 0 | return true; |
375 | 0 | } |
376 | | |
377 | | CallResult<bool> JSProxy::isExtensible( |
378 | | Handle<JSObject> selfHandle, |
379 | 0 | Runtime &runtime) { |
380 | 0 | GCScope gcScope{runtime}; |
381 | 0 | ScopedNativeDepthTracker depthTracker(runtime); |
382 | 0 | if (LLVM_UNLIKELY(depthTracker.overflowed())) { |
383 | 0 | return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); |
384 | 0 | } |
385 | | // Make sure to retrieve the target and handler before any JS can execute. |
386 | 0 | auto &slots = detail::slots(*selfHandle); |
387 | 0 | Handle<JSObject> target = runtime.makeHandle(slots.target); |
388 | 0 | Handle<JSObject> handler = runtime.makeHandle(slots.handler); |
389 | 0 | CallResult<Handle<Callable>> trapRes = |
390 | 0 | detail::findTrap(selfHandle, runtime, Predefined::isExtensible); |
391 | 0 | if (trapRes == ExecutionStatus::EXCEPTION) { |
392 | 0 | return ExecutionStatus::EXCEPTION; |
393 | 0 | } |
394 | | // 6. If trap is undefined, then |
395 | 0 | if (!*trapRes) { |
396 | | // a. Return ? target.[[IsExtensible]](). |
397 | 0 | return JSObject::isExtensible(target, runtime); |
398 | 0 | } |
399 | | // 7. Let booleanTrapResult be ToBoolean(? Call(trap, handler, « target »)). |
400 | 0 | CallResult<PseudoHandle<>> res = Callable::executeCall1( |
401 | 0 | *trapRes, runtime, handler, target.getHermesValue()); |
402 | 0 | if (res == ExecutionStatus::EXCEPTION) { |
403 | 0 | return ExecutionStatus::EXCEPTION; |
404 | 0 | } |
405 | 0 | bool booleanTrapResult = toBoolean(res->get()); |
406 | 0 | res->invalidate(); |
407 | | // 8. Let targetResult be ? target.[[IsExtensible]](). |
408 | 0 | CallResult<bool> targetRes = JSObject::isExtensible(target, runtime); |
409 | 0 | if (targetRes == ExecutionStatus::EXCEPTION) { |
410 | 0 | return ExecutionStatus::EXCEPTION; |
411 | 0 | } |
412 | | // 9. If SameValue(booleanTrapResult, targetResult) is false, throw |
413 | | // a TypeError exception. |
414 | 0 | if (booleanTrapResult != *targetRes) { |
415 | 0 | return runtime.raiseTypeError( |
416 | 0 | "isExtensible trap returned different value than target"); |
417 | 0 | } |
418 | | // 10. Return booleanTrapResult. |
419 | 0 | return booleanTrapResult; |
420 | 0 | } |
421 | | |
422 | | CallResult<bool> JSProxy::preventExtensions( |
423 | | Handle<JSObject> selfHandle, |
424 | | Runtime &runtime, |
425 | 0 | PropOpFlags opFlags) { |
426 | 0 | GCScope gcScope{runtime}; |
427 | 0 | ScopedNativeDepthTracker depthTracker(runtime); |
428 | 0 | if (LLVM_UNLIKELY(depthTracker.overflowed())) { |
429 | 0 | return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); |
430 | 0 | } |
431 | | // Make sure to retrieve the target and handler before any JS can execute. |
432 | 0 | auto &slots = detail::slots(*selfHandle); |
433 | 0 | Handle<JSObject> target = runtime.makeHandle(slots.target); |
434 | 0 | Handle<JSObject> handler = runtime.makeHandle(slots.handler); |
435 | 0 | CallResult<Handle<Callable>> trapRes = |
436 | 0 | detail::findTrap(selfHandle, runtime, Predefined::preventExtensions); |
437 | 0 | if (trapRes == ExecutionStatus::EXCEPTION) { |
438 | 0 | return ExecutionStatus::EXCEPTION; |
439 | 0 | } |
440 | | // 6. If trap is undefined, then |
441 | 0 | if (!*trapRes) { |
442 | | // a. Return ? target.[[PreventExtensions]](). |
443 | | // We pass in opFlags here. If getThrowOnError, then this will cause |
444 | | // the underlying exception to bubble up. If !getThrowOnError, then |
445 | | // we don't get a chance to raise a particular exception anyway. So in |
446 | | // either case, just return the CallResult. |
447 | 0 | return JSObject::preventExtensions(target, runtime, opFlags); |
448 | 0 | } |
449 | | // 7. Let booleanTrapResult be ToBoolean(? Call(trap, handler, « target »)). |
450 | 0 | CallResult<PseudoHandle<>> res = Callable::executeCall1( |
451 | 0 | *trapRes, runtime, handler, target.getHermesValue()); |
452 | 0 | if (res == ExecutionStatus::EXCEPTION) { |
453 | 0 | return ExecutionStatus::EXCEPTION; |
454 | 0 | } |
455 | 0 | bool booleanTrapResult = toBoolean(res->get()); |
456 | 0 | if (booleanTrapResult) { |
457 | | // a. Let targetIsExtensible be ? target.[[IsExtensible]](). |
458 | 0 | CallResult<bool> targetRes = JSObject::isExtensible(target, runtime); |
459 | 0 | if (targetRes == ExecutionStatus::EXCEPTION) { |
460 | 0 | return ExecutionStatus::EXCEPTION; |
461 | 0 | } |
462 | | // b. If targetIsExtensible is true, throw a TypeError exception. |
463 | 0 | if (*targetRes) { |
464 | 0 | return runtime.raiseTypeError( |
465 | 0 | "preventExtensions trap returned true for extensible target"); |
466 | 0 | } |
467 | 0 | } |
468 | | // 10. Return booleanTrapResult. |
469 | 0 | if (!booleanTrapResult && opFlags.getThrowOnError()) { |
470 | 0 | return runtime.raiseTypeError("preventExtensions trap returned false"); |
471 | 0 | } |
472 | 0 | return booleanTrapResult; |
473 | 0 | } |
474 | | |
475 | | CallResult<bool> JSProxy::getOwnProperty( |
476 | | Handle<JSObject> selfHandle, |
477 | | Runtime &runtime, |
478 | | Handle<> nameValHandle, |
479 | | ComputedPropertyDescriptor &desc, |
480 | 0 | MutableHandle<> *valueOrAccessor) { |
481 | 0 | GCScope gcScope{runtime}; |
482 | 0 | ScopedNativeDepthTracker depthTracker(runtime); |
483 | 0 | if (LLVM_UNLIKELY(depthTracker.overflowed())) { |
484 | 0 | return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); |
485 | 0 | } |
486 | | // Make sure to retrieve the target and handler before any JS can execute. |
487 | 0 | auto &slots = detail::slots(*selfHandle); |
488 | 0 | Handle<JSObject> target = runtime.makeHandle(slots.target); |
489 | 0 | Handle<JSObject> handler = runtime.makeHandle(slots.handler); |
490 | 0 | CallResult<Handle<Callable>> trapRes = detail::findTrap( |
491 | 0 | selfHandle, runtime, Predefined::getOwnPropertyDescriptor); |
492 | 0 | if (trapRes == ExecutionStatus::EXCEPTION) { |
493 | 0 | return ExecutionStatus::EXCEPTION; |
494 | 0 | } |
495 | 0 | MutableHandle<SymbolID> tmpPropNameStorage{runtime}; |
496 | | // 7. If trap is undefined, then |
497 | 0 | if (!*trapRes) { |
498 | | // a. Return ? target.[[GetOwnProperty]](P). |
499 | 0 | return valueOrAccessor |
500 | 0 | ? JSObject::getOwnComputedDescriptor( |
501 | 0 | target, |
502 | 0 | runtime, |
503 | 0 | nameValHandle, |
504 | 0 | tmpPropNameStorage, |
505 | 0 | desc, |
506 | 0 | *valueOrAccessor) |
507 | 0 | : JSObject::getOwnComputedDescriptor( |
508 | 0 | target, runtime, nameValHandle, tmpPropNameStorage, desc); |
509 | 0 | } |
510 | | // 8. Let trapResultObj be ? Call(trap, handler, « target, P »). |
511 | | // 9. If Type(trapResultObj) is neither Object nor Undefined, throw a |
512 | | // TypeError exception. |
513 | 0 | CallResult<PseudoHandle<>> trapResultRes = Callable::executeCall2( |
514 | 0 | *trapRes, |
515 | 0 | runtime, |
516 | 0 | handler, |
517 | 0 | target.getHermesValue(), |
518 | 0 | nameValHandle.getHermesValue()); |
519 | 0 | if (trapResultRes == ExecutionStatus::EXCEPTION) { |
520 | 0 | return ExecutionStatus::EXCEPTION; |
521 | 0 | } |
522 | 0 | Handle<> trapResultObj = runtime.makeHandle(std::move(*trapResultRes)); |
523 | | // 10. Let targetDesc be ? target.[[GetOwnProperty]](P). |
524 | 0 | ComputedPropertyDescriptor targetDesc; |
525 | 0 | MutableHandle<> targetValueOrAccessor{runtime}; |
526 | 0 | CallResult<bool> targetDescRes = JSObject::getOwnComputedDescriptor( |
527 | 0 | target, |
528 | 0 | runtime, |
529 | 0 | nameValHandle, |
530 | 0 | tmpPropNameStorage, |
531 | 0 | targetDesc, |
532 | 0 | targetValueOrAccessor); |
533 | 0 | if (targetDescRes == ExecutionStatus::EXCEPTION) { |
534 | 0 | return ExecutionStatus::EXCEPTION; |
535 | 0 | } |
536 | | // 11. If trapResultObj is undefined, then |
537 | 0 | if (trapResultObj->isUndefined()) { |
538 | | // a. If targetDesc is undefined, return undefined. |
539 | 0 | if (!*targetDescRes) { |
540 | 0 | return false; |
541 | 0 | } |
542 | | // b. If targetDesc.[[Configurable]] is false, throw a TypeError |
543 | | // exception. |
544 | 0 | if (!targetDesc.flags.configurable) { |
545 | 0 | return runtime.raiseTypeError( |
546 | 0 | "getOwnPropertyDescriptor trap result is not configurable"); |
547 | 0 | } |
548 | | // c. Let extensibleTarget be ? IsExtensible(target). |
549 | 0 | CallResult<bool> extensibleRes = JSObject::isExtensible(target, runtime); |
550 | 0 | if (extensibleRes == ExecutionStatus::EXCEPTION) { |
551 | 0 | return ExecutionStatus::EXCEPTION; |
552 | 0 | } |
553 | | // d. Assert: Type(extensibleTarget) is Boolean. |
554 | | // e. If extensibleTarget is false, throw a TypeError exception. |
555 | 0 | if (!*extensibleRes) { |
556 | 0 | return runtime.raiseTypeErrorForValue( |
557 | 0 | target, " is not extensible (getOwnPropertyDescriptor target)"); |
558 | 0 | } |
559 | | // f. Return undefined. |
560 | 0 | return false; |
561 | 0 | } else if (!trapResultObj->isObject()) { |
562 | | // 9. If Type(trapResultObj) is neither Object nor Undefined, throw a |
563 | | // TypeError exception. |
564 | 0 | return runtime.raiseTypeErrorForValue( |
565 | 0 | trapResultObj, |
566 | 0 | " is not undefined or Object (Proxy getOwnPropertyDescriptor)"); |
567 | 0 | } |
568 | | // 12. Let extensibleTarget be ? IsExtensible(target). |
569 | 0 | CallResult<bool> extensibleRes = JSObject::isExtensible(target, runtime); |
570 | 0 | if (extensibleRes == ExecutionStatus::EXCEPTION) { |
571 | 0 | return ExecutionStatus::EXCEPTION; |
572 | 0 | } |
573 | | // 13. Let resultDesc be ? ToPropertyDescriptor(trapResultObj). |
574 | | // 14. Call CompletePropertyDescriptor(resultDesc). |
575 | 0 | DefinePropertyFlags resultDesc; |
576 | 0 | MutableHandle<> resultValueOrAccessor{runtime}; |
577 | 0 | Handle<JSObject> trapResult = runtime.makeHandle<JSObject>(*trapResultObj); |
578 | 0 | if (LLVM_UNLIKELY( |
579 | 0 | toPropertyDescriptor( |
580 | 0 | trapResult, runtime, resultDesc, resultValueOrAccessor) == |
581 | 0 | ExecutionStatus::EXCEPTION)) { |
582 | 0 | return ExecutionStatus::EXCEPTION; |
583 | 0 | } |
584 | 0 | completePropertyDescriptor(resultDesc); |
585 | | // 15. Let valid be IsCompatiblePropertyDescriptor(extensibleTarget, |
586 | | // resultDesc, targetDesc). |
587 | | // 16. If valid is false, throw a TypeError exception. |
588 | | |
589 | | // ES9 9.1.6.3 ValidateAndApplyPropertyDescriptor step 2 [O is undefined] |
590 | 0 | if (!*targetDescRes) { |
591 | | // a. If extensible is false, return false. |
592 | 0 | if (!*extensibleRes) { |
593 | 0 | return runtime.raiseTypeErrorForValue( |
594 | 0 | "getOwnPropertyDescriptor target is not extensible and has no property ", |
595 | 0 | nameValHandle, |
596 | 0 | ""); |
597 | 0 | } |
598 | | // e. return true |
599 | | // this concludes steps 15 and 16. |
600 | 0 | } else { |
601 | 0 | if (LLVM_UNLIKELY( |
602 | 0 | isCompatiblePropertyDescriptor( |
603 | 0 | runtime, |
604 | 0 | resultDesc, |
605 | 0 | resultValueOrAccessor, |
606 | 0 | targetDesc, |
607 | 0 | targetValueOrAccessor) == ExecutionStatus::EXCEPTION)) { |
608 | 0 | return ExecutionStatus::EXCEPTION; |
609 | 0 | } |
610 | 0 | } |
611 | | // 17. If resultDesc.[[Configurable]] is false, then |
612 | | // a. If targetDesc is undefined or targetDesc.[[Configurable]] is true, |
613 | | // then |
614 | | // i. Throw a TypeError exception. |
615 | 0 | if (!resultDesc.configurable && |
616 | 0 | (!*targetDescRes || targetDesc.flags.configurable)) { |
617 | 0 | return runtime.raiseTypeErrorForValue( |
618 | 0 | "getOwnPropertyDescriptor trap result is not configurable but " |
619 | 0 | "target property ", |
620 | 0 | nameValHandle, |
621 | 0 | " is configurable or non-existent"); |
622 | 0 | } |
623 | | // 18. Return resultDesc. |
624 | 0 | desc.flags.enumerable = resultDesc.enumerable; |
625 | 0 | desc.flags.configurable = resultDesc.configurable; |
626 | 0 | desc.flags.writable = resultDesc.writable; |
627 | 0 | if (resultDesc.setGetter || resultDesc.setSetter) { |
628 | 0 | desc.flags.accessor = true; |
629 | 0 | } |
630 | 0 | if (valueOrAccessor) { |
631 | 0 | *valueOrAccessor = std::move(resultValueOrAccessor); |
632 | 0 | } |
633 | 0 | return true; |
634 | 0 | } |
635 | | |
636 | | CallResult<bool> JSProxy::defineOwnProperty( |
637 | | Handle<JSObject> selfHandle, |
638 | | Runtime &runtime, |
639 | | Handle<> nameValHandle, |
640 | | DefinePropertyFlags dpFlags, |
641 | | Handle<> valueOrAccessor, |
642 | 0 | PropOpFlags opFlags) { |
643 | 0 | GCScope gcScope{runtime}; |
644 | 0 | ScopedNativeDepthTracker depthTracker(runtime); |
645 | 0 | if (LLVM_UNLIKELY(depthTracker.overflowed())) { |
646 | 0 | return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); |
647 | 0 | } |
648 | | // Make sure to retrieve the target and handler before any JS can execute. |
649 | 0 | auto &slots = detail::slots(*selfHandle); |
650 | 0 | Handle<JSObject> target = runtime.makeHandle(slots.target); |
651 | 0 | Handle<JSObject> handler = runtime.makeHandle(slots.handler); |
652 | 0 | CallResult<Handle<Callable>> trapRes = |
653 | 0 | detail::findTrap(selfHandle, runtime, Predefined::defineProperty); |
654 | 0 | if (trapRes == ExecutionStatus::EXCEPTION) { |
655 | 0 | return ExecutionStatus::EXCEPTION; |
656 | 0 | } |
657 | | // 7. If trap is undefined, then |
658 | 0 | if (!*trapRes) { |
659 | | // a. Return ? target.[[GetOwnProperty]](P). |
660 | 0 | return JSObject::defineOwnComputedPrimitive( |
661 | 0 | target, runtime, nameValHandle, dpFlags, valueOrAccessor, opFlags); |
662 | 0 | } |
663 | | // 8. Let descObj be FromPropertyDescriptor(Desc). |
664 | 0 | CallResult<HermesValue> descObjRes = |
665 | 0 | objectFromPropertyDescriptor(runtime, dpFlags, valueOrAccessor); |
666 | 0 | if (descObjRes == ExecutionStatus::EXCEPTION) { |
667 | 0 | return ExecutionStatus::EXCEPTION; |
668 | 0 | } |
669 | | // 9. Let booleanTrapResult be ToBoolean(? Call(trap, handler, « target, P, |
670 | | // descObj »)). |
671 | 0 | CallResult<PseudoHandle<>> trapResultRes = Callable::executeCall3( |
672 | 0 | *trapRes, |
673 | 0 | runtime, |
674 | 0 | handler, |
675 | 0 | target.getHermesValue(), |
676 | 0 | nameValHandle.getHermesValue(), |
677 | 0 | *descObjRes); |
678 | 0 | if (trapResultRes == ExecutionStatus::EXCEPTION) { |
679 | 0 | return ExecutionStatus::EXCEPTION; |
680 | 0 | } |
681 | 0 | bool trapResult = toBoolean(trapResultRes->get()); |
682 | | // 10. If booleanTrapResult is false, return false. |
683 | 0 | if (!trapResult) { |
684 | 0 | if (opFlags.getThrowOnError()) { |
685 | 0 | return runtime.raiseTypeError("defineProperty proxy trap returned false"); |
686 | 0 | } else { |
687 | 0 | return false; |
688 | 0 | } |
689 | 0 | } |
690 | | // 11. Let targetDesc be ? target.[[GetOwnProperty]](P). |
691 | 0 | ComputedPropertyDescriptor targetDesc; |
692 | 0 | MutableHandle<> targetDescValueOrAccessor{runtime}; |
693 | 0 | MutableHandle<SymbolID> tmpPropNameStorage{runtime}; |
694 | 0 | CallResult<bool> targetDescRes = JSObject::getOwnComputedDescriptor( |
695 | 0 | target, |
696 | 0 | runtime, |
697 | 0 | nameValHandle, |
698 | 0 | tmpPropNameStorage, |
699 | 0 | targetDesc, |
700 | 0 | targetDescValueOrAccessor); |
701 | 0 | if (targetDescRes == ExecutionStatus::EXCEPTION) { |
702 | 0 | return ExecutionStatus::EXCEPTION; |
703 | 0 | } |
704 | | // 12. Let extensibleTarget be ? IsExtensible(target). |
705 | 0 | CallResult<bool> extensibleRes = JSObject::isExtensible(target, runtime); |
706 | 0 | if (extensibleRes == ExecutionStatus::EXCEPTION) { |
707 | 0 | return ExecutionStatus::EXCEPTION; |
708 | 0 | } |
709 | | // 13. If Desc has a [[Configurable]] field and if Desc.[[Configurable]] is |
710 | | // false, then |
711 | | // a. Let settingConfigFalse be true. |
712 | | // 14. Else, let settingConfigFalse be false. |
713 | 0 | bool settingConfigFalse = dpFlags.setConfigurable && !dpFlags.configurable; |
714 | | // 15. If targetDesc is undefined, then |
715 | 0 | if (!*targetDescRes) { |
716 | | // a. If extensibleTarget is false, throw a TypeError exception. |
717 | 0 | if (!*extensibleRes) { |
718 | 0 | return runtime.raiseTypeError( |
719 | 0 | "defineProperty trap called for non-existent property on non-extensible target"); |
720 | 0 | } |
721 | | // b. If settingConfigFalse is true, throw a TypeError exception. |
722 | 0 | if (settingConfigFalse) { |
723 | 0 | return runtime.raiseTypeError( |
724 | 0 | "defineProperty trap attempted to define non-configurable property for non-existent property in the target"); |
725 | 0 | } |
726 | 0 | } else { |
727 | | // 16. Else targetDesc is not undefined, |
728 | | // a. If IsCompatiblePropertyDescriptor(extensibleTarget, Desc, |
729 | | // targetDesc) is false, throw a TypeError exception. |
730 | 0 | if (LLVM_UNLIKELY( |
731 | 0 | isCompatiblePropertyDescriptor( |
732 | 0 | runtime, |
733 | 0 | dpFlags, |
734 | 0 | valueOrAccessor, |
735 | 0 | targetDesc, |
736 | 0 | targetDescValueOrAccessor) == ExecutionStatus::EXCEPTION)) { |
737 | 0 | return ExecutionStatus::EXCEPTION; |
738 | 0 | } |
739 | | // b. If settingConfigFalse is true and targetDesc.[[Configurable]] is |
740 | | // true, throw a TypeError exception. |
741 | 0 | if (settingConfigFalse && targetDesc.flags.configurable) { |
742 | 0 | return runtime.raiseTypeError( |
743 | 0 | "defineProperty trap attempted to define non-configurable property for configurable property in the target"); |
744 | 0 | } |
745 | 0 | } |
746 | | // 17. Return true. |
747 | 0 | return true; |
748 | 0 | } |
749 | | |
750 | | namespace { |
751 | | |
752 | | /// Common parts of hasNamed/hasComputed |
753 | | CallResult<bool> hasWithTrap( |
754 | | Runtime &runtime, |
755 | | Handle<> nameValHandle, |
756 | | Handle<Callable> trap, |
757 | | Handle<JSObject> handler, |
758 | 0 | Handle<JSObject> target) { |
759 | | // 1. Assert: IsPropertyKey(P) is true. |
760 | 0 | assert(isPropertyKey(nameValHandle) && "key is not a String or Symbol"); |
761 | | // 8. Let booleanTrapResult be ToBoolean(? Call(trap, handler, « target, P |
762 | | // »)). |
763 | 0 | CallResult<PseudoHandle<>> trapResultRes = Callable::executeCall2( |
764 | 0 | trap, |
765 | 0 | runtime, |
766 | 0 | handler, |
767 | 0 | target.getHermesValue(), |
768 | 0 | nameValHandle.getHermesValue()); |
769 | 0 | if (trapResultRes == ExecutionStatus::EXCEPTION) { |
770 | 0 | return ExecutionStatus::EXCEPTION; |
771 | 0 | } |
772 | 0 | bool trapResult = toBoolean(trapResultRes->get()); |
773 | | // 9. If booleanTrapResult is false, then |
774 | 0 | if (!trapResult) { |
775 | | // a. Let targetDesc be ? target.[[GetOwnProperty]](P). |
776 | 0 | ComputedPropertyDescriptor targetDesc; |
777 | 0 | MutableHandle<SymbolID> tmpPropNameStorage{runtime}; |
778 | 0 | CallResult<bool> targetDescRes = JSObject::getOwnComputedDescriptor( |
779 | 0 | target, runtime, nameValHandle, tmpPropNameStorage, targetDesc); |
780 | 0 | if (targetDescRes == ExecutionStatus::EXCEPTION) { |
781 | 0 | return ExecutionStatus::EXCEPTION; |
782 | 0 | } |
783 | | // b. If targetDesc is not undefined, then |
784 | 0 | if (*targetDescRes) { |
785 | | // i. If targetDesc.[[Configurable]] is false, throw a TypeError |
786 | | // exception. |
787 | 0 | if (!targetDesc.flags.configurable) { |
788 | 0 | return runtime.raiseTypeError( |
789 | 0 | "HasProperty trap result is not configurable"); |
790 | 0 | } |
791 | | // ii. Let extensibleTarget be ? IsExtensible(target). |
792 | 0 | CallResult<bool> extensibleRes = JSObject::isExtensible(target, runtime); |
793 | 0 | if (extensibleRes == ExecutionStatus::EXCEPTION) { |
794 | 0 | return ExecutionStatus::EXCEPTION; |
795 | 0 | } |
796 | | // iii. If extensibleTarget is false, throw a TypeError exception. |
797 | 0 | if (!*extensibleRes) { |
798 | 0 | return runtime.raiseTypeError( |
799 | 0 | "HasProperty proxy target is not extensible"); |
800 | 0 | } |
801 | 0 | } |
802 | 0 | } |
803 | | // 11. Return trapResult. |
804 | 0 | return trapResult; |
805 | 0 | } |
806 | | |
807 | | } // namespace |
808 | | |
809 | | CallResult<bool> JSProxy::hasNamed( |
810 | | Handle<JSObject> selfHandle, |
811 | | Runtime &runtime, |
812 | 0 | SymbolID name) { |
813 | 0 | GCScope gcScope{runtime}; |
814 | 0 | ScopedNativeDepthTracker depthTracker(runtime); |
815 | 0 | if (LLVM_UNLIKELY(depthTracker.overflowed())) { |
816 | 0 | return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); |
817 | 0 | } |
818 | | // Make sure to retrieve the target and handler before any JS can execute. |
819 | 0 | auto &slots = detail::slots(*selfHandle); |
820 | 0 | Handle<JSObject> target = runtime.makeHandle(slots.target); |
821 | 0 | Handle<JSObject> handler = runtime.makeHandle(slots.handler); |
822 | 0 | CallResult<Handle<Callable>> trapRes = |
823 | 0 | detail::findTrap(selfHandle, runtime, Predefined::has); |
824 | 0 | if (trapRes == ExecutionStatus::EXCEPTION) { |
825 | 0 | return ExecutionStatus::EXCEPTION; |
826 | 0 | } |
827 | | // 7. If trap is undefined, then |
828 | 0 | if (!*trapRes) { |
829 | | // a. Return ? target.[[HasProperty]](P, Receiver). |
830 | 0 | return JSObject::hasNamed(target, runtime, name); |
831 | 0 | } |
832 | 0 | return hasWithTrap( |
833 | 0 | runtime, |
834 | 0 | runtime.makeHandle( |
835 | 0 | HermesValue::encodeStringValue( |
836 | 0 | runtime.getStringPrimFromSymbolID(name))), |
837 | 0 | *trapRes, |
838 | 0 | handler, |
839 | 0 | target); |
840 | 0 | } |
841 | | |
842 | | CallResult<bool> JSProxy::hasComputed( |
843 | | Handle<JSObject> selfHandle, |
844 | | Runtime &runtime, |
845 | 0 | Handle<> nameValHandle) { |
846 | 0 | GCScope gcScope{runtime}; |
847 | 0 | ScopedNativeDepthTracker depthTracker(runtime); |
848 | 0 | if (LLVM_UNLIKELY(depthTracker.overflowed())) { |
849 | 0 | return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); |
850 | 0 | } |
851 | | // Make sure to retrieve the target and handler before any JS can execute. |
852 | 0 | auto &slots = detail::slots(*selfHandle); |
853 | 0 | Handle<JSObject> target = runtime.makeHandle(slots.target); |
854 | 0 | Handle<JSObject> handler = runtime.makeHandle(slots.handler); |
855 | 0 | CallResult<Handle<Callable>> trapRes = |
856 | 0 | detail::findTrap(selfHandle, runtime, Predefined::has); |
857 | 0 | if (trapRes == ExecutionStatus::EXCEPTION) { |
858 | 0 | return ExecutionStatus::EXCEPTION; |
859 | 0 | } |
860 | 0 | if (!*trapRes) { |
861 | | // 7. If trap is undefined, then |
862 | | // a. Return ? target.[[HasProperty]](P, Receiver). |
863 | 0 | return JSObject::hasComputed(target, runtime, nameValHandle); |
864 | 0 | } |
865 | 0 | return hasWithTrap(runtime, nameValHandle, *trapRes, handler, target); |
866 | 0 | } |
867 | | |
868 | | namespace { |
869 | | |
870 | | /// Common parts of getNamed/getComputed |
871 | | CallResult<PseudoHandle<>> getWithTrap( |
872 | | Runtime &runtime, |
873 | | Handle<> nameValHandle, |
874 | | Handle<Callable> trap, |
875 | | Handle<JSObject> handler, |
876 | | Handle<JSObject> target, |
877 | 0 | Handle<> receiver) { |
878 | | // 1. Assert: IsPropertyKey(P) is true. |
879 | 0 | assert(isPropertyKey(nameValHandle) && "key is not a String or Symbol"); |
880 | | // 8. Let trapResult be ? Call(trap, handler, « target, P, Receiver »). |
881 | 0 | CallResult<PseudoHandle<>> trapResultRes = Callable::executeCall3( |
882 | 0 | trap, |
883 | 0 | runtime, |
884 | 0 | handler, |
885 | 0 | target.getHermesValue(), |
886 | 0 | nameValHandle.getHermesValue(), |
887 | 0 | receiver.getHermesValue()); |
888 | 0 | if (trapResultRes == ExecutionStatus::EXCEPTION) { |
889 | 0 | return ExecutionStatus::EXCEPTION; |
890 | 0 | } |
891 | 0 | Handle<> trapResult = runtime.makeHandle(std::move(*trapResultRes)); |
892 | | // 9. Let targetDesc be ? target.[[GetOwnProperty]](P). |
893 | 0 | ComputedPropertyDescriptor targetDesc; |
894 | 0 | MutableHandle<> targetValueOrAccessor{runtime}; |
895 | 0 | MutableHandle<SymbolID> tmpPropNameStorage{runtime}; |
896 | 0 | CallResult<bool> targetDescRes = JSObject::getOwnComputedDescriptor( |
897 | 0 | target, |
898 | 0 | runtime, |
899 | 0 | nameValHandle, |
900 | 0 | tmpPropNameStorage, |
901 | 0 | targetDesc, |
902 | 0 | targetValueOrAccessor); |
903 | 0 | if (targetDescRes == ExecutionStatus::EXCEPTION) { |
904 | 0 | return ExecutionStatus::EXCEPTION; |
905 | 0 | } |
906 | | // 10. If targetDesc is not undefined and targetDesc.[[Configurable]] is |
907 | | // false, then |
908 | 0 | if (*targetDescRes && !targetDesc.flags.configurable) { |
909 | | // a. If IsDataDescriptor(targetDesc) is true and targetDesc.[[Writable]] |
910 | | // is false, then |
911 | 0 | if (!targetDesc.flags.accessor && !targetDesc.flags.writable) { |
912 | | // i. If SameValue(trapResult, targetDesc.[[Value]]) is false, throw a |
913 | | // TypeError exception. |
914 | 0 | if (!isSameValue(*trapResult, targetValueOrAccessor.getHermesValue())) { |
915 | 0 | return runtime.raiseTypeError( |
916 | 0 | "target property is non-configurable and non-writable, and get trap result differs from target property value"); |
917 | 0 | } |
918 | 0 | } |
919 | | // b. If IsAccessorDescriptor(targetDesc) is true and targetDesc.[[Get]] |
920 | | // is undefined, then |
921 | | // i. If trapResult is not undefined, throw a TypeError exception. |
922 | 0 | if (targetDesc.flags.accessor && |
923 | 0 | !vmcast<PropertyAccessor>(*targetValueOrAccessor)->getter && |
924 | 0 | !trapResult->isUndefined()) { |
925 | 0 | return runtime.raiseTypeError( |
926 | 0 | "target property is non-configurable accessor with no getter, but get trap returned not undefined"); |
927 | 0 | } |
928 | 0 | } |
929 | | |
930 | | // 11. Return trapResult. |
931 | 0 | return {trapResult}; |
932 | 0 | } |
933 | | |
934 | | } // namespace |
935 | | |
936 | | CallResult<PseudoHandle<>> JSProxy::getNamed( |
937 | | Handle<JSObject> selfHandle, |
938 | | Runtime &runtime, |
939 | | SymbolID name, |
940 | 0 | Handle<> receiver) { |
941 | 0 | GCScope gcScope{runtime}; |
942 | 0 | ScopedNativeDepthTracker depthTracker(runtime); |
943 | 0 | if (LLVM_UNLIKELY(depthTracker.overflowed())) { |
944 | 0 | return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); |
945 | 0 | } |
946 | | // Make sure to retrieve the target and handler before calling findTrap, as |
947 | | // that may result in these fields being erased if the proxy is revoked in the |
948 | | // handler. |
949 | 0 | auto &slots = detail::slots(*selfHandle); |
950 | 0 | Handle<JSObject> target = runtime.makeHandle(slots.target); |
951 | 0 | Handle<JSObject> handler = runtime.makeHandle(slots.handler); |
952 | 0 | CallResult<Handle<Callable>> trapRes = |
953 | 0 | detail::findTrap(selfHandle, runtime, Predefined::get); |
954 | 0 | if (trapRes == ExecutionStatus::EXCEPTION) { |
955 | 0 | return ExecutionStatus::EXCEPTION; |
956 | 0 | } |
957 | | // 7. If trap is undefined, then |
958 | 0 | if (!*trapRes) { |
959 | | // a. Return ? target.[[Get]](P, Receiver). |
960 | 0 | return JSObject::getNamedWithReceiver_RJS(target, runtime, name, receiver); |
961 | 0 | } |
962 | 0 | return getWithTrap( |
963 | 0 | runtime, |
964 | 0 | name.isUniqued() ? runtime.makeHandle( |
965 | 0 | HermesValue::encodeStringValue( |
966 | 0 | runtime.getStringPrimFromSymbolID(name))) |
967 | 0 | : runtime.makeHandle(name), |
968 | 0 | *trapRes, |
969 | 0 | handler, |
970 | 0 | target, |
971 | 0 | receiver); |
972 | 0 | } |
973 | | |
974 | | CallResult<PseudoHandle<>> JSProxy::getComputed( |
975 | | Handle<JSObject> selfHandle, |
976 | | Runtime &runtime, |
977 | | Handle<> nameValHandle, |
978 | 0 | Handle<> receiver) { |
979 | 0 | GCScope gcScope{runtime}; |
980 | 0 | ScopedNativeDepthTracker depthTracker(runtime); |
981 | 0 | if (LLVM_UNLIKELY(depthTracker.overflowed())) { |
982 | 0 | return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); |
983 | 0 | } |
984 | | // Make sure to retrieve the target and handler before any JS can execute. |
985 | 0 | auto &slots = detail::slots(*selfHandle); |
986 | 0 | Handle<JSObject> target = runtime.makeHandle(slots.target); |
987 | 0 | Handle<JSObject> handler = runtime.makeHandle(slots.handler); |
988 | 0 | CallResult<Handle<Callable>> trapRes = |
989 | 0 | detail::findTrap(selfHandle, runtime, Predefined::get); |
990 | 0 | if (trapRes == ExecutionStatus::EXCEPTION) { |
991 | 0 | return ExecutionStatus::EXCEPTION; |
992 | 0 | } |
993 | | // 7. If trap is undefined, then |
994 | 0 | if (!*trapRes) { |
995 | | // a. Return ? target.[[Get]](P, Receiver). |
996 | 0 | return JSObject::getComputedWithReceiver_RJS( |
997 | 0 | target, runtime, nameValHandle, receiver); |
998 | 0 | } |
999 | 0 | return getWithTrap( |
1000 | 0 | runtime, nameValHandle, *trapRes, handler, target, receiver); |
1001 | 0 | } |
1002 | | |
1003 | | namespace { |
1004 | | |
1005 | | /// Common parts of setNamed/setComputed |
1006 | | CallResult<bool> setWithTrap( |
1007 | | Runtime &runtime, |
1008 | | Handle<> nameValHandle, |
1009 | | Handle<> valueHandle, |
1010 | | Handle<Callable> trap, |
1011 | | Handle<JSObject> handler, |
1012 | | Handle<JSObject> target, |
1013 | 0 | Handle<> receiver) { |
1014 | | // 1. Assert: IsPropertyKey(P) is true. |
1015 | 0 | assert(isPropertyKey(nameValHandle) && "key is not a String or Symbol"); |
1016 | | // 8. Let booleanTrapResult be ToBoolean(? Call(trap, handler, « target, P, V, |
1017 | | // Receiver »)). |
1018 | 0 | CallResult<PseudoHandle<>> trapResultRes = Callable::executeCall4( |
1019 | 0 | trap, |
1020 | 0 | runtime, |
1021 | 0 | handler, |
1022 | 0 | target.getHermesValue(), |
1023 | 0 | nameValHandle.getHermesValue(), |
1024 | 0 | valueHandle.getHermesValue(), |
1025 | 0 | receiver.getHermesValue()); |
1026 | 0 | if (trapResultRes == ExecutionStatus::EXCEPTION) { |
1027 | 0 | return ExecutionStatus::EXCEPTION; |
1028 | 0 | } |
1029 | | // 9. If booleanTrapResult is false, return false. |
1030 | 0 | if (!toBoolean(trapResultRes->get())) { |
1031 | 0 | return false; |
1032 | 0 | } |
1033 | | // 10. Let targetDesc be ? target.[[GetOwnProperty]](P). |
1034 | 0 | ComputedPropertyDescriptor targetDesc; |
1035 | 0 | MutableHandle<> targetValueOrAccessor{runtime}; |
1036 | 0 | MutableHandle<SymbolID> tmpPropNameStorage{runtime}; |
1037 | 0 | CallResult<bool> targetDescRes = JSObject::getOwnComputedDescriptor( |
1038 | 0 | target, |
1039 | 0 | runtime, |
1040 | 0 | nameValHandle, |
1041 | 0 | tmpPropNameStorage, |
1042 | 0 | targetDesc, |
1043 | 0 | targetValueOrAccessor); |
1044 | 0 | if (targetDescRes == ExecutionStatus::EXCEPTION) { |
1045 | 0 | return ExecutionStatus::EXCEPTION; |
1046 | 0 | } |
1047 | | // 11. If targetDesc is not undefined and targetDesc.[[Configurable]] is |
1048 | | // false, then |
1049 | 0 | if (*targetDescRes && !targetDesc.flags.configurable) { |
1050 | | // a. If IsDataDescriptor(targetDesc) is true and targetDesc.[[Writable]] |
1051 | | // is false, then |
1052 | 0 | if (!targetDesc.flags.accessor && !targetDesc.flags.writable) { |
1053 | | // i. If SameValue(V, targetDesc.[[Value]]) is false, throw a |
1054 | | // TypeError exception. |
1055 | 0 | if (!isSameValue( |
1056 | 0 | valueHandle.getHermesValue(), |
1057 | 0 | targetValueOrAccessor.getHermesValue())) { |
1058 | 0 | return runtime.raiseTypeError( |
1059 | 0 | "target property is non-configurable and non-writable, and set trap value differs from target property value"); |
1060 | 0 | } |
1061 | 0 | } |
1062 | | // b. If IsAccessorDescriptor(targetDesc) is true, then |
1063 | | // i. If targetDesc.[[Set]] is undefined, throw a TypeError exception. |
1064 | 0 | if (targetDesc.flags.accessor && |
1065 | 0 | !vmcast<PropertyAccessor>(*targetValueOrAccessor)->setter) { |
1066 | 0 | return runtime.raiseTypeError( |
1067 | 0 | "set trap called, but target property is non-configurable accessor with no setter"); |
1068 | 0 | } |
1069 | 0 | } |
1070 | | |
1071 | | // 12. Return true. |
1072 | 0 | return true; |
1073 | 0 | } |
1074 | | |
1075 | | } // namespace |
1076 | | |
1077 | | CallResult<bool> JSProxy::setNamed( |
1078 | | Handle<JSObject> selfHandle, |
1079 | | Runtime &runtime, |
1080 | | SymbolID name, |
1081 | | Handle<> valueHandle, |
1082 | | // TODO could be HermesValue |
1083 | 0 | Handle<> receiver) { |
1084 | 0 | GCScope gcScope{runtime}; |
1085 | 0 | ScopedNativeDepthTracker depthTracker(runtime); |
1086 | 0 | if (LLVM_UNLIKELY(depthTracker.overflowed())) { |
1087 | 0 | return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); |
1088 | 0 | } |
1089 | | // Make sure to retrieve the target and handler before any JS can execute. |
1090 | 0 | auto &slots = detail::slots(*selfHandle); |
1091 | 0 | Handle<JSObject> target = runtime.makeHandle(slots.target); |
1092 | 0 | Handle<JSObject> handler = runtime.makeHandle(slots.handler); |
1093 | 0 | CallResult<Handle<Callable>> trapRes = |
1094 | 0 | detail::findTrap(selfHandle, runtime, Predefined::set); |
1095 | 0 | if (trapRes == ExecutionStatus::EXCEPTION) { |
1096 | 0 | return ExecutionStatus::EXCEPTION; |
1097 | 0 | } |
1098 | | // 7. If trap is undefined, then |
1099 | 0 | if (!*trapRes) { |
1100 | | // a. Return ? target.[[Set]](P, V, Receiver). |
1101 | 0 | return JSObject::putNamedWithReceiver_RJS( |
1102 | 0 | target, runtime, name, valueHandle, receiver); |
1103 | 0 | } |
1104 | 0 | return setWithTrap( |
1105 | 0 | runtime, |
1106 | 0 | name.isUniqued() ? runtime.makeHandle( |
1107 | 0 | HermesValue::encodeStringValue( |
1108 | 0 | runtime.getStringPrimFromSymbolID(name))) |
1109 | 0 | : runtime.makeHandle(name), |
1110 | 0 | valueHandle, |
1111 | 0 | *trapRes, |
1112 | 0 | handler, |
1113 | 0 | target, |
1114 | 0 | receiver); |
1115 | 0 | } |
1116 | | |
1117 | | CallResult<bool> JSProxy::setComputed( |
1118 | | Handle<JSObject> selfHandle, |
1119 | | Runtime &runtime, |
1120 | | Handle<> nameValHandle, |
1121 | | Handle<> valueHandle, |
1122 | | // TODO could be HermesValue |
1123 | 0 | Handle<> receiver) { |
1124 | 0 | GCScope gcScope{runtime}; |
1125 | 0 | ScopedNativeDepthTracker depthTracker(runtime); |
1126 | 0 | if (LLVM_UNLIKELY(depthTracker.overflowed())) { |
1127 | 0 | return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); |
1128 | 0 | } |
1129 | | // Make sure to retrieve the target and handler before any JS can execute. |
1130 | 0 | auto &slots = detail::slots(*selfHandle); |
1131 | 0 | Handle<JSObject> target = runtime.makeHandle(slots.target); |
1132 | 0 | Handle<JSObject> handler = runtime.makeHandle(slots.handler); |
1133 | 0 | CallResult<Handle<Callable>> trapRes = |
1134 | 0 | detail::findTrap(selfHandle, runtime, Predefined::set); |
1135 | 0 | if (trapRes == ExecutionStatus::EXCEPTION) { |
1136 | 0 | return ExecutionStatus::EXCEPTION; |
1137 | 0 | } |
1138 | | // 7. If trap is undefined, then |
1139 | 0 | if (!*trapRes) { |
1140 | | // a. Return ? target.[[Set]](P, V, Receiver). |
1141 | 0 | return JSObject::putComputedWithReceiver_RJS( |
1142 | 0 | target, runtime, nameValHandle, valueHandle, receiver); |
1143 | 0 | } |
1144 | 0 | return setWithTrap( |
1145 | 0 | runtime, nameValHandle, valueHandle, *trapRes, handler, target, receiver); |
1146 | 0 | } |
1147 | | |
1148 | | namespace { |
1149 | | |
1150 | | /// Common parts of deleteNamed/deleteComputed |
1151 | | CallResult<bool> deleteWithTrap( |
1152 | | Runtime &runtime, |
1153 | | Handle<> nameValHandle, |
1154 | | Handle<Callable> trap, |
1155 | | Handle<JSObject> handler, |
1156 | 0 | Handle<JSObject> target) { |
1157 | | // 1. Assert: IsPropertyKey(P) is true. |
1158 | 0 | assert(isPropertyKey(nameValHandle) && "key is not a String or Symbol"); |
1159 | | // 8. Let booleanTrapResult be ToBoolean(? Call(trap, handler, « target, P |
1160 | | // »)). |
1161 | 0 | CallResult<PseudoHandle<>> trapResultRes = Callable::executeCall2( |
1162 | 0 | trap, |
1163 | 0 | runtime, |
1164 | 0 | handler, |
1165 | 0 | target.getHermesValue(), |
1166 | 0 | nameValHandle.getHermesValue()); |
1167 | 0 | if (trapResultRes == ExecutionStatus::EXCEPTION) { |
1168 | 0 | return ExecutionStatus::EXCEPTION; |
1169 | 0 | } |
1170 | 0 | bool trapResult = toBoolean(trapResultRes->getHermesValue()); |
1171 | | // 9. If booleanTrapResult is false, return false. |
1172 | 0 | if (!trapResult) { |
1173 | 0 | return false; |
1174 | 0 | } |
1175 | | // 10. Let targetDesc be ? target.[[GetOwnProperty]](P). |
1176 | 0 | ComputedPropertyDescriptor targetDesc; |
1177 | 0 | MutableHandle<> targetValueOrAccessor{runtime}; |
1178 | 0 | MutableHandle<SymbolID> tmpPropNameStorage{runtime}; |
1179 | 0 | CallResult<bool> targetDescRes = JSObject::getOwnComputedDescriptor( |
1180 | 0 | target, |
1181 | 0 | runtime, |
1182 | 0 | nameValHandle, |
1183 | 0 | tmpPropNameStorage, |
1184 | 0 | targetDesc, |
1185 | 0 | targetValueOrAccessor); |
1186 | 0 | if (targetDescRes == ExecutionStatus::EXCEPTION) { |
1187 | 0 | return ExecutionStatus::EXCEPTION; |
1188 | 0 | } |
1189 | | // 11. If targetDesc is undefined, return true. |
1190 | 0 | if (!*targetDescRes) { |
1191 | 0 | return true; |
1192 | 0 | } |
1193 | | // 12. If targetDesc.[[Configurable]] is false, throw a TypeError exception. |
1194 | 0 | if (!targetDesc.flags.configurable) { |
1195 | 0 | return runtime.raiseTypeError( |
1196 | 0 | "Delete trap target called, but target property is non-configurable"); |
1197 | 0 | } |
1198 | | // 13. Return true. |
1199 | 0 | return true; |
1200 | 0 | } |
1201 | | |
1202 | | } // namespace |
1203 | | |
1204 | | CallResult<bool> JSProxy::deleteNamed( |
1205 | | Handle<JSObject> selfHandle, |
1206 | | Runtime &runtime, |
1207 | 0 | SymbolID name) { |
1208 | 0 | GCScope gcScope{runtime}; |
1209 | 0 | ScopedNativeDepthTracker depthTracker(runtime); |
1210 | 0 | if (LLVM_UNLIKELY(depthTracker.overflowed())) { |
1211 | 0 | return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); |
1212 | 0 | } |
1213 | | // Make sure to retrieve the target and handler before any JS can execute. |
1214 | 0 | auto &slots = detail::slots(*selfHandle); |
1215 | 0 | Handle<JSObject> target = runtime.makeHandle(slots.target); |
1216 | 0 | Handle<JSObject> handler = runtime.makeHandle(slots.handler); |
1217 | 0 | CallResult<Handle<Callable>> trapRes = |
1218 | 0 | detail::findTrap(selfHandle, runtime, Predefined::deleteProperty); |
1219 | 0 | if (trapRes == ExecutionStatus::EXCEPTION) { |
1220 | 0 | return ExecutionStatus::EXCEPTION; |
1221 | 0 | } |
1222 | | // 7. If trap is undefined, then |
1223 | 0 | if (!*trapRes) { |
1224 | | // a. Return ? target.[[Delete]](P, Receiver). |
1225 | 0 | return JSObject::deleteNamed(target, runtime, name); |
1226 | 0 | } |
1227 | 0 | return deleteWithTrap( |
1228 | 0 | runtime, |
1229 | 0 | runtime.makeHandle( |
1230 | 0 | HermesValue::encodeStringValue( |
1231 | 0 | runtime.getStringPrimFromSymbolID(name))), |
1232 | 0 | *trapRes, |
1233 | 0 | handler, |
1234 | 0 | target); |
1235 | 0 | } |
1236 | | |
1237 | | CallResult<bool> JSProxy::deleteComputed( |
1238 | | Handle<JSObject> selfHandle, |
1239 | | Runtime &runtime, |
1240 | 0 | Handle<> nameValHandle) { |
1241 | 0 | GCScope gcScope{runtime}; |
1242 | 0 | ScopedNativeDepthTracker depthTracker(runtime); |
1243 | 0 | if (LLVM_UNLIKELY(depthTracker.overflowed())) { |
1244 | 0 | return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); |
1245 | 0 | } |
1246 | | // Make sure to retrieve the target and handler before any JS can execute. |
1247 | 0 | auto &slots = detail::slots(*selfHandle); |
1248 | 0 | Handle<JSObject> target = runtime.makeHandle(slots.target); |
1249 | 0 | Handle<JSObject> handler = runtime.makeHandle(slots.handler); |
1250 | 0 | CallResult<Handle<Callable>> trapRes = |
1251 | 0 | detail::findTrap(selfHandle, runtime, Predefined::deleteProperty); |
1252 | 0 | if (trapRes == ExecutionStatus::EXCEPTION) { |
1253 | 0 | return ExecutionStatus::EXCEPTION; |
1254 | 0 | } |
1255 | | // 7. If trap is undefined, then |
1256 | 0 | if (!*trapRes) { |
1257 | | // a. Return ? target.[[Delete]](P, Receiver). |
1258 | 0 | return JSObject::deleteComputed(target, runtime, nameValHandle); |
1259 | 0 | } |
1260 | 0 | return deleteWithTrap(runtime, nameValHandle, *trapRes, handler, target); |
1261 | 0 | } |
1262 | | |
1263 | | namespace { |
1264 | | |
1265 | | CallResult<PseudoHandle<JSArray>> filterKeys( |
1266 | | Handle<JSObject> selfHandle, |
1267 | | Handle<JSArray> keys, |
1268 | | Runtime &runtime, |
1269 | 0 | OwnKeysFlags okFlags) { |
1270 | 0 | assert( |
1271 | 0 | (okFlags.getIncludeNonSymbols() || okFlags.getIncludeSymbols()) && |
1272 | 0 | "Can't exclude symbols and strings"); |
1273 | | // If nothing is excluded, just return the array as-is. |
1274 | 0 | if (okFlags.getIncludeNonSymbols() && okFlags.getIncludeSymbols() && |
1275 | 0 | okFlags.getIncludeNonEnumerable()) { |
1276 | 0 | return createPseudoHandle(*keys); |
1277 | 0 | } |
1278 | | // Count number of matching elements by type. |
1279 | 0 | assert( |
1280 | 0 | ((okFlags.getIncludeSymbols() ? 0 : 1) + |
1281 | 0 | (okFlags.getIncludeNonSymbols() ? 0 : 1)) == 1 && |
1282 | 0 | "Exactly one of Symbols or non-Symbols is included here"); |
1283 | 0 | bool onlySymbols = okFlags.getIncludeSymbols(); |
1284 | 0 | uint32_t len = JSArray::getLength(*keys, runtime); |
1285 | 0 | uint32_t count = 0; |
1286 | | // Verify this loop is alloc-free |
1287 | 0 | { |
1288 | 0 | NoAllocScope noAlloc(runtime); |
1289 | 0 | for (uint32_t i = 0; i < len; ++i) { |
1290 | 0 | if (keys->at(runtime, i).isSymbol() == onlySymbols) { |
1291 | 0 | ++count; |
1292 | 0 | } |
1293 | 0 | } |
1294 | 0 | } |
1295 | | // If everything in the array matches the filter by type, return |
1296 | | // the list as-is. |
1297 | 0 | if (len == count && okFlags.getIncludeNonEnumerable()) { |
1298 | 0 | return createPseudoHandle(*keys); |
1299 | 0 | } |
1300 | | // Filter the desired elements we want into the result |
1301 | 0 | auto resultRes = JSArray::create(runtime, count, count); |
1302 | 0 | if (LLVM_UNLIKELY(resultRes == ExecutionStatus::EXCEPTION)) { |
1303 | 0 | return ExecutionStatus::EXCEPTION; |
1304 | 0 | } |
1305 | 0 | Handle<JSArray> resultHandle = *resultRes; |
1306 | 0 | MutableHandle<> elemHandle{runtime}; |
1307 | 0 | uint32_t resultIndex = 0; |
1308 | 0 | GCScopeMarkerRAII marker{runtime}; |
1309 | 0 | for (uint32_t i = 0; i < len; ++i) { |
1310 | 0 | marker.flush(); |
1311 | 0 | SmallHermesValue elem = keys->at(runtime, i); |
1312 | 0 | if (elem.isSymbol() ? !okFlags.getIncludeSymbols() |
1313 | 0 | : !okFlags.getIncludeNonSymbols()) { |
1314 | 0 | continue; |
1315 | 0 | } |
1316 | 0 | elemHandle = elem.unboxToHV(runtime); |
1317 | 0 | if (!okFlags.getIncludeNonEnumerable()) { |
1318 | 0 | ComputedPropertyDescriptor desc; |
1319 | 0 | CallResult<bool> propRes = JSProxy::getOwnProperty( |
1320 | 0 | selfHandle, runtime, elemHandle, desc, nullptr); |
1321 | 0 | if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { |
1322 | 0 | return ExecutionStatus::EXCEPTION; |
1323 | 0 | } |
1324 | 0 | if (!*propRes || !desc.flags.enumerable) { |
1325 | 0 | continue; |
1326 | 0 | } |
1327 | 0 | } |
1328 | 0 | JSArray::setElementAt(resultHandle, runtime, resultIndex++, elemHandle); |
1329 | 0 | } |
1330 | 0 | assert( |
1331 | 0 | (!okFlags.getIncludeNonEnumerable() || resultIndex == count) && |
1332 | 0 | "Expected count was not correct"); |
1333 | 0 | CallResult<bool> setLenRes = |
1334 | 0 | JSArray::setLengthProperty(resultHandle, runtime, resultIndex); |
1335 | 0 | if (LLVM_UNLIKELY(setLenRes == ExecutionStatus::EXCEPTION)) { |
1336 | 0 | return ExecutionStatus::EXCEPTION; |
1337 | 0 | } |
1338 | 0 | return createPseudoHandle(*resultHandle); |
1339 | 0 | } |
1340 | | |
1341 | | } // namespace |
1342 | | |
1343 | | CallResult<PseudoHandle<JSArray>> JSProxy::ownPropertyKeys( |
1344 | | Handle<JSObject> selfHandle, |
1345 | | Runtime &runtime, |
1346 | 0 | OwnKeysFlags okFlags) { |
1347 | 0 | GCScope gcScope{runtime}; |
1348 | 0 | ScopedNativeDepthTracker depthTracker(runtime); |
1349 | 0 | if (LLVM_UNLIKELY(depthTracker.overflowed())) { |
1350 | 0 | return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); |
1351 | 0 | } |
1352 | | // Make sure to retrieve the target and handler before any JS can execute. |
1353 | 0 | auto &slots = detail::slots(*selfHandle); |
1354 | 0 | Handle<JSObject> target = runtime.makeHandle(slots.target); |
1355 | 0 | Handle<JSObject> handler = runtime.makeHandle(slots.handler); |
1356 | 0 | CallResult<Handle<Callable>> trapRes = |
1357 | 0 | detail::findTrap(selfHandle, runtime, Predefined::ownKeys); |
1358 | 0 | if (trapRes == ExecutionStatus::EXCEPTION) { |
1359 | 0 | return ExecutionStatus::EXCEPTION; |
1360 | 0 | } |
1361 | | // 6. If trap is undefined, then |
1362 | 0 | if (!*trapRes) { |
1363 | | // a. Return ? target.[[OwnPropertyKeys]](). |
1364 | 0 | CallResult<Handle<JSArray>> targetRes = |
1365 | | // Include everything here, so that filterKeys has a chance to |
1366 | | // make observable trap calls. |
1367 | 0 | JSObject::getOwnPropertyKeys( |
1368 | 0 | target, |
1369 | 0 | runtime, |
1370 | 0 | OwnKeysFlags() |
1371 | 0 | .plusIncludeSymbols() |
1372 | 0 | .plusIncludeNonSymbols() |
1373 | 0 | .plusIncludeNonEnumerable()); |
1374 | 0 | if (targetRes == ExecutionStatus::EXCEPTION) { |
1375 | 0 | return ExecutionStatus::EXCEPTION; |
1376 | 0 | } |
1377 | 0 | return filterKeys(selfHandle, *targetRes, runtime, okFlags); |
1378 | 0 | } |
1379 | | // 7. Let trapResultArray be ? Call(trap, handler, « target »). |
1380 | 0 | CallResult<PseudoHandle<>> trapResultArrayRes = Callable::executeCall1( |
1381 | 0 | *trapRes, runtime, handler, target.getHermesValue()); |
1382 | 0 | if (trapResultArrayRes == ExecutionStatus::EXCEPTION) { |
1383 | 0 | return ExecutionStatus::EXCEPTION; |
1384 | 0 | } |
1385 | 0 | if (!vmisa<JSObject>(trapResultArrayRes->get())) { |
1386 | 0 | return runtime.raiseTypeErrorForValue( |
1387 | 0 | runtime.makeHandle(std::move(*trapResultArrayRes)), |
1388 | 0 | " ownKeys trap result is not an Object"); |
1389 | 0 | } |
1390 | 0 | auto trapResultArray = |
1391 | 0 | runtime.makeHandle<JSObject>(trapResultArrayRes->get()); |
1392 | | // 8. Let trapResult be ? CreateListFromArrayLike(trapResultArray, « String, |
1393 | | // Symbol ») |
1394 | | // 9. If trapResult contains any duplicate entries, throw a TypeError |
1395 | | // exception. |
1396 | 0 | CallResult<uint64_t> countRes = |
1397 | 0 | getArrayLikeLength_RJS(trapResultArray, runtime); |
1398 | 0 | if (LLVM_UNLIKELY(countRes == ExecutionStatus::EXCEPTION)) { |
1399 | 0 | return ExecutionStatus::EXCEPTION; |
1400 | 0 | } |
1401 | 0 | if (*countRes > UINT32_MAX) { |
1402 | 0 | return runtime.raiseRangeError( |
1403 | 0 | "Too many elements returned from ownKeys trap"); |
1404 | 0 | } |
1405 | 0 | uint32_t count = static_cast<uint32_t>(*countRes); |
1406 | 0 | auto trapResultRes = JSArray::create(runtime, count, count); |
1407 | 0 | if (LLVM_UNLIKELY(trapResultRes == ExecutionStatus::EXCEPTION)) { |
1408 | 0 | return ExecutionStatus::EXCEPTION; |
1409 | 0 | } |
1410 | 0 | Handle<JSArray> trapResult = *trapResultRes; |
1411 | 0 | CallResult<PseudoHandle<OrderedHashMap>> dupcheckRes = |
1412 | 0 | OrderedHashMap::create(runtime); |
1413 | 0 | if (LLVM_UNLIKELY(dupcheckRes == ExecutionStatus::EXCEPTION)) { |
1414 | 0 | return ExecutionStatus::EXCEPTION; |
1415 | 0 | } |
1416 | 0 | Handle<OrderedHashMap> dupcheck = runtime.makeHandle(std::move(*dupcheckRes)); |
1417 | 0 | if (LLVM_UNLIKELY( |
1418 | 0 | createListFromArrayLike_RJS( |
1419 | 0 | trapResultArray, |
1420 | 0 | runtime, |
1421 | 0 | count, |
1422 | 0 | [&dupcheck, &trapResult]( |
1423 | 0 | Runtime &runtime, uint64_t index, PseudoHandle<> value) { |
1424 | 0 | Handle<> valHandle = runtime.makeHandle(std::move(value)); |
1425 | 0 | if (!valHandle->isString() && !valHandle->isSymbol()) { |
1426 | 0 | return runtime.raiseTypeErrorForValue( |
1427 | 0 | valHandle, |
1428 | 0 | " ownKeys trap result element is not String or Symbol"); |
1429 | 0 | } |
1430 | 0 | if (OrderedHashMap::has(dupcheck, runtime, valHandle)) { |
1431 | 0 | return runtime.raiseTypeErrorForValue( |
1432 | 0 | "ownKeys trap result has duplicate ", valHandle, ""); |
1433 | 0 | } |
1434 | 0 | if (LLVM_UNLIKELY( |
1435 | 0 | OrderedHashMap::insert( |
1436 | 0 | dupcheck, runtime, valHandle, valHandle) == |
1437 | 0 | ExecutionStatus::EXCEPTION)) |
1438 | 0 | return ExecutionStatus::RETURNED; |
1439 | 0 | JSArray::setElementAt(trapResult, runtime, index, valHandle); |
1440 | 0 | return ExecutionStatus::RETURNED; |
1441 | 0 | }) == ExecutionStatus::EXCEPTION)) { |
1442 | 0 | return ExecutionStatus::EXCEPTION; |
1443 | 0 | } |
1444 | | |
1445 | | // 10. Let extensibleTarget be ? IsExtensible(target). |
1446 | 0 | CallResult<bool> extensibleRes = JSObject::isExtensible(target, runtime); |
1447 | 0 | if (extensibleRes == ExecutionStatus::EXCEPTION) { |
1448 | 0 | return ExecutionStatus::EXCEPTION; |
1449 | 0 | } |
1450 | | // 11. Let targetKeys be ? target.[[OwnPropertyKeys]](). |
1451 | 0 | CallResult<Handle<JSArray>> targetKeysRes = JSObject::getOwnPropertyKeys( |
1452 | 0 | target, |
1453 | 0 | runtime, |
1454 | 0 | OwnKeysFlags() |
1455 | 0 | .plusIncludeSymbols() |
1456 | 0 | .plusIncludeNonSymbols() |
1457 | 0 | .plusIncludeNonEnumerable()); |
1458 | 0 | if (targetKeysRes == ExecutionStatus::EXCEPTION) { |
1459 | 0 | return ExecutionStatus::EXCEPTION; |
1460 | 0 | } |
1461 | 0 | Handle<JSArray> targetKeys = *targetKeysRes; |
1462 | | // 12. Assert: targetKeys is a List containing only String and Symbol values. |
1463 | | // 13. Assert: targetKeys contains no duplicate entries. |
1464 | | // 14. Let targetConfigurableKeys be a new empty List. |
1465 | | // 15. Let targetNonconfigurableKeys be a new empty List. |
1466 | 0 | llvh::SmallSet<uint32_t, 8> nonConfigurable; |
1467 | 0 | MutableHandle<SymbolID> tmpPropNameStorage{runtime}; |
1468 | | // 16. For each element key of targetKeys, do |
1469 | 0 | GCScopeMarkerRAII marker{runtime}; |
1470 | 0 | for (uint32_t i = 0, len = JSArray::getLength(*targetKeys, runtime); i < len; |
1471 | 0 | ++i) { |
1472 | 0 | marker.flush(); |
1473 | | // a. Let desc be ? target.[[GetOwnProperty]](key). |
1474 | 0 | ComputedPropertyDescriptor desc; |
1475 | 0 | CallResult<bool> descRes = JSObject::getOwnComputedDescriptor( |
1476 | 0 | target, |
1477 | 0 | runtime, |
1478 | 0 | runtime.makeHandle(targetKeys->at(runtime, i).unboxToHV(runtime)), |
1479 | 0 | tmpPropNameStorage, |
1480 | 0 | desc); |
1481 | 0 | if (descRes == ExecutionStatus::EXCEPTION) { |
1482 | 0 | return ExecutionStatus::EXCEPTION; |
1483 | 0 | } |
1484 | | // b. If desc is not undefined and desc.[[Configurable]] is false, then |
1485 | | // i. Append key as an element of targetNonconfigurableKeys. |
1486 | | // c. Else, |
1487 | | // i. Append key as an element of targetConfigurableKeys. |
1488 | 0 | if (*descRes && !desc.flags.configurable) { |
1489 | 0 | nonConfigurable.insert(i); |
1490 | 0 | } |
1491 | 0 | } |
1492 | | // 17. If extensibleTarget is true and targetNonconfigurableKeys is empty, |
1493 | | // then |
1494 | 0 | if (*extensibleRes && nonConfigurable.empty()) { |
1495 | | // a. Return trapResult. |
1496 | 0 | return filterKeys(selfHandle, trapResult, runtime, okFlags); |
1497 | 0 | } |
1498 | | // 18. Let uncheckedResultKeys be a new List which is a copy of trapResult. |
1499 | | // 19. For each key that is an element of targetNonconfigurableKeys, do |
1500 | | // a. If key is not an element of uncheckedResultKeys, throw a TypeError |
1501 | | // exception. b. Remove key from uncheckedResultKeys. |
1502 | 0 | auto inTrapResult = [&runtime, &trapResult](HermesValue value) { |
1503 | 0 | for (uint32_t j = 0, len = JSArray::getLength(*trapResult, runtime); |
1504 | 0 | j < len; |
1505 | 0 | ++j) { |
1506 | 0 | if (isSameValue(value, trapResult->at(runtime, j).unboxToHV(runtime))) { |
1507 | 0 | return true; |
1508 | 0 | } |
1509 | 0 | } |
1510 | 0 | return false; |
1511 | 0 | }; |
1512 | 0 | for (auto i : nonConfigurable) { |
1513 | 0 | if (!inTrapResult(targetKeys->at(runtime, i).unboxToHV(runtime))) { |
1514 | 0 | return runtime.raiseTypeError( |
1515 | 0 | "ownKeys target key is non-configurable but not present in trap result"); |
1516 | 0 | } |
1517 | 0 | } |
1518 | | // 20. If extensibleTarget is true, return trapResult. |
1519 | 0 | if (*extensibleRes) { |
1520 | 0 | return filterKeys(selfHandle, trapResult, runtime, okFlags); |
1521 | 0 | } |
1522 | | // 21. For each key that is an element of targetConfigurableKeys, do |
1523 | | // a. If key is not an element of uncheckedResultKeys, throw a TypeError |
1524 | | // exception. b. Remove key from uncheckedResultKeys. |
1525 | 0 | for (uint32_t i = 0, len = JSArray::getLength(*targetKeys, runtime); i < len; |
1526 | 0 | ++i) { |
1527 | 0 | if (nonConfigurable.count(i) > 0) { |
1528 | 0 | continue; |
1529 | 0 | } |
1530 | 0 | if (!inTrapResult(targetKeys->at(runtime, i).unboxToHV(runtime))) { |
1531 | 0 | return runtime.raiseTypeError( |
1532 | 0 | "ownKeys target is non-extensible but key is missing from trap result"); |
1533 | 0 | } |
1534 | 0 | } |
1535 | | // 22. If uncheckedResultKeys is not empty, throw a TypeError exception. |
1536 | 0 | if (JSArray::getLength(*targetKeys, runtime) != |
1537 | 0 | JSArray::getLength(*trapResult, runtime)) { |
1538 | 0 | return runtime.raiseTypeError( |
1539 | 0 | "ownKeys target is non-extensible but trap result keys differ from target keys"); |
1540 | 0 | } |
1541 | | // 23. Return trapResult. |
1542 | 0 | return filterKeys(selfHandle, trapResult, runtime, okFlags); |
1543 | 0 | } |
1544 | | |
1545 | | } // namespace vm |
1546 | | } // namespace hermes |