Browse Source

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
Linus Groh 3 years ago
parent
commit
e20efaa083

+ 1 - 0
Userland/Libraries/LibJS/Runtime/ErrorTypes.h

@@ -277,6 +277,7 @@
     M(UnknownIdentifier, "'{}' is not defined")                                                                                         \
     M(UnknownIdentifier, "'{}' is not defined")                                                                                         \
     M(UnsupportedDeleteSuperProperty, "Can't delete a property on 'super'")                                                             \
     M(UnsupportedDeleteSuperProperty, "Can't delete a property on 'super'")                                                             \
     M(WrappedFunctionCallThrowCompletion, "Call of wrapped target function did not complete normally")                                  \
     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(URIMalformed, "URI malformed") /* LibWeb bindings */                                                                              \
     M(NotAByteString, "Argument to {}() must be a byte string")                                                                         \
     M(NotAByteString, "Argument to {}() must be a byte string")                                                                         \
     M(BadArgCountOne, "{}() needs one argument")                                                                                        \
     M(BadArgCountOne, "{}() needs one argument")                                                                                        \

+ 65 - 5
Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp

@@ -31,7 +31,67 @@ void ShadowRealm::visit_edges(Visitor& visitor)
     visitor.visit(&m_shadow_realm);
     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)
 ThrowCompletionOr<Value> perform_shadow_realm_eval(GlobalObject& global_object, StringView source_text, Realm& caller_realm, Realm& eval_realm)
 {
 {
     auto& vm = global_object.vm();
     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.
     // 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)
 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();
     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);
     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)
 ThrowCompletionOr<Value> get_wrapped_value(GlobalObject& global_object, Realm& caller_realm, Value value)
 {
 {
     auto& vm = global_object.vm();
     auto& vm = global_object.vm();
@@ -240,8 +300,8 @@ ThrowCompletionOr<Value> get_wrapped_value(GlobalObject& global_object, Realm& c
         if (!value.is_function())
         if (!value.is_function())
             return vm.throw_completion<TypeError>(global_object, ErrorType::ShadowRealmWrappedValueNonFunctionObject, value);
             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.
     // 3. Return value.

+ 2 - 1
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
  * SPDX-License-Identifier: BSD-2-Clause
  */
  */
@@ -32,6 +32,7 @@ private:
     ExecutionContext m_execution_context; // [[ExecutionContext]]
     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> 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> 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);
 ThrowCompletionOr<Value> get_wrapped_value(GlobalObject&, Realm& caller_realm, Value);

+ 20 - 15
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
  * SPDX-License-Identifier: BSD-2-Clause
  */
  */
@@ -10,24 +10,29 @@
 
 
 namespace JS {
 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& 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
 // 2 Wrapped Function Exotic Objects, https://tc39.es/proposal-shadowrealm/#sec-wrapped-function-exotic-objects

+ 1 - 1
Userland/Libraries/LibJS/Runtime/WrappedFunction.h

@@ -15,7 +15,7 @@ class WrappedFunction final : public FunctionObject {
     JS_OBJECT(WrappedFunction, FunctionObject);
     JS_OBJECT(WrappedFunction, FunctionObject);
 
 
 public:
 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);
     WrappedFunction(Realm&, FunctionObject&, Object& prototype);
     virtual ~WrappedFunction() = default;
     virtual ~WrappedFunction() = default;

+ 31 - 0
Userland/Libraries/LibJS/Tests/builtins/ShadowRealm/ShadowRealm.prototype.evaluate.js

@@ -46,6 +46,37 @@ describe("normal behavior", () => {
         expect(typeof wrappedFunction).toBe("function");
         expect(typeof wrappedFunction).toBe("function");
         expect(Object.getPrototypeOf(wrappedFunction)).toBe(Function.prototype);
         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(() => {
         expect(() => {
             shadowRealm.evaluate("(function () { throw Error(); })")();
             shadowRealm.evaluate("(function () { throw Error(); })")();
         }).toThrowWithMessage(
         }).toThrowWithMessage(