lib/VM/JSProxy.cpp (1,234 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include "hermes/VM/JSProxy.h" #include "hermes/VM/ArrayLike.h" #include "hermes/VM/Callable.h" #include "hermes/VM/JSArray.h" #include "hermes/VM/JSCallableProxy.h" #include "hermes/VM/OrderedHashMap.h" #include "hermes/VM/PropertyAccessor.h" #include "llvh/ADT/SmallSet.h" namespace hermes { namespace vm { namespace detail { ProxySlots &slots(JSObject *self) { if (auto *proxy = dyn_vmcast<JSProxy>(self)) { return proxy->slots_; } else { auto *cproxy = dyn_vmcast<JSCallableProxy>(self); assert( cproxy && "JSProxy methods must be passed JSProxy or JSCallableProxy"); return cproxy->slots_; } } CallResult<Handle<Callable>> findTrap(Handle<JSObject> selfHandle, Runtime &runtime, Predefined::Str name) { // 2. Let handler be O.[[ProxyHandler]]. // 3. If handler is null, throw a TypeError exception. JSObject *handlerPtr = detail::slots(*selfHandle).handler.get(runtime); if (!handlerPtr) { return runtime.raiseTypeError("Proxy handler is null"); } // 4. Assert: Type(handler) is Object. // 5. Let target be O.[[ProxyTarget]]. // 6. Let trap be ? GetMethod(handler, « name »). CallResult<PseudoHandle<>> trapVal = [&]() { GCScope gcScope(runtime); Handle<JSObject> handler = runtime.makeHandle(handlerPtr); return JSObject::getNamed_RJS( handler, runtime, Predefined::getSymbolID(name)); }(); if (trapVal == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } if ((*trapVal)->isUndefined() || (*trapVal)->isNull()) { return Runtime::makeNullHandle<Callable>(); } if (!vmisa<Callable>(trapVal->get())) { return runtime.raiseTypeErrorForValue( runtime.makeHandle(std::move(*trapVal)), " is not a Proxy trap function"); } return runtime.makeHandle<Callable>(std::move(trapVal->get())); } } // namespace detail //===----------------------------------------------------------------------===// // class JSProxy const ObjectVTable JSProxy::vt{ VTable(CellKind::JSProxyKind, cellSize<JSProxy>()), JSProxy::_getOwnIndexedRangeImpl, JSProxy::_haveOwnIndexedImpl, JSProxy::_getOwnIndexedPropertyFlagsImpl, JSProxy::_getOwnIndexedImpl, JSProxy::_setOwnIndexedImpl, JSProxy::_deleteOwnIndexedImpl, JSProxy::_checkAllOwnIndexedImpl, }; void JSProxyBuildMeta(const GCCell *cell, Metadata::Builder &mb) { mb.addJSObjectOverlapSlots(JSObject::numOverlapSlots<JSProxy>()); JSObjectBuildMeta(cell, mb); const auto *self = static_cast<const JSProxy *>(cell); mb.setVTable(&JSProxy::vt); mb.addField("@target", &self->slots_.target); mb.addField("@handler", &self->slots_.handler); } PseudoHandle<JSProxy> JSProxy::create(Runtime &runtime) { JSProxy *proxy = runtime.makeAFixed<JSProxy>( runtime, Handle<JSObject>::vmcast(&runtime.objectPrototype), runtime.getHiddenClassForPrototype( runtime.objectPrototypeRawPtr, JSObject::numOverlapSlots<JSProxy>())); proxy->flags_.proxyObject = true; return JSObjectInit::initToPseudoHandle(runtime, proxy); } void JSProxy::setTargetAndHandler( Handle<JSObject> selfHandle, Runtime &runtime, Handle<JSObject> target, Handle<JSObject> handler) { auto &slots = detail::slots(*selfHandle); slots.target.set(runtime, target.get(), &runtime.getHeap()); slots.handler.set(runtime, handler.get(), &runtime.getHeap()); } namespace { void completePropertyDescriptor(DefinePropertyFlags &desc) { if ((desc.setValue || desc.setWritable) || (!desc.setGetter && !desc.setSetter)) { if (!desc.setWritable) { desc.writable = false; } } if (!desc.setEnumerable) { desc.enumerable = false; } if (!desc.setConfigurable) { desc.configurable = false; } } // ES9 9.1.6.2 IsCompatiblePropertyDescriptor // prereq: step 2 is already done externally. // The abstract definition returns a boolean; this returns EXCEPTION // (and sets an exception) or RETURNED instead of false or true, so // the exception messages can be more specific. ExecutionStatus isCompatiblePropertyDescriptor( Runtime &runtime, const DefinePropertyFlags &desc, Handle<> descValueOrAccessor, const ComputedPropertyDescriptor &current, Handle<> currentValueOrAccessor) { // 4. If current.[[Configurable]] is false, then if (!current.flags.configurable) { // a. If Desc.[[Configurable]] is present and its value is true, return // false. if (desc.setConfigurable && desc.configurable) { return runtime.raiseTypeError( "trap result is configurable but target property is non-configurable"); } // b. If Desc.[[Enumerable]] is present and the [[Enumerable]] // fields of current and Desc are the Boolean negation of each // other, return false. if (desc.setEnumerable && desc.enumerable != current.flags.enumerable) { return runtime.raiseTypeError( TwineChar16("trap result is ") + (desc.enumerable ? "" : "not ") + "enumerable but target property is " + (current.flags.enumerable ? "" : "not ") + "enumerable"); } } // 5. If IsGenericDescriptor(Desc) is true, no further validation is required. bool descIsAccessor = desc.setSetter || desc.setGetter; bool descIsData = desc.setValue || desc.setWritable; assert( (!descIsData || !descIsAccessor) && "descriptor cannot be both Data and Accessor"); if (!descIsData && !descIsAccessor) { return ExecutionStatus::RETURNED; } // 6. Else if IsDataDescriptor(current) and IsDataDescriptor(Desc) // have different results, then bool currentIsAccessor = current.flags.accessor; bool currentIsData = !currentIsAccessor; if (currentIsData != descIsData) { // a. If current.[[Configurable]] is false, return false. if (!current.flags.configurable) { return runtime.raiseTypeError( TwineChar16("trap result is ") + (currentIsData ? "data " : "accessor ") + "but target property is " + (descIsData ? "data " : "accessor ") + "and non-configurable"); } } // 7. Else if IsDataDescriptor(current) and IsDataDescriptor(Desc) are both // true, then // a. If current.[[Configurable]] is false and current.[[Writable]] is // false, then if (currentIsData && descIsData && !current.flags.configurable && !current.flags.writable) { // i. If Desc.[[Writable]] is present and Desc.[[Writable]] is true, // return false. if (desc.setWritable && desc.writable) { return runtime.raiseTypeError( "trap result is writable but " "target property is non-configurable and non-writable"); } // ii. If Desc.[[Value]] is present and SameValue(Desc.[[Value]], // current.[[Value]]) is false, return false. if (desc.setValue && !isSameValue( descValueOrAccessor.getHermesValue(), currentValueOrAccessor.getHermesValue())) { return runtime.raiseTypeError( "trap result has different value than target property but " "target property is non-configurable and non-writable"); } // iii. Return true. return ExecutionStatus::RETURNED; } // 8. Else IsAccessorDescriptor(current) and IsAccessorDescriptor(Desc) are // both true, // a. If current.[[Configurable]] is false, then if (currentIsAccessor && descIsAccessor && !current.flags.configurable) { PropertyAccessor *descAccessor = vmcast<PropertyAccessor>(descValueOrAccessor.get()); PropertyAccessor *currentAccessor = vmcast<PropertyAccessor>(currentValueOrAccessor.get()); // i. If Desc.[[Set]] is present and SameValue(Desc.[[Set]], // current.[[Set]]) is false, return false. if (descAccessor->setter && descAccessor->setter != currentAccessor->setter) { return runtime.raiseTypeError( "trap result has different setter than target property but " "target property is non-configurable"); } // ii. If Desc.[[Get]] is present and SameValue(Desc.[[Get]], // current.[[Get]]) is false, return false. if (descAccessor->getter && descAccessor->getter != currentAccessor->getter) { return runtime.raiseTypeError( "trap result has different getter than target property but " "target property is non-configurable"); } // iii. Return true. } return ExecutionStatus::RETURNED; } } // namespace CallResult<PseudoHandle<JSObject>> JSProxy::getPrototypeOf( Handle<JSObject> selfHandle, Runtime &runtime) { // Proxies are complex, and various parts of the logic (finding // traps, undefined traps handling, calling traps, etc) are all // potentially recursive. Therefore, every entry point creates a // scope and a ScopedNativeDepthTracker, as it's possible to use up // arbitrary native stack depth with nested proxies. GCScope gcScope(runtime); ScopedNativeDepthTracker depthTracker(runtime); if (LLVM_UNLIKELY(depthTracker.overflowed())) { return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); } Handle<JSObject> target = runtime.makeHandle(detail::slots(*selfHandle).target); CallResult<Handle<Callable>> trapRes = detail::findTrap(selfHandle, runtime, Predefined::getPrototypeOf); if (LLVM_UNLIKELY(trapRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // 6. If trap is undefined, then if (!*trapRes) { // a. Return ? target.[[GetPrototypeOf]](P). return JSObject::getPrototypeOf(target, runtime); } // 7. Let handlerProto be ? Call(trap, handler, « target »). CallResult<PseudoHandle<>> handlerProtoRes = Callable::executeCall1( *trapRes, runtime, runtime.makeHandle(detail::slots(*selfHandle).handler), target.getHermesValue()); if (handlerProtoRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 8. If Type(handlerProto) is neither Object nor Null, throw a TypeError // exception. if (!(*handlerProtoRes)->isObject() && !(*handlerProtoRes)->isNull()) { return runtime.raiseTypeError( "getPrototypeOf trap result is neither Object nor Null"); } Handle<JSObject> handlerProto = runtime.makeHandle(dyn_vmcast<JSObject>(handlerProtoRes->get())); // 9. Let extensibleTarget be ? IsExtensible(target). CallResult<bool> extensibleRes = JSObject::isExtensible(target, runtime); if (extensibleRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 10. If extensibleTarget is true, return handlerProto. if (*extensibleRes) { return createPseudoHandle(*handlerProto); } // 11. Let targetProto be ? target.[[GetPrototypeOf]](). CallResult<PseudoHandle<JSObject>> targetProtoRes = JSObject::getPrototypeOf(target, runtime); if (targetProtoRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 12. If SameValue(handlerProto, targetProto) is false, throw a TypeError // exception. if (handlerProto.get() != targetProtoRes->get()) { return runtime.raiseTypeError( "getPrototypeOf trap result is not the same as non-extensible target getPrototypeOf"); } // 13. Return handlerProto. return std::move(*targetProtoRes); } CallResult<bool> JSProxy::setPrototypeOf( Handle<JSObject> selfHandle, Runtime &runtime, Handle<JSObject> parent) { GCScope gcScope{runtime}; ScopedNativeDepthTracker depthTracker(runtime); if (LLVM_UNLIKELY(depthTracker.overflowed())) { return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); } Handle<JSObject> target = runtime.makeHandle(detail::slots(*selfHandle).target); CallResult<Handle<Callable>> trapRes = detail::findTrap(selfHandle, runtime, Predefined::setPrototypeOf); if (trapRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 7. If trap is undefined, then if (!*trapRes) { // a. Return ? target.[[SetPrototypeOf]](V). return JSObject::setParent(*target, runtime, *parent); } // 8. Let booleanTrapResult be ToBoolean(? Call(trap, handler, « target, V // »)). CallResult<PseudoHandle<>> booleanTrapRes = Callable::executeCall2( *trapRes, runtime, runtime.makeHandle(detail::slots(*selfHandle).handler), target.getHermesValue(), *parent ? parent.getHermesValue() : HermesValue::encodeNullValue()); if (booleanTrapRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 9. If booleanTrapResult is false, return false. if (!toBoolean(booleanTrapRes->get())) { return false; } // 10. Let extensibleTarget be ? IsExtensible(target). CallResult<bool> extensibleRes = JSObject::isExtensible(target, runtime); if (extensibleRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 11. If extensibleTarget is true, return true. if (*extensibleRes) { return true; } // 12. Let targetProto be ? target.[[GetPrototypeOf]](). CallResult<PseudoHandle<JSObject>> targetProtoRes = JSObject::getPrototypeOf(target, runtime); if (targetProtoRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 13. If SameValue(V, targetProto) is false, throw a TypeError exception. if (parent.get() != targetProtoRes->get()) { return runtime.raiseTypeError( "setPrototypeOf trap changed prototype on non-extensible target"); } // 14. Return true. return true; } CallResult<bool> JSProxy::isExtensible( Handle<JSObject> selfHandle, Runtime &runtime) { GCScope gcScope{runtime}; ScopedNativeDepthTracker depthTracker(runtime); if (LLVM_UNLIKELY(depthTracker.overflowed())) { return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); } Handle<JSObject> target = runtime.makeHandle(detail::slots(*selfHandle).target); CallResult<Handle<Callable>> trapRes = detail::findTrap(selfHandle, runtime, Predefined::isExtensible); if (trapRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 6. If trap is undefined, then if (!*trapRes) { // a. Return ? target.[[IsExtensible]](). return JSObject::isExtensible(target, runtime); } // 7. Let booleanTrapResult be ToBoolean(? Call(trap, handler, « target »)). CallResult<PseudoHandle<>> res = Callable::executeCall1( *trapRes, runtime, runtime.makeHandle(detail::slots(*selfHandle).handler), target.getHermesValue()); if (res == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 8. Let targetResult be ? target.[[IsExtensible]](). CallResult<bool> targetRes = JSObject::isExtensible(target, runtime); if (targetRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 9. If SameValue(booleanTrapResult, targetResult) is false, throw // a TypeError exception. bool booleanTrapResult = toBoolean(res->get()); if (booleanTrapResult != *targetRes) { return runtime.raiseTypeError( "isExtensible trap returned different value than target"); } // 10. Return booleanTrapResult. return booleanTrapResult; } CallResult<bool> JSProxy::preventExtensions( Handle<JSObject> selfHandle, Runtime &runtime, PropOpFlags opFlags) { GCScope gcScope{runtime}; ScopedNativeDepthTracker depthTracker(runtime); if (LLVM_UNLIKELY(depthTracker.overflowed())) { return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); } Handle<JSObject> target = runtime.makeHandle(detail::slots(*selfHandle).target); CallResult<Handle<Callable>> trapRes = detail::findTrap(selfHandle, runtime, Predefined::preventExtensions); if (trapRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 6. If trap is undefined, then if (!*trapRes) { // a. Return ? target.[[PreventExtensions]](). // We pass in opFlags here. If getThrowOnError, then this will cause // the underlying exception to bubble up. If !getThrowOnError, then // we don't get a chance to raise a particular exception anyway. So in // either case, just return the CallResult. return JSObject::preventExtensions(target, runtime, opFlags); } // 7. Let booleanTrapResult be ToBoolean(? Call(trap, handler, « target »)). CallResult<PseudoHandle<>> res = Callable::executeCall1( *trapRes, runtime, runtime.makeHandle(detail::slots(*selfHandle).handler), target.getHermesValue()); if (res == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } bool booleanTrapResult = toBoolean(res->get()); if (booleanTrapResult) { // a. Let targetIsExtensible be ? target.[[IsExtensible]](). CallResult<bool> targetRes = JSObject::isExtensible(target, runtime); if (targetRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // b. If targetIsExtensible is true, throw a TypeError exception. if (*targetRes) { return runtime.raiseTypeError( "preventExtensions trap returned true for extensible target"); } } // 10. Return booleanTrapResult. if (!booleanTrapResult && opFlags.getThrowOnError()) { return runtime.raiseTypeError("preventExtensions trap returned false"); } return booleanTrapResult; } CallResult<bool> JSProxy::getOwnProperty( Handle<JSObject> selfHandle, Runtime &runtime, Handle<> nameValHandle, ComputedPropertyDescriptor &desc, MutableHandle<> *valueOrAccessor) { GCScope gcScope{runtime}; ScopedNativeDepthTracker depthTracker(runtime); if (LLVM_UNLIKELY(depthTracker.overflowed())) { return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); } Handle<JSObject> target = runtime.makeHandle(detail::slots(*selfHandle).target); CallResult<Handle<Callable>> trapRes = detail::findTrap( selfHandle, runtime, Predefined::getOwnPropertyDescriptor); if (trapRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } MutableHandle<SymbolID> tmpPropNameStorage{runtime}; // 7. If trap is undefined, then if (!*trapRes) { // a. Return ? target.[[GetOwnProperty]](P). return valueOrAccessor ? JSObject::getOwnComputedDescriptor( target, runtime, nameValHandle, tmpPropNameStorage, desc, *valueOrAccessor) : JSObject::getOwnComputedDescriptor( target, runtime, nameValHandle, tmpPropNameStorage, desc); } // 8. Let trapResultObj be ? Call(trap, handler, « target, P »). // 9. If Type(trapResultObj) is neither Object nor Undefined, throw a // TypeError exception. CallResult<PseudoHandle<>> trapResultRes = Callable::executeCall2( *trapRes, runtime, runtime.makeHandle(detail::slots(*selfHandle).handler), target.getHermesValue(), nameValHandle.getHermesValue()); if (trapResultRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } Handle<> trapResultObj = runtime.makeHandle(std::move(*trapResultRes)); // 10. Let targetDesc be ? target.[[GetOwnProperty]](P). ComputedPropertyDescriptor targetDesc; MutableHandle<> targetValueOrAccessor{runtime}; CallResult<bool> targetDescRes = JSObject::getOwnComputedDescriptor( target, runtime, nameValHandle, tmpPropNameStorage, targetDesc, targetValueOrAccessor); if (targetDescRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 11. If trapResultObj is undefined, then if (trapResultObj->isUndefined()) { // a. If targetDesc is undefined, return undefined. if (!*targetDescRes) { return false; } // b. If targetDesc.[[Configurable]] is false, throw a TypeError // exception. if (!targetDesc.flags.configurable) { return runtime.raiseTypeError( "getOwnPropertyDescriptor trap result is not configurable"); } // c. Let extensibleTarget be ? IsExtensible(target). CallResult<bool> extensibleRes = JSObject::isExtensible(target, runtime); if (extensibleRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // d. Assert: Type(extensibleTarget) is Boolean. // e. If extensibleTarget is false, throw a TypeError exception. if (!*extensibleRes) { return runtime.raiseTypeErrorForValue( runtime.makeHandle(detail::slots(*selfHandle).target), " is not extensible (getOwnPropertyDescriptor target)"); } // f. Return undefined. return false; } else if (!trapResultObj->isObject()) { // 9. If Type(trapResultObj) is neither Object nor Undefined, throw a // TypeError exception. return runtime.raiseTypeErrorForValue( trapResultObj, " is not undefined or Object (Proxy getOwnPropertyDescriptor)"); } // 12. Let extensibleTarget be ? IsExtensible(target). CallResult<bool> extensibleRes = JSObject::isExtensible(target, runtime); if (extensibleRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 13. Let resultDesc be ? ToPropertyDescriptor(trapResultObj). // 14. Call CompletePropertyDescriptor(resultDesc). DefinePropertyFlags resultDesc; MutableHandle<> resultValueOrAccessor{runtime}; Handle<JSObject> trapResult = runtime.makeHandle<JSObject>(*trapResultObj); if (LLVM_UNLIKELY( toPropertyDescriptor( trapResult, runtime, resultDesc, resultValueOrAccessor) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } completePropertyDescriptor(resultDesc); // 15. Let valid be IsCompatiblePropertyDescriptor(extensibleTarget, // resultDesc, targetDesc). // 16. If valid is false, throw a TypeError exception. // ES9 9.1.6.3 ValidateAndApplyPropertyDescriptor step 2 [O is undefined] if (!*targetDescRes) { // a. If extensible is false, return false. if (!*extensibleRes) { return runtime.raiseTypeErrorForValue( "getOwnPropertyDescriptor target is not extensible and has no property ", nameValHandle, ""); } // e. return true // this concludes steps 15 and 16. } else { if (LLVM_UNLIKELY( isCompatiblePropertyDescriptor( runtime, resultDesc, resultValueOrAccessor, targetDesc, targetValueOrAccessor) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } } // 17. If resultDesc.[[Configurable]] is false, then // a. If targetDesc is undefined or targetDesc.[[Configurable]] is true, // then // i. Throw a TypeError exception. if (!resultDesc.configurable && (!*targetDescRes || targetDesc.flags.configurable)) { return runtime.raiseTypeErrorForValue( "getOwnPropertyDescriptor trap result is not configurable but " "target property ", nameValHandle, " is configurable or non-existent"); } // 18. Return resultDesc. desc.flags.enumerable = resultDesc.enumerable; desc.flags.configurable = resultDesc.configurable; desc.flags.writable = resultDesc.writable; if (resultDesc.setGetter || resultDesc.setSetter) { desc.flags.accessor = true; } if (valueOrAccessor) { *valueOrAccessor = std::move(resultValueOrAccessor); } return true; } CallResult<bool> JSProxy::defineOwnProperty( Handle<JSObject> selfHandle, Runtime &runtime, Handle<> nameValHandle, DefinePropertyFlags dpFlags, Handle<> valueOrAccessor, PropOpFlags opFlags) { GCScope gcScope{runtime}; ScopedNativeDepthTracker depthTracker(runtime); if (LLVM_UNLIKELY(depthTracker.overflowed())) { return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); } Handle<JSObject> target = runtime.makeHandle(detail::slots(*selfHandle).target); CallResult<Handle<Callable>> trapRes = detail::findTrap(selfHandle, runtime, Predefined::defineProperty); if (trapRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 7. If trap is undefined, then if (!*trapRes) { // a. Return ? target.[[GetOwnProperty]](P). return JSObject::defineOwnComputedPrimitive( target, runtime, nameValHandle, dpFlags, valueOrAccessor, opFlags); } // 8. Let descObj be FromPropertyDescriptor(Desc). ComputedPropertyDescriptor desc; desc.flags.accessor = dpFlags.setGetter || dpFlags.setSetter; desc.flags.writable = dpFlags.setWritable && dpFlags.writable; desc.flags.enumerable = dpFlags.setEnumerable && dpFlags.enumerable; desc.flags.configurable = dpFlags.setConfigurable && dpFlags.configurable; CallResult<HermesValue> descObjRes = objectFromPropertyDescriptor(runtime, desc, valueOrAccessor); if (descObjRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 9. Let booleanTrapResult be ToBoolean(? Call(trap, handler, « target, P, // descObj »)). CallResult<PseudoHandle<>> trapResultRes = Callable::executeCall3( *trapRes, runtime, runtime.makeHandle(detail::slots(*selfHandle).handler), target.getHermesValue(), nameValHandle.getHermesValue(), *descObjRes); if (trapResultRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } bool trapResult = toBoolean(trapResultRes->get()); // 10. If booleanTrapResult is false, return false. if (!trapResult) { if (opFlags.getThrowOnError()) { return runtime.raiseTypeError("defineProperty proxy trap returned false"); } else { return false; } } // 11. Let targetDesc be ? target.[[GetOwnProperty]](P). ComputedPropertyDescriptor targetDesc; MutableHandle<> targetDescValueOrAccessor{runtime}; MutableHandle<SymbolID> tmpPropNameStorage{runtime}; CallResult<bool> targetDescRes = JSObject::getOwnComputedDescriptor( target, runtime, nameValHandle, tmpPropNameStorage, targetDesc, targetDescValueOrAccessor); if (targetDescRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 12. Let extensibleTarget be ? IsExtensible(target). CallResult<bool> extensibleRes = JSObject::isExtensible(target, runtime); if (extensibleRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 13. If Desc has a [[Configurable]] field and if Desc.[[Configurable]] is // false, then // a. Let settingConfigFalse be true. // 14. Else, let settingConfigFalse be false. bool settingConfigFalse = dpFlags.setConfigurable && !dpFlags.configurable; // 15. If targetDesc is undefined, then if (!*targetDescRes) { // a. If extensibleTarget is false, throw a TypeError exception. if (!*extensibleRes) { return runtime.raiseTypeError( "defineProperty trap called for non-existent property on non-extensible target"); } // b. If settingConfigFalse is true, throw a TypeError exception. if (settingConfigFalse) { return runtime.raiseTypeError( "defineProperty trap attempted to define non-configurable property for non-existent property in the target"); } } else { // 16. Else targetDesc is not undefined, // a. If IsCompatiblePropertyDescriptor(extensibleTarget, Desc, // targetDesc) is false, throw a TypeError exception. if (LLVM_UNLIKELY( isCompatiblePropertyDescriptor( runtime, dpFlags, valueOrAccessor, targetDesc, targetDescValueOrAccessor) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // b. If settingConfigFalse is true and targetDesc.[[Configurable]] is // true, throw a TypeError exception. if (settingConfigFalse && targetDesc.flags.configurable) { return runtime.raiseTypeError( "defineProperty trap attempted to define non-configurable property for configurable property in the target"); } } // 17. Return true. return true; } namespace { /// Common parts of hasNamed/hasComputed CallResult<bool> hasWithTrap( Runtime &runtime, Handle<> nameValHandle, Handle<Callable> trap, Handle<JSObject> handler, Handle<JSObject> target) { // 1. Assert: IsPropertyKey(P) is true. assert(isPropertyKey(nameValHandle) && "key is not a String or Symbol"); // 8. Let booleanTrapResult be ToBoolean(? Call(trap, handler, « target, P // »)). CallResult<PseudoHandle<>> trapResultRes = Callable::executeCall2( trap, runtime, handler, target.getHermesValue(), nameValHandle.getHermesValue()); if (trapResultRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } bool trapResult = toBoolean(trapResultRes->get()); // 9. If booleanTrapResult is false, then if (!trapResult) { // a. Let targetDesc be ? target.[[GetOwnProperty]](P). ComputedPropertyDescriptor targetDesc; MutableHandle<SymbolID> tmpPropNameStorage{runtime}; CallResult<bool> targetDescRes = JSObject::getOwnComputedDescriptor( target, runtime, nameValHandle, tmpPropNameStorage, targetDesc); if (targetDescRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // b. If targetDesc is not undefined, then if (*targetDescRes) { // i. If targetDesc.[[Configurable]] is false, throw a TypeError // exception. if (!targetDesc.flags.configurable) { return runtime.raiseTypeError( "HasProperty trap result is not configurable"); } // ii. Let extensibleTarget be ? IsExtensible(target). CallResult<bool> extensibleRes = JSObject::isExtensible(target, runtime); if (extensibleRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // iii. If extensibleTarget is false, throw a TypeError exception. if (!*extensibleRes) { return runtime.raiseTypeError( "HasProperty proxy target is not extensible"); } } } // 11. Return trapResult. return trapResult; } } // namespace CallResult<bool> JSProxy::hasNamed( Handle<JSObject> selfHandle, Runtime &runtime, SymbolID name) { GCScope gcScope{runtime}; ScopedNativeDepthTracker depthTracker(runtime); if (LLVM_UNLIKELY(depthTracker.overflowed())) { return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); } Handle<JSObject> target = runtime.makeHandle(detail::slots(*selfHandle).target); CallResult<Handle<Callable>> trapRes = detail::findTrap(selfHandle, runtime, Predefined::has); if (trapRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 7. If trap is undefined, then if (!*trapRes) { // a. Return ? target.[[HasProperty]](P, Receiver). return JSObject::hasNamed(target, runtime, name); } return hasWithTrap( runtime, runtime.makeHandle(HermesValue::encodeStringValue( runtime.getStringPrimFromSymbolID(name))), *trapRes, runtime.makeHandle(detail::slots(*selfHandle).handler), target); } CallResult<bool> JSProxy::hasComputed( Handle<JSObject> selfHandle, Runtime &runtime, Handle<> nameValHandle) { GCScope gcScope{runtime}; ScopedNativeDepthTracker depthTracker(runtime); if (LLVM_UNLIKELY(depthTracker.overflowed())) { return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); } Handle<JSObject> target = runtime.makeHandle(detail::slots(*selfHandle).target); CallResult<Handle<Callable>> trapRes = detail::findTrap(selfHandle, runtime, Predefined::has); if (trapRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } if (!*trapRes) { // 7. If trap is undefined, then // a. Return ? target.[[HasProperty]](P, Receiver). return JSObject::hasComputed(target, runtime, nameValHandle); } return hasWithTrap( runtime, nameValHandle, *trapRes, runtime.makeHandle(detail::slots(*selfHandle).handler), target); } namespace { /// Common parts of getNamed/getComputed CallResult<PseudoHandle<>> getWithTrap( Runtime &runtime, Handle<> nameValHandle, Handle<Callable> trap, Handle<JSObject> handler, Handle<JSObject> target, Handle<> receiver) { // 1. Assert: IsPropertyKey(P) is true. assert(isPropertyKey(nameValHandle) && "key is not a String or Symbol"); // 8. Let trapResult be ? Call(trap, handler, « target, P, Receiver »). CallResult<PseudoHandle<>> trapResultRes = Callable::executeCall3( trap, runtime, handler, target.getHermesValue(), nameValHandle.getHermesValue(), receiver.getHermesValue()); if (trapResultRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } Handle<> trapResult = runtime.makeHandle(std::move(*trapResultRes)); // 9. Let targetDesc be ? target.[[GetOwnProperty]](P). ComputedPropertyDescriptor targetDesc; MutableHandle<> targetValueOrAccessor{runtime}; MutableHandle<SymbolID> tmpPropNameStorage{runtime}; CallResult<bool> targetDescRes = JSObject::getOwnComputedDescriptor( target, runtime, nameValHandle, tmpPropNameStorage, targetDesc, targetValueOrAccessor); if (targetDescRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 10. If targetDesc is not undefined and targetDesc.[[Configurable]] is // false, then if (*targetDescRes && !targetDesc.flags.configurable) { // a. If IsDataDescriptor(targetDesc) is true and targetDesc.[[Writable]] // is false, then if (!targetDesc.flags.accessor && !targetDesc.flags.writable) { // i. If SameValue(trapResult, targetDesc.[[Value]]) is false, throw a // TypeError exception. if (!isSameValue(*trapResult, targetValueOrAccessor.getHermesValue())) { return runtime.raiseTypeError( "target property is non-configurable and non-writable, and get trap result differs from target property value"); } } // b. If IsAccessorDescriptor(targetDesc) is true and targetDesc.[[Get]] // is undefined, then // i. If trapResult is not undefined, throw a TypeError exception. if (targetDesc.flags.accessor && !vmcast<PropertyAccessor>(*targetValueOrAccessor)->getter && !trapResult->isUndefined()) { return runtime.raiseTypeError( "target property is non-configurable accessor with no getter, but get trap returned not undefined"); } } // 11. Return trapResult. return {trapResult}; } } // namespace CallResult<PseudoHandle<>> JSProxy::getNamed( Handle<JSObject> selfHandle, Runtime &runtime, SymbolID name, Handle<> receiver) { GCScope gcScope{runtime}; ScopedNativeDepthTracker depthTracker(runtime); if (LLVM_UNLIKELY(depthTracker.overflowed())) { return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); } Handle<JSObject> target = runtime.makeHandle(detail::slots(*selfHandle).target); CallResult<Handle<Callable>> trapRes = detail::findTrap(selfHandle, runtime, Predefined::get); if (trapRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 7. If trap is undefined, then if (!*trapRes) { // a. Return ? target.[[Get]](P, Receiver). return JSObject::getNamedWithReceiver_RJS(target, runtime, name, receiver); } return getWithTrap( runtime, name.isUniqued() ? runtime.makeHandle(HermesValue::encodeStringValue( runtime.getStringPrimFromSymbolID(name))) : runtime.makeHandle(name), *trapRes, runtime.makeHandle(detail::slots(*selfHandle).handler), target, receiver); } CallResult<PseudoHandle<>> JSProxy::getComputed( Handle<JSObject> selfHandle, Runtime &runtime, Handle<> nameValHandle, Handle<> receiver) { GCScope gcScope{runtime}; ScopedNativeDepthTracker depthTracker(runtime); if (LLVM_UNLIKELY(depthTracker.overflowed())) { return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); } Handle<JSObject> target = runtime.makeHandle(detail::slots(*selfHandle).target); CallResult<Handle<Callable>> trapRes = detail::findTrap(selfHandle, runtime, Predefined::get); if (trapRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 7. If trap is undefined, then if (!*trapRes) { // a. Return ? target.[[Get]](P, Receiver). return JSObject::getComputedWithReceiver_RJS( target, runtime, nameValHandle, receiver); } return getWithTrap( runtime, nameValHandle, *trapRes, runtime.makeHandle(detail::slots(*selfHandle).handler), target, receiver); } namespace { /// Common parts of setNamed/setComputed CallResult<bool> setWithTrap( Runtime &runtime, Handle<> nameValHandle, Handle<> valueHandle, Handle<Callable> trap, Handle<JSObject> handler, Handle<JSObject> target, Handle<> receiver) { // 1. Assert: IsPropertyKey(P) is true. assert(isPropertyKey(nameValHandle) && "key is not a String or Symbol"); // 8. Let booleanTrapResult be ToBoolean(? Call(trap, handler, « target, P, V, // Receiver »)). CallResult<PseudoHandle<>> trapResultRes = Callable::executeCall4( trap, runtime, handler, target.getHermesValue(), nameValHandle.getHermesValue(), valueHandle.getHermesValue(), receiver.getHermesValue()); if (trapResultRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 9. If booleanTrapResult is false, return false. if (!toBoolean(trapResultRes->get())) { return false; } // 10. Let targetDesc be ? target.[[GetOwnProperty]](P). ComputedPropertyDescriptor targetDesc; MutableHandle<> targetValueOrAccessor{runtime}; MutableHandle<SymbolID> tmpPropNameStorage{runtime}; CallResult<bool> targetDescRes = JSObject::getOwnComputedDescriptor( target, runtime, nameValHandle, tmpPropNameStorage, targetDesc, targetValueOrAccessor); if (targetDescRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 11. If targetDesc is not undefined and targetDesc.[[Configurable]] is // false, then if (*targetDescRes && !targetDesc.flags.configurable) { // a. If IsDataDescriptor(targetDesc) is true and targetDesc.[[Writable]] // is false, then if (!targetDesc.flags.accessor && !targetDesc.flags.writable) { // i. If SameValue(V, targetDesc.[[Value]]) is false, throw a // TypeError exception. if (!isSameValue( valueHandle.getHermesValue(), targetValueOrAccessor.getHermesValue())) { return runtime.raiseTypeError( "target property is non-configurable and non-writable, and set trap value differs from target property value"); } } // b. If IsAccessorDescriptor(targetDesc) is true, then // i. If targetDesc.[[Set]] is undefined, throw a TypeError exception. if (targetDesc.flags.accessor && !vmcast<PropertyAccessor>(*targetValueOrAccessor)->setter) { return runtime.raiseTypeError( "set trap called, but target property is non-configurable accessor with no setter"); } } // 12. Return true. return true; } } // namespace CallResult<bool> JSProxy::setNamed( Handle<JSObject> selfHandle, Runtime &runtime, SymbolID name, Handle<> valueHandle, // TODO could be HermesValue Handle<> receiver) { GCScope gcScope{runtime}; ScopedNativeDepthTracker depthTracker(runtime); if (LLVM_UNLIKELY(depthTracker.overflowed())) { return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); } Handle<JSObject> target = runtime.makeHandle(detail::slots(*selfHandle).target); CallResult<Handle<Callable>> trapRes = detail::findTrap(selfHandle, runtime, Predefined::set); if (trapRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 7. If trap is undefined, then if (!*trapRes) { // a. Return ? target.[[Set]](P, V, Receiver). return JSObject::putNamedWithReceiver_RJS( target, runtime, name, valueHandle, receiver); } return setWithTrap( runtime, name.isUniqued() ? runtime.makeHandle(HermesValue::encodeStringValue( runtime.getStringPrimFromSymbolID(name))) : runtime.makeHandle(name), valueHandle, *trapRes, runtime.makeHandle(detail::slots(*selfHandle).handler), target, receiver); } CallResult<bool> JSProxy::setComputed( Handle<JSObject> selfHandle, Runtime &runtime, Handle<> nameValHandle, Handle<> valueHandle, // TODO could be HermesValue Handle<> receiver) { GCScope gcScope{runtime}; ScopedNativeDepthTracker depthTracker(runtime); if (LLVM_UNLIKELY(depthTracker.overflowed())) { return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); } Handle<JSObject> target = runtime.makeHandle(detail::slots(*selfHandle).target); CallResult<Handle<Callable>> trapRes = detail::findTrap(selfHandle, runtime, Predefined::set); if (trapRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 7. If trap is undefined, then if (!*trapRes) { // a. Return ? target.[[Set]](P, V, Receiver). return JSObject::putComputedWithReceiver_RJS( target, runtime, nameValHandle, valueHandle, receiver); } return setWithTrap( runtime, nameValHandle, valueHandle, *trapRes, runtime.makeHandle(detail::slots(*selfHandle).handler), target, receiver); } namespace { /// Common parts of deleteNamed/deleteComputed CallResult<bool> deleteWithTrap( Runtime &runtime, Handle<> nameValHandle, Handle<Callable> trap, Handle<JSObject> handler, Handle<JSObject> target) { // 1. Assert: IsPropertyKey(P) is true. assert(isPropertyKey(nameValHandle) && "key is not a String or Symbol"); // 8. Let booleanTrapResult be ToBoolean(? Call(trap, handler, « target, P // »)). CallResult<PseudoHandle<>> trapResultRes = Callable::executeCall2( trap, runtime, handler, target.getHermesValue(), nameValHandle.getHermesValue()); if (trapResultRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } bool trapResult = toBoolean(trapResultRes->getHermesValue()); // 9. If booleanTrapResult is false, return false. if (!trapResult) { return false; } // 10. Let targetDesc be ? target.[[GetOwnProperty]](P). ComputedPropertyDescriptor targetDesc; MutableHandle<> targetValueOrAccessor{runtime}; MutableHandle<SymbolID> tmpPropNameStorage{runtime}; CallResult<bool> targetDescRes = JSObject::getOwnComputedDescriptor( target, runtime, nameValHandle, tmpPropNameStorage, targetDesc, targetValueOrAccessor); if (targetDescRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 11. If targetDesc is undefined, return true. if (!*targetDescRes) { return true; } // 12. If targetDesc.[[Configurable]] is false, throw a TypeError exception. if (!targetDesc.flags.configurable) { return runtime.raiseTypeError( "Delete trap target called, but target property is non-configurable"); } // 13. Return true. return true; } } // namespace CallResult<bool> JSProxy::deleteNamed( Handle<JSObject> selfHandle, Runtime &runtime, SymbolID name) { GCScope gcScope{runtime}; ScopedNativeDepthTracker depthTracker(runtime); if (LLVM_UNLIKELY(depthTracker.overflowed())) { return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); } Handle<JSObject> target = runtime.makeHandle(detail::slots(*selfHandle).target); CallResult<Handle<Callable>> trapRes = detail::findTrap(selfHandle, runtime, Predefined::deleteProperty); if (trapRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 7. If trap is undefined, then if (!*trapRes) { // a. Return ? target.[[Delete]](P, Receiver). return JSObject::deleteNamed(target, runtime, name); } return deleteWithTrap( runtime, runtime.makeHandle(HermesValue::encodeStringValue( runtime.getStringPrimFromSymbolID(name))), *trapRes, runtime.makeHandle(detail::slots(*selfHandle).handler), target); } CallResult<bool> JSProxy::deleteComputed( Handle<JSObject> selfHandle, Runtime &runtime, Handle<> nameValHandle) { GCScope gcScope{runtime}; ScopedNativeDepthTracker depthTracker(runtime); if (LLVM_UNLIKELY(depthTracker.overflowed())) { return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); } Handle<JSObject> target = runtime.makeHandle(detail::slots(*selfHandle).target); CallResult<Handle<Callable>> trapRes = detail::findTrap(selfHandle, runtime, Predefined::deleteProperty); if (trapRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 7. If trap is undefined, then if (!*trapRes) { // a. Return ? target.[[Delete]](P, Receiver). return JSObject::deleteComputed(target, runtime, nameValHandle); } return deleteWithTrap( runtime, nameValHandle, *trapRes, runtime.makeHandle(detail::slots(*selfHandle).handler), target); } namespace { CallResult<PseudoHandle<JSArray>> filterKeys( Handle<JSObject> selfHandle, Handle<JSArray> keys, Runtime &runtime, OwnKeysFlags okFlags) { assert( (okFlags.getIncludeNonSymbols() || okFlags.getIncludeSymbols()) && "Can't exclude symbols and strings"); // If nothing is excluded, just return the array as-is. if (okFlags.getIncludeNonSymbols() && okFlags.getIncludeSymbols() && okFlags.getIncludeNonEnumerable()) { return createPseudoHandle(*keys); } // Count number of matching elements by type. assert( ((okFlags.getIncludeSymbols() ? 0 : 1) + (okFlags.getIncludeNonSymbols() ? 0 : 1)) == 1 && "Exactly one of Symbols or non-Symbols is included here"); bool onlySymbols = okFlags.getIncludeSymbols(); uint32_t len = JSArray::getLength(*keys, runtime); uint32_t count = 0; // Verify this loop is alloc-free { NoAllocScope noAlloc(runtime); for (uint32_t i = 0; i < len; ++i) { if (keys->at(runtime, i).isSymbol() == onlySymbols) { ++count; } } } // If everything in the array matches the filter by type, return // the list as-is. if (len == count && okFlags.getIncludeNonEnumerable()) { return createPseudoHandle(*keys); } // Filter the desired elements we want into the result auto resultRes = JSArray::create(runtime, count, count); if (LLVM_UNLIKELY(resultRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } Handle<JSArray> resultHandle = *resultRes; MutableHandle<> elemHandle{runtime}; uint32_t resultIndex = 0; GCScopeMarkerRAII marker{runtime}; for (uint32_t i = 0; i < len; ++i) { marker.flush(); HermesValue elem = keys->at(runtime, i); if (elem.isSymbol() ? !okFlags.getIncludeSymbols() : !okFlags.getIncludeNonSymbols()) { continue; } elemHandle = elem; if (!okFlags.getIncludeNonEnumerable()) { ComputedPropertyDescriptor desc; CallResult<bool> propRes = JSProxy::getOwnProperty( selfHandle, runtime, elemHandle, desc, nullptr); if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } if (!*propRes || !desc.flags.enumerable) { continue; } } JSArray::setElementAt(resultHandle, runtime, resultIndex++, elemHandle); } assert( (!okFlags.getIncludeNonEnumerable() || resultIndex == count) && "Expected count was not correct"); CallResult<bool> setLenRes = JSArray::setLengthProperty(resultHandle, runtime, resultIndex); if (LLVM_UNLIKELY(setLenRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return createPseudoHandle(*resultHandle); } } // namespace CallResult<PseudoHandle<JSArray>> JSProxy::ownPropertyKeys( Handle<JSObject> selfHandle, Runtime &runtime, OwnKeysFlags okFlags) { GCScope gcScope{runtime}; ScopedNativeDepthTracker depthTracker(runtime); if (LLVM_UNLIKELY(depthTracker.overflowed())) { return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); } Handle<JSObject> target = runtime.makeHandle(detail::slots(*selfHandle).target); CallResult<Handle<Callable>> trapRes = detail::findTrap(selfHandle, runtime, Predefined::ownKeys); if (trapRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 6. If trap is undefined, then if (!*trapRes) { // a. Return ? target.[[OwnPropertyKeys]](). CallResult<Handle<JSArray>> targetRes = // Include everything here, so that filterKeys has a chance to // make observable trap calls. JSObject::getOwnPropertyKeys( target, runtime, OwnKeysFlags() .plusIncludeSymbols() .plusIncludeNonSymbols() .plusIncludeNonEnumerable()); if (targetRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } return filterKeys(selfHandle, *targetRes, runtime, okFlags); } // 7. Let trapResultArray be ? Call(trap, handler, « target »). CallResult<PseudoHandle<>> trapResultArrayRes = Callable::executeCall1( *trapRes, runtime, runtime.makeHandle(detail::slots(*selfHandle).handler), target.getHermesValue()); if (trapResultArrayRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } if (!vmisa<JSObject>(trapResultArrayRes->get())) { return runtime.raiseTypeErrorForValue( runtime.makeHandle(std::move(*trapResultArrayRes)), " ownKeys trap result is not an Object"); } auto trapResultArray = runtime.makeHandle<JSObject>(trapResultArrayRes->get()); // 8. Let trapResult be ? CreateListFromArrayLike(trapResultArray, « String, // Symbol ») // 9. If trapResult contains any duplicate entries, throw a TypeError // exception. CallResult<uint64_t> countRes = getArrayLikeLength(trapResultArray, runtime); if (LLVM_UNLIKELY(countRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } if (*countRes > UINT32_MAX) { return runtime.raiseRangeError( "Too many elements returned from ownKeys trap"); } uint32_t count = static_cast<uint32_t>(*countRes); auto trapResultRes = JSArray::create(runtime, count, count); if (LLVM_UNLIKELY(trapResultRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } Handle<JSArray> trapResult = *trapResultRes; CallResult<PseudoHandle<OrderedHashMap>> dupcheckRes = OrderedHashMap::create(runtime); if (LLVM_UNLIKELY(dupcheckRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } Handle<OrderedHashMap> dupcheck = runtime.makeHandle(std::move(*dupcheckRes)); if (LLVM_UNLIKELY( createListFromArrayLike( trapResultArray, runtime, count, [&dupcheck, &trapResult]( Runtime &runtime, uint64_t index, PseudoHandle<> value) { Handle<> valHandle = runtime.makeHandle(std::move(value)); if (!valHandle->isString() && !valHandle->isSymbol()) { return runtime.raiseTypeErrorForValue( valHandle, " ownKeys trap result element is not String or Symbol"); } if (OrderedHashMap::has(dupcheck, runtime, valHandle)) { return runtime.raiseTypeErrorForValue( "ownKeys trap result has duplicate ", valHandle, ""); } if (LLVM_UNLIKELY( OrderedHashMap::insert( dupcheck, runtime, valHandle, valHandle) == ExecutionStatus::EXCEPTION)) return ExecutionStatus::RETURNED; JSArray::setElementAt(trapResult, runtime, index, valHandle); return ExecutionStatus::RETURNED; }) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // 10. Let extensibleTarget be ? IsExtensible(target). CallResult<bool> extensibleRes = JSObject::isExtensible(target, runtime); if (extensibleRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // 11. Let targetKeys be ? target.[[OwnPropertyKeys]](). CallResult<Handle<JSArray>> targetKeysRes = JSObject::getOwnPropertyKeys( target, runtime, OwnKeysFlags() .plusIncludeSymbols() .plusIncludeNonSymbols() .plusIncludeNonEnumerable()); if (targetKeysRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } Handle<JSArray> targetKeys = *targetKeysRes; // 12. Assert: targetKeys is a List containing only String and Symbol values. // 13. Assert: targetKeys contains no duplicate entries. // 14. Let targetConfigurableKeys be a new empty List. // 15. Let targetNonconfigurableKeys be a new empty List. llvh::SmallSet<uint32_t, 8> nonConfigurable; MutableHandle<SymbolID> tmpPropNameStorage{runtime}; // 16. For each element key of targetKeys, do GCScopeMarkerRAII marker{runtime}; for (uint32_t i = 0, len = JSArray::getLength(*targetKeys, runtime); i < len; ++i) { marker.flush(); // a. Let desc be ? target.[[GetOwnProperty]](key). ComputedPropertyDescriptor desc; CallResult<bool> descRes = JSObject::getOwnComputedDescriptor( target, runtime, runtime.makeHandle(targetKeys->at(runtime, i)), tmpPropNameStorage, desc); if (descRes == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // b. If desc is not undefined and desc.[[Configurable]] is false, then // i. Append key as an element of targetNonconfigurableKeys. // c. Else, // i. Append key as an element of targetConfigurableKeys. if (*descRes && !desc.flags.configurable) { nonConfigurable.insert(i); } } // 17. If extensibleTarget is true and targetNonconfigurableKeys is empty, // then if (*extensibleRes && nonConfigurable.empty()) { // a. Return trapResult. return filterKeys(selfHandle, trapResult, runtime, okFlags); } // 18. Let uncheckedResultKeys be a new List which is a copy of trapResult. // 19. For each key that is an element of targetNonconfigurableKeys, do // a. If key is not an element of uncheckedResultKeys, throw a TypeError // exception. b. Remove key from uncheckedResultKeys. auto inTrapResult = [&runtime, &trapResult](HermesValue value) { for (uint32_t j = 0, len = JSArray::getLength(*trapResult, runtime); j < len; ++j) { if (isSameValue(value, trapResult->at(runtime, j))) { return true; } } return false; }; for (auto i : nonConfigurable) { if (!inTrapResult(targetKeys->at(runtime, i))) { return runtime.raiseTypeError( "ownKeys target key is non-configurable but not present in trap result"); } } // 20. If extensibleTarget is true, return trapResult. if (*extensibleRes) { return filterKeys(selfHandle, trapResult, runtime, okFlags); } // 21. For each key that is an element of targetConfigurableKeys, do // a. If key is not an element of uncheckedResultKeys, throw a TypeError // exception. b. Remove key from uncheckedResultKeys. for (uint32_t i = 0, len = JSArray::getLength(*targetKeys, runtime); i < len; ++i) { if (nonConfigurable.count(i) > 0) { continue; } if (!inTrapResult(targetKeys->at(runtime, i))) { return runtime.raiseTypeError( "ownKeys target is non-extensible but key is missing from trap result"); } } // 22. If uncheckedResultKeys is not empty, throw a TypeError exception. if (JSArray::getLength(*targetKeys, runtime) != JSArray::getLength(*trapResult, runtime)) { return runtime.raiseTypeError( "ownKeys target is non-extensible but trap result keys differ from target keys"); } // 23. Return trapResult. return filterKeys(selfHandle, trapResult, runtime, okFlags); } } // namespace vm } // namespace hermes