diff options
author | Linus Groh <mail@linusgroh.de> | 2022-01-24 19:14:04 +0000 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2022-01-24 20:17:07 +0000 |
commit | e20efaa083ab3c231949d7bcaf1d21cdf8cf23a1 (patch) | |
tree | 2cc61c12ce884f191580b1b08823389e4cd951a2 | |
parent | 886d6c62f9b5f08b15628b291b886f967e6974c8 (diff) | |
download | serenity-e20efaa083ab3c231949d7bcaf1d21cdf8cf23a1.zip |
LibJS: Let WrappedFunction inherit target name and length
This is a normative change in the ShadowRealm spec.
See: https://github.com/tc39/proposal-shadowrealm/commit/b73a1dc
6 files changed, 120 insertions, 22 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index bb63ac29be..acb521a6e2 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -277,6 +277,7 @@ M(UnknownIdentifier, "'{}' is not defined") \ M(UnsupportedDeleteSuperProperty, "Can't delete a property on 'super'") \ M(WrappedFunctionCallThrowCompletion, "Call of wrapped target function did not complete normally") \ + M(WrappedFunctionCopyNameAndLengthThrowCompletion, "Trying to copy target name and length did not complete normally") \ M(URIMalformed, "URI malformed") /* LibWeb bindings */ \ M(NotAByteString, "Argument to {}() must be a byte string") \ M(BadArgCountOne, "{}() needs one argument") \ diff --git a/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp b/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp index fc21f4bb17..a14c02d5aa 100644 --- a/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp +++ b/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp @@ -31,7 +31,67 @@ void ShadowRealm::visit_edges(Visitor& visitor) visitor.visit(&m_shadow_realm); } -// 3.1.1 PerformShadowRealmEval ( sourceText, callerRealm, evalRealm ), https://tc39.es/proposal-shadowrealm/#sec-performshadowrealmeval +// 3.1.2 CopyNameAndLength ( F: a function object, Target: a function object, prefix: a String, optional argCount: a Number, ), https://tc39.es/proposal-shadowrealm/#sec-copynameandlength +ThrowCompletionOr<void> copy_name_and_length(GlobalObject& global_object, FunctionObject& function, FunctionObject& target, StringView prefix, Optional<unsigned> arg_count) +{ + auto& vm = global_object.vm(); + + // 1. If argCount is undefined, then set argCount to 0. + if (!arg_count.has_value()) + arg_count = 0; + + // 2. Let L be 0. + double length = 0; + + // 3. Let targetHasLength be ? HasOwnProperty(Target, "length"). + auto target_has_length = TRY(target.has_own_property(vm.names.length)); + + // 4. If targetHasLength is true, then + if (target_has_length) { + // a. Let targetLen be ? Get(Target, "length"). + auto target_length = TRY(target.get(vm.names.length)); + + // b. If Type(targetLen) is Number, then + if (target_length.is_number()) { + // i. If targetLen is +∞𝔽, set L to +∞. + if (target_length.is_positive_infinity()) { + length = target_length.as_double(); + } + // ii. Else if targetLen is -∞𝔽, set L to 0. + else if (target_length.is_negative_infinity()) { + length = 0; + } + // iii. Else, + else { + // 1. Let targetLenAsInt be ! ToIntegerOrInfinity(targetLen). + auto target_length_as_int = MUST(target_length.to_integer_or_infinity(global_object)); + + // 2. Assert: targetLenAsInt is finite. + VERIFY(!isinf(target_length_as_int)); + + // 3. Set L to max(targetLenAsInt - argCount, 0). + length = max(target_length_as_int - *arg_count, 0); + } + } + } + + // 5. Perform ! SetFunctionLength(F, L). + function.set_function_length(length); + + // 6. Let targetName be ? Get(Target, "name"). + auto target_name = TRY(target.get(vm.names.name)); + + // 7. If Type(targetName) is not String, set targetName to the empty String. + if (!target_name.is_string()) + target_name = js_string(vm, String::empty()); + + // 8. Perform ! SetFunctionName(F, targetName, prefix). + function.set_function_name({ target_name.as_string().string() }, prefix); + + return {}; +} + +// 3.1.3 PerformShadowRealmEval ( sourceText, callerRealm, evalRealm ), https://tc39.es/proposal-shadowrealm/#sec-performshadowrealmeval ThrowCompletionOr<Value> perform_shadow_realm_eval(GlobalObject& global_object, StringView source_text, Realm& caller_realm, Realm& eval_realm) { auto& vm = global_object.vm(); @@ -143,7 +203,7 @@ ThrowCompletionOr<Value> perform_shadow_realm_eval(GlobalObject& global_object, // NOTE: Also see "Editor's Note" in the spec regarding the TypeError above. } -// 3.1.2 ShadowRealmImportValue ( specifierString, exportNameString, callerRealm, evalRealm, evalContext ), https://tc39.es/proposal-shadowrealm/#sec-shadowrealmimportvalue +// 3.1.4 ShadowRealmImportValue ( specifierString, exportNameString, callerRealm, evalRealm, evalContext ), https://tc39.es/proposal-shadowrealm/#sec-shadowrealmimportvalue ThrowCompletionOr<Value> shadow_realm_import_value(GlobalObject& global_object, String specifier_string, String export_name_string, Realm& caller_realm, Realm& eval_realm, ExecutionContext& eval_context) { auto& vm = global_object.vm(); @@ -227,7 +287,7 @@ ThrowCompletionOr<Value> shadow_realm_import_value(GlobalObject& global_object, return verify_cast<Promise>(inner_capability.promise)->perform_then(on_fulfilled, throw_type_error, promise_capability); } -// 3.1.3 GetWrappedValue ( callerRealm, value ), https://tc39.es/proposal-shadowrealm/#sec-getwrappedvalue +// 3.1.5 GetWrappedValue ( callerRealm, value ), https://tc39.es/proposal-shadowrealm/#sec-getwrappedvalue ThrowCompletionOr<Value> get_wrapped_value(GlobalObject& global_object, Realm& caller_realm, Value value) { auto& vm = global_object.vm(); @@ -240,8 +300,8 @@ ThrowCompletionOr<Value> get_wrapped_value(GlobalObject& global_object, Realm& c if (!value.is_function()) return vm.throw_completion<TypeError>(global_object, ErrorType::ShadowRealmWrappedValueNonFunctionObject, value); - // b. Return ! WrappedFunctionCreate(callerRealm, value). - return WrappedFunction::create(global_object, caller_realm, value.as_function()); + // b. Return ? WrappedFunctionCreate(callerRealm, value). + return TRY(WrappedFunction::create(global_object, caller_realm, value.as_function())); } // 3. Return value. diff --git a/Userland/Libraries/LibJS/Runtime/ShadowRealm.h b/Userland/Libraries/LibJS/Runtime/ShadowRealm.h index 9e57ff86a8..164d8a3b3c 100644 --- a/Userland/Libraries/LibJS/Runtime/ShadowRealm.h +++ b/Userland/Libraries/LibJS/Runtime/ShadowRealm.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Linus Groh <linusg@serenityos.org> + * Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org> * * SPDX-License-Identifier: BSD-2-Clause */ @@ -32,6 +32,7 @@ private: ExecutionContext m_execution_context; // [[ExecutionContext]] }; +ThrowCompletionOr<void> copy_name_and_length(GlobalObject&, FunctionObject& function, FunctionObject& target, StringView prefix, Optional<unsigned> arg_count = {}); ThrowCompletionOr<Value> perform_shadow_realm_eval(GlobalObject&, StringView source_text, Realm& caller_realm, Realm& eval_realm); ThrowCompletionOr<Value> shadow_realm_import_value(GlobalObject&, String specifier_string, String export_name_string, Realm& caller_realm, Realm& eval_realm, ExecutionContext& eval_context); ThrowCompletionOr<Value> get_wrapped_value(GlobalObject&, Realm& caller_realm, Value); diff --git a/Userland/Libraries/LibJS/Runtime/WrappedFunction.cpp b/Userland/Libraries/LibJS/Runtime/WrappedFunction.cpp index bf81f116f4..422e0e7f14 100644 --- a/Userland/Libraries/LibJS/Runtime/WrappedFunction.cpp +++ b/Userland/Libraries/LibJS/Runtime/WrappedFunction.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Linus Groh <linusg@serenityos.org> + * Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org> * * SPDX-License-Identifier: BSD-2-Clause */ @@ -10,24 +10,29 @@ namespace JS { -// 2.2 WrappedFunctionCreate ( callerRealm, targetFunction ), https://tc39.es/proposal-shadowrealm/#sec-wrappedfunctioncreate -WrappedFunction* WrappedFunction::create(GlobalObject& global_object, Realm& caller_realm, FunctionObject& target_function) +// 3.1.1 WrappedFunctionCreate ( callerRealm: a Realm Record, Target: a function object, ), https://tc39.es/proposal-shadowrealm/#sec-wrappedfunctioncreate +ThrowCompletionOr<WrappedFunction*> WrappedFunction::create(GlobalObject& global_object, Realm& caller_realm, FunctionObject& target) { - // 1. Assert: callerRealm is a Realm Record. - // 2. Assert: IsCallable(targetFunction) is true. - - // 3. Let internalSlotsList be the internal slots listed in Table 2, plus [[Prototype]] and [[Extensible]]. - // 4. Let obj be ! MakeBasicObject(internalSlotsList). + auto& vm = global_object.vm(); + + // 1. Let internalSlotsList be the internal slots listed in Table 2, plus [[Prototype]] and [[Extensible]]. + // 2. Let wrapped be ! MakeBasicObject(internalSlotsList). + // 3. Set wrapped.[[Prototype]] to callerRealm.[[Intrinsics]].[[%Function.prototype%]]. + // 4. Set wrapped.[[Call]] as described in 2.1. + // 5. Set wrapped.[[WrappedTargetFunction]] to Target. + // 6. Set wrapped.[[Realm]] to callerRealm. auto& prototype = *caller_realm.global_object().function_prototype(); - auto* object = global_object.heap().allocate<WrappedFunction>(global_object, caller_realm, target_function, prototype); + auto* wrapped = global_object.heap().allocate<WrappedFunction>(global_object, caller_realm, target, prototype); + + // 7. Let result be CopyNameAndLength(wrapped, Target, "wrapped"). + auto result = copy_name_and_length(global_object, *wrapped, target, "wrapped"sv); - // 5. Set obj.[[Prototype]] to callerRealm.[[Intrinsics]].[[%Function.prototype%]]. - // 6. Set obj.[[Call]] as described in 2.1. - // 7. Set obj.[[WrappedTargetFunction]] to targetFunction. - // 8. Set obj.[[Realm]] to callerRealm. + // 8. If result is an Abrupt Completion, throw a TypeError exception. + if (result.is_throw_completion()) + return vm.throw_completion<TypeError>(global_object, ErrorType::WrappedFunctionCopyNameAndLengthThrowCompletion); - // 9. Return obj. - return object; + // 9. Return wrapped. + return wrapped; } // 2 Wrapped Function Exotic Objects, https://tc39.es/proposal-shadowrealm/#sec-wrapped-function-exotic-objects diff --git a/Userland/Libraries/LibJS/Runtime/WrappedFunction.h b/Userland/Libraries/LibJS/Runtime/WrappedFunction.h index 89770c02ea..00eb2fd7ec 100644 --- a/Userland/Libraries/LibJS/Runtime/WrappedFunction.h +++ b/Userland/Libraries/LibJS/Runtime/WrappedFunction.h @@ -15,7 +15,7 @@ class WrappedFunction final : public FunctionObject { JS_OBJECT(WrappedFunction, FunctionObject); public: - static WrappedFunction* create(GlobalObject&, Realm& caller_realm, FunctionObject& target_function); + static ThrowCompletionOr<WrappedFunction*> create(GlobalObject&, Realm& caller_realm, FunctionObject& target_function); WrappedFunction(Realm&, FunctionObject&, Object& prototype); virtual ~WrappedFunction() = default; diff --git a/Userland/Libraries/LibJS/Tests/builtins/ShadowRealm/ShadowRealm.prototype.evaluate.js b/Userland/Libraries/LibJS/Tests/builtins/ShadowRealm/ShadowRealm.prototype.evaluate.js index d301b4e5eb..e4d508380d 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/ShadowRealm/ShadowRealm.prototype.evaluate.js +++ b/Userland/Libraries/LibJS/Tests/builtins/ShadowRealm/ShadowRealm.prototype.evaluate.js @@ -46,6 +46,37 @@ describe("normal behavior", () => { expect(typeof wrappedFunction).toBe("function"); expect(Object.getPrototypeOf(wrappedFunction)).toBe(Function.prototype); + expect(shadowRealm.evaluate("(function () {})").name).toBe("wrapped "); + expect(shadowRealm.evaluate("(function foo() {})").name).toBe("wrapped foo"); + expect(shadowRealm.evaluate("(function () {})")).toHaveLength(0); + expect(shadowRealm.evaluate("(function (foo, bar) {})")).toHaveLength(2); + expect( + shadowRealm.evaluate( + "Object.defineProperty(function () {}, 'length', { get() { return -Infinity } })" + ) + ).toHaveLength(0); + expect( + shadowRealm.evaluate( + "Object.defineProperty(function () {}, 'length', { get() { return Infinity } })" + ) + ).toHaveLength(Infinity); + + for (const property of ["name", "length"]) { + expect(() => { + shadowRealm.evaluate( + ` + function foo() {} + Object.defineProperty(foo, "${property}", { + get() { throw Error(); } + }); + ` + ); + }).toThrowWithMessage( + TypeError, + "Trying to copy target name and length did not complete normally" + ); + } + expect(() => { shadowRealm.evaluate("(function () { throw Error(); })")(); }).toThrowWithMessage( |