diff --git a/Libraries/LibJS/Parser.h b/Libraries/LibJS/Parser.h index ccd5165aa89..233279a367b 100644 --- a/Libraries/LibJS/Parser.h +++ b/Libraries/LibJS/Parser.h @@ -209,9 +209,6 @@ public: bool try_parse_arrow_function_expression_failed; }; - // Needs to mess with m_state, and we're not going to expose a non-const getter for that :^) - friend ThrowCompletionOr> FunctionConstructor::create_dynamic_function(VM&, FunctionObject&, FunctionObject*, FunctionKind, ReadonlySpan parameter_args, String const& body_arg); - static Parser parse_function_body_from_string(ByteString const& body_string, u16 parse_options, NonnullRefPtr, FunctionKind kind, FunctionParsingInsights&); private: diff --git a/Libraries/LibJS/Runtime/AbstractOperations.cpp b/Libraries/LibJS/Runtime/AbstractOperations.cpp index 3e573ad320d..5fea586ba6a 100644 --- a/Libraries/LibJS/Runtime/AbstractOperations.cpp +++ b/Libraries/LibJS/Runtime/AbstractOperations.cpp @@ -526,36 +526,61 @@ Object* get_super_constructor(VM& vm) } // 19.2.1.1 PerformEval ( x, strictCaller, direct ), https://tc39.es/ecma262/#sec-performeval +// 3 PerformEval ( x, strictCaller, direct ), https://tc39.es/proposal-dynamic-code-brand-checks/#sec-performeval ThrowCompletionOr perform_eval(VM& vm, Value x, CallerMode strict_caller, EvalMode direct) { // 1. Assert: If direct is false, then strictCaller is also false. VERIFY(direct == EvalMode::Direct || strict_caller == CallerMode::NonStrict); - // 2. If Type(x) is not String, return x. - if (!x.is_string()) - return x; - auto& code_string = x.as_string(); + GC::Ptr code_string; - // 3. Let evalRealm be the current Realm Record. + // 2. If x is a String, then + if (x.is_string()) { + // a. Let xStr be x. + code_string = x.as_string(); + } + // 3. Else if x is an Object, then + else if (x.is_object()) { + // a. Let code be HostGetCodeForEval(x). + auto code = vm.host_get_code_for_eval(x.as_object()); + + // b. If code is a String, let xStr be code. + if (code) { + code_string = code; + } + // c. Else, return x. + else { + return x; + } + } + // 4. Else, + else { + // a. Return x. + return x; + } + + VERIFY(code_string); + + // 5. Let evalRealm be the current Realm Record. auto& eval_realm = *vm.running_execution_context().realm; - // 4. NOTE: In the case of a direct eval, evalRealm is the realm of both the caller of eval and of the eval function itself. - // 5. Perform ? HostEnsureCanCompileStrings(evalRealm, « », x, direct). - TRY(vm.host_ensure_can_compile_strings(eval_realm, {}, code_string.utf8_string_view(), direct)); + // 6. NOTE: In the case of a direct eval, evalRealm is the realm of both the caller of eval and of the eval function itself. + // 7. Perform ? HostEnsureCanCompileStrings(evalRealm, « », xStr, xStr, direct, « », x). + TRY(vm.host_ensure_can_compile_strings(eval_realm, {}, code_string->utf8_string_view(), code_string->utf8_string_view(), direct == EvalMode::Direct ? CompilationType::DirectEval : CompilationType::IndirectEval, {}, x)); - // 6. Let inFunction be false. + // 8. Let inFunction be false. bool in_function = false; - // 7. Let inMethod be false. + // 9. Let inMethod be false. bool in_method = false; - // 8. Let inDerivedConstructor be false. + // 10. Let inDerivedConstructor be false. bool in_derived_constructor = false; - // 9. Let inClassFieldInitializer be false. + // 11. Let inClassFieldInitializer be false. bool in_class_field_initializer = false; - // 10. If direct is true, then + // 12. If direct is true, then if (direct == EvalMode::Direct) { // a. Let thisEnvRec be GetThisEnvironment(). auto this_environment_record = get_this_environment(vm); @@ -586,7 +611,7 @@ ThrowCompletionOr perform_eval(VM& vm, Value x, CallerMode strict_caller, } } - // 11. Perform the following substeps in an implementation-defined order, possibly interleaving parsing and error detection: + // 13. Perform the following substeps in an implementation-defined order, possibly interleaving parsing and error detection: // a. Let script be ParseText(StringToCodePoints(x), Script). // c. If script Contains ScriptBody is false, return undefined. // d. Let body be the ScriptBody of script. @@ -602,7 +627,7 @@ ThrowCompletionOr perform_eval(VM& vm, Value x, CallerMode strict_caller, .in_class_field_initializer = in_class_field_initializer, }; - Parser parser { Lexer { code_string.utf8_string_view() }, Program::Type::Script, move(initial_state) }; + Parser parser { Lexer { code_string->utf8_string_view() }, Program::Type::Script, move(initial_state) }; auto program = parser.parse_program(strict_caller == CallerMode::Strict); // b. If script is a List of errors, throw a SyntaxError exception. @@ -613,22 +638,22 @@ ThrowCompletionOr perform_eval(VM& vm, Value x, CallerMode strict_caller, bool strict_eval = false; - // 12. If strictCaller is true, let strictEval be true. + // 14. If strictCaller is true, let strictEval be true. if (strict_caller == CallerMode::Strict) strict_eval = true; - // 13. Else, let strictEval be IsStrict of script. + // 15. Else, let strictEval be IsStrict of script. else strict_eval = program->is_strict_mode(); - // 14. Let runningContext be the running execution context. - // 15. NOTE: If direct is true, runningContext will be the execution context that performed the direct eval. If direct is false, runningContext will be the execution context for the invocation of the eval function. + // 16. Let runningContext be the running execution context. + // 17. NOTE: If direct is true, runningContext will be the execution context that performed the direct eval. If direct is false, runningContext will be the execution context for the invocation of the eval function. auto& running_context = vm.running_execution_context(); Environment* lexical_environment; Environment* variable_environment; PrivateEnvironment* private_environment; - // 16. If direct is true, then + // 18. If direct is true, then if (direct == EvalMode::Direct) { // a. Let lexEnv be NewDeclarativeEnvironment(runningContext's LexicalEnvironment). lexical_environment = new_declarative_environment(*running_context.lexical_environment); @@ -639,7 +664,7 @@ ThrowCompletionOr perform_eval(VM& vm, Value x, CallerMode strict_caller, // c. Let privateEnv be runningContext's PrivateEnvironment. private_environment = running_context.private_environment; } - // 17. Else, + // 19. Else, else { // a. Let lexEnv be NewDeclarativeEnvironment(evalRealm.[[GlobalEnv]]). lexical_environment = new_declarative_environment(eval_realm.global_environment()); @@ -651,7 +676,7 @@ ThrowCompletionOr perform_eval(VM& vm, Value x, CallerMode strict_caller, private_environment = nullptr; } - // 18. If strictEval is true, set varEnv to lexEnv. + // 20. If strictEval is true, set varEnv to lexEnv. if (strict_eval) variable_environment = lexical_environment; @@ -662,15 +687,15 @@ ThrowCompletionOr perform_eval(VM& vm, Value x, CallerMode strict_caller, variable_environment->set_permanently_screwed_by_eval(); } - // 19. If runningContext is not already suspended, suspend runningContext. - // NOTE: Done by the push on step 27. + // 21. If runningContext is not already suspended, suspend runningContext. + // NOTE: Done by the push on step 29. // NOTE: Spec steps are rearranged in order to compute number of registers+constants+locals before construction of the execution context. - // 28. Let result be Completion(EvalDeclarationInstantiation(body, varEnv, lexEnv, privateEnv, strictEval)). + // 30. Let result be Completion(EvalDeclarationInstantiation(body, varEnv, lexEnv, privateEnv, strictEval)). TRY(eval_declaration_instantiation(vm, program, variable_environment, lexical_environment, private_environment, strict_eval)); - // 29. If result.[[Type]] is normal, then + // 31. If result.[[Type]] is normal, then // a. Set result to the result of evaluating body. auto executable_result = Bytecode::Generator::generate_from_ast_node(vm, program, {}); if (executable_result.is_error()) @@ -680,38 +705,38 @@ ThrowCompletionOr perform_eval(VM& vm, Value x, CallerMode strict_caller, if (Bytecode::g_dump_bytecode) executable->dump(); - // 20. Let evalContext be a new ECMAScript code execution context. + // 22. Let evalContext be a new ECMAScript code execution context. ExecutionContext* eval_context = nullptr; ALLOCATE_EXECUTION_CONTEXT_ON_NATIVE_STACK(eval_context, executable->number_of_registers + executable->constants.size() + executable->local_variable_names.size(), 0); - // 21. Set evalContext's Function to null. + // 23. Set evalContext's Function to null. // NOTE: This was done in the construction of eval_context. - // 22. Set evalContext's Realm to evalRealm. + // 24. Set evalContext's Realm to evalRealm. eval_context->realm = &eval_realm; - // 23. Set evalContext's ScriptOrModule to runningContext's ScriptOrModule. + // 25. Set evalContext's ScriptOrModule to runningContext's ScriptOrModule. eval_context->script_or_module = running_context.script_or_module; - // 24. Set evalContext's VariableEnvironment to varEnv. + // 26. Set evalContext's VariableEnvironment to varEnv. eval_context->variable_environment = variable_environment; - // 25. Set evalContext's LexicalEnvironment to lexEnv. + // 27. Set evalContext's LexicalEnvironment to lexEnv. eval_context->lexical_environment = lexical_environment; - // 26. Set evalContext's PrivateEnvironment to privateEnv. + // 28. Set evalContext's PrivateEnvironment to privateEnv. eval_context->private_environment = private_environment; // NOTE: This isn't in the spec, but we require it. eval_context->is_strict_mode = strict_eval; - // 27. Push evalContext onto the execution context stack; evalContext is now the running execution context. + // 29. Push evalContext onto the execution context stack; evalContext is now the running execution context. TRY(vm.push_execution_context(*eval_context, {})); // NOTE: We use a ScopeGuard to automatically pop the execution context when any of the `TRY`s below return a throw completion. ScopeGuard pop_guard = [&] { - // 31. Suspend evalContext and remove it from the execution context stack. - // 32. Resume the context that is now on the top of the execution context stack as the running execution context. + // 33. Suspend evalContext and remove it from the execution context stack. + // 34. Resume the context that is now on the top of the execution context stack as the running execution context. vm.pop_execution_context(); }; @@ -723,11 +748,11 @@ ThrowCompletionOr perform_eval(VM& vm, Value x, CallerMode strict_caller, eval_result = result_or_error.return_register_value; - // 30. If result.[[Type]] is normal and result.[[Value]] is empty, then + // 32. If result.[[Type]] is normal and result.[[Value]] is empty, then // a. Set result to NormalCompletion(undefined). - // NOTE: Step 31 and 32 is handled by `pop_guard` above. - // 33. Return ? result. - // NOTE: Step 33 is also performed with each use of `TRY` above. + // NOTE: Step 33 and 34 is handled by `pop_guard` above. + // 35. Return ? result. + // NOTE: Step 35 is also performed with each use of `TRY` above. return eval_result.value_or(js_undefined()); } diff --git a/Libraries/LibJS/Runtime/AsyncFunctionConstructor.cpp b/Libraries/LibJS/Runtime/AsyncFunctionConstructor.cpp index e37fc15dab1..4e2f2c0b3ed 100644 --- a/Libraries/LibJS/Runtime/AsyncFunctionConstructor.cpp +++ b/Libraries/LibJS/Runtime/AsyncFunctionConstructor.cpp @@ -41,15 +41,22 @@ ThrowCompletionOr> AsyncFunctionConstructor::construct(FunctionO { auto& vm = this->vm(); + ReadonlySpan arguments = vm.running_execution_context().arguments; + + ReadonlySpan parameter_args = arguments; + if (!parameter_args.is_empty()) + parameter_args = parameter_args.slice(0, parameter_args.size() - 1); + // 1. Let C be the active function object. auto* constructor = vm.active_function_object(); // 2. If bodyArg is not present, set bodyArg to the empty String. - // NOTE: This does that, as well as the string extraction done inside of CreateDynamicFunction - auto extracted = TRY(extract_parameter_arguments_and_body(vm, vm.running_execution_context().arguments)); + Value body_arg = &vm.empty_string(); + if (!arguments.is_empty()) + body_arg = arguments.last(); // 3. Return ? CreateDynamicFunction(C, NewTarget, async, parameterArgs, bodyArg). - return TRY(FunctionConstructor::create_dynamic_function(vm, *constructor, &new_target, FunctionKind::Async, extracted.parameters, extracted.body)); + return TRY(FunctionConstructor::create_dynamic_function(vm, *constructor, &new_target, FunctionKind::Async, parameter_args, body_arg)); } } diff --git a/Libraries/LibJS/Runtime/AsyncGeneratorFunctionConstructor.cpp b/Libraries/LibJS/Runtime/AsyncGeneratorFunctionConstructor.cpp index 0ed9fd202c1..c42c3df3be4 100644 --- a/Libraries/LibJS/Runtime/AsyncGeneratorFunctionConstructor.cpp +++ b/Libraries/LibJS/Runtime/AsyncGeneratorFunctionConstructor.cpp @@ -42,15 +42,22 @@ ThrowCompletionOr> AsyncGeneratorFunctionConstructor::construct( { auto& vm = this->vm(); + ReadonlySpan arguments = vm.running_execution_context().arguments; + + ReadonlySpan parameter_args = arguments; + if (!parameter_args.is_empty()) + parameter_args = parameter_args.slice(0, parameter_args.size() - 1); + // 1. Let C be the active function object. auto* constructor = vm.active_function_object(); // 2. If bodyArg is not present, set bodyArg to the empty String. - // NOTE: This does that, as well as the string extraction done inside of CreateDynamicFunction - auto extracted = TRY(extract_parameter_arguments_and_body(vm, vm.running_execution_context().arguments)); + Value body_arg = &vm.empty_string(); + if (!arguments.is_empty()) + body_arg = arguments.last(); // 3. Return ? CreateDynamicFunction(C, NewTarget, async-generator, parameterArgs, bodyArg). - return TRY(FunctionConstructor::create_dynamic_function(vm, *constructor, &new_target, FunctionKind::AsyncGenerator, extracted.parameters, extracted.body)); + return TRY(FunctionConstructor::create_dynamic_function(vm, *constructor, &new_target, FunctionKind::AsyncGenerator, parameter_args, body_arg)); } } diff --git a/Libraries/LibJS/Runtime/FunctionConstructor.cpp b/Libraries/LibJS/Runtime/FunctionConstructor.cpp index 33780d59934..ef31a364486 100644 --- a/Libraries/LibJS/Runtime/FunctionConstructor.cpp +++ b/Libraries/LibJS/Runtime/FunctionConstructor.cpp @@ -36,33 +36,8 @@ void FunctionConstructor::initialize(Realm& realm) define_direct_property(vm.names.length, Value(1), Attribute::Configurable); } -// NON-STANDARD: Exists to simplify calling CreateDynamicFunction using strong types, instead of a Value. -// Analogous to parts of the following two AO's - and basically just extracts the body and parameters as strings. -// -// 20.2.1.1 Function ( ...parameterArgs, bodyArg ), https://tc39.es/ecma262/#sec-function-p1-p2-pn-body // 20.2.1.1.1 CreateDynamicFunction ( constructor, newTarget, kind, parameterArgs, bodyArg ), https://tc39.es/ecma262/#sec-createdynamicfunction -ThrowCompletionOr extract_parameter_arguments_and_body(VM& vm, Span arguments) -{ - if (arguments.is_empty()) - return ParameterArgumentsAndBody {}; - - auto parameter_values = arguments.slice(0, arguments.size() - 1); - - Vector parameters; - parameters.ensure_capacity(parameter_values.size()); - for (auto const& parameter_value : parameter_values) - parameters.unchecked_append(TRY(parameter_value.to_string(vm))); - - auto body = TRY(arguments.last().to_string(vm)); - - return ParameterArgumentsAndBody { - .parameters = move(parameters), - .body = move(body), - }; -} - -// 20.2.1.1.1 CreateDynamicFunction ( constructor, newTarget, kind, parameterArgs, bodyArg ), https://tc39.es/ecma262/#sec-createdynamicfunction -ThrowCompletionOr> FunctionConstructor::create_dynamic_function(VM& vm, FunctionObject& constructor, FunctionObject* new_target, FunctionKind kind, ReadonlySpan parameter_strings, String const& body_string) +ThrowCompletionOr> FunctionConstructor::create_dynamic_function(VM& vm, FunctionObject& constructor, FunctionObject* new_target, FunctionKind kind, ReadonlySpan parameter_args, Value body_arg) { // 1. If newTarget is undefined, set newTarget to constructor. if (new_target == nullptr) @@ -131,24 +106,28 @@ ThrowCompletionOr> FunctionConstructor::create } // 6. Let argCount be the number of elements in parameterArgs. - auto arg_count = parameter_strings.size(); + auto arg_count = parameter_args.size(); - // NOTE: Done by caller // 7. Let parameterStrings be a new empty List. + Vector parameter_strings; + parameter_strings.ensure_capacity(arg_count); + // 8. For each element arg of parameterArgs, do - // a. Append ? ToString(arg) to parameterStrings. + for (auto const& parameter_value : parameter_args) { + // a. Append ? ToString(arg) to parameterStrings. + parameter_strings.unchecked_append(TRY(parameter_value.to_string(vm))); + } + // 9. Let bodyString be ? ToString(bodyArg). + auto body_string = TRY(body_arg.to_string(vm)); // 10. Let currentRealm be the current Realm Record. auto& realm = *vm.current_realm(); - // 11. Perform ? HostEnsureCanCompileStrings(currentRealm, parameterStrings, bodyString, false). - TRY(vm.host_ensure_can_compile_strings(realm, parameter_strings, body_string, EvalMode::Indirect)); - - // 12. Let P be the empty String. + // 11. Let P be the empty String. String parameters_string; - // 13. If argCount > 0, then + // 12. If argCount > 0, then if (arg_count > 0) { // a. Set P to parameterStrings[0]. // b. Let k be 1. @@ -159,13 +138,16 @@ ThrowCompletionOr> FunctionConstructor::create parameters_string = MUST(String::join(',', parameter_strings)); } - // 14. Let bodyParseString be the string-concatenation of 0x000A (LINE FEED), bodyString, and 0x000A (LINE FEED). + // 13. Let bodyParseString be the string-concatenation of 0x000A (LINE FEED), bodyString, and 0x000A (LINE FEED). auto body_parse_string = ByteString::formatted("\n{}\n", body_string); - // 15. Let sourceString be the string-concatenation of prefix, " anonymous(", P, 0x000A (LINE FEED), ") {", bodyParseString, and "}". - // 16. Let sourceText be StringToCodePoints(sourceString). + // 14. Let sourceString be the string-concatenation of prefix, " anonymous(", P, 0x000A (LINE FEED), ") {", bodyParseString, and "}". + // 15. Let sourceText be StringToCodePoints(sourceString). auto source_text = ByteString::formatted("{} anonymous({}\n) {{{}}}", prefix, parameters_string, body_parse_string); + // 16. Perform ? HostEnsureCanCompileStrings(currentRealm, parameterStrings, bodyString, sourceString, FUNCTION, parameterArgs, bodyArg). + TRY(vm.host_ensure_can_compile_strings(realm, parameter_strings, body_string, source_text, CompilationType::Function, parameter_args, body_arg)); + u8 parse_options = FunctionNodeParseOptions::CheckForFunctionAndName; if (kind == FunctionKind::Async || kind == FunctionKind::AsyncGenerator) parse_options |= FunctionNodeParseOptions::IsAsyncFunction; @@ -264,15 +246,22 @@ ThrowCompletionOr> FunctionConstructor::construct(FunctionObject { auto& vm = this->vm(); + ReadonlySpan arguments = vm.running_execution_context().arguments; + + ReadonlySpan parameter_args = arguments; + if (!parameter_args.is_empty()) + parameter_args = parameter_args.slice(0, parameter_args.size() - 1); + // 1. Let C be the active function object. auto* constructor = vm.active_function_object(); // 2. If bodyArg is not present, set bodyArg to the empty String. - // NOTE: This does that, as well as the string extraction done inside of CreateDynamicFunction - auto extracted = TRY(extract_parameter_arguments_and_body(vm, vm.running_execution_context().arguments)); + Value body_arg = &vm.empty_string(); + if (!arguments.is_empty()) + body_arg = arguments.last(); // 3. Return ? CreateDynamicFunction(C, NewTarget, normal, parameterArgs, bodyArg). - return TRY(create_dynamic_function(vm, *constructor, &new_target, FunctionKind::Normal, extracted.parameters, extracted.body)); + return TRY(create_dynamic_function(vm, *constructor, &new_target, FunctionKind::Normal, parameter_args, body_arg)); } } diff --git a/Libraries/LibJS/Runtime/FunctionConstructor.h b/Libraries/LibJS/Runtime/FunctionConstructor.h index f69ef24493b..aa2a7c8bf62 100644 --- a/Libraries/LibJS/Runtime/FunctionConstructor.h +++ b/Libraries/LibJS/Runtime/FunctionConstructor.h @@ -11,19 +11,12 @@ namespace JS { -struct ParameterArgumentsAndBody { - Vector parameters; - String body; -}; - -JS_API ThrowCompletionOr extract_parameter_arguments_and_body(VM&, Span arguments); - class JS_API FunctionConstructor final : public NativeFunction { JS_OBJECT(FunctionConstructor, NativeFunction); GC_DECLARE_ALLOCATOR(FunctionConstructor); public: - static ThrowCompletionOr> create_dynamic_function(VM&, FunctionObject& constructor, FunctionObject* new_target, FunctionKind kind, ReadonlySpan parameter_args, String const& body_string); + static ThrowCompletionOr> create_dynamic_function(VM&, FunctionObject& constructor, FunctionObject* new_target, FunctionKind kind, ReadonlySpan parameter_args, Value body_arg); virtual void initialize(Realm&) override; virtual ~FunctionConstructor() override = default; diff --git a/Libraries/LibJS/Runtime/GeneratorFunctionConstructor.cpp b/Libraries/LibJS/Runtime/GeneratorFunctionConstructor.cpp index 2c0632d33ad..a7257207e55 100644 --- a/Libraries/LibJS/Runtime/GeneratorFunctionConstructor.cpp +++ b/Libraries/LibJS/Runtime/GeneratorFunctionConstructor.cpp @@ -40,15 +40,22 @@ ThrowCompletionOr> GeneratorFunctionConstructor::construct(Funct { auto& vm = this->vm(); + ReadonlySpan arguments = vm.running_execution_context().arguments; + + ReadonlySpan parameter_args = arguments; + if (!parameter_args.is_empty()) + parameter_args = parameter_args.slice(0, parameter_args.size() - 1); + // 1. Let C be the active function object. auto* constructor = vm.active_function_object(); // 2. If bodyArg is not present, set bodyArg to the empty String. - // NOTE: This does that, as well as the string extraction done inside of CreateDynamicFunction - auto extracted = TRY(extract_parameter_arguments_and_body(vm, vm.running_execution_context().arguments)); + Value body_arg = &vm.empty_string(); + if (!arguments.is_empty()) + body_arg = arguments.last(); // 3. Return ? CreateDynamicFunction(C, NewTarget, generator, parameterArgs, bodyArg). - return TRY(FunctionConstructor::create_dynamic_function(vm, *constructor, &new_target, FunctionKind::Generator, extracted.parameters, extracted.body)); + return TRY(FunctionConstructor::create_dynamic_function(vm, *constructor, &new_target, FunctionKind::Generator, parameter_args, body_arg)); } } diff --git a/Libraries/LibJS/Runtime/ShadowRealm.cpp b/Libraries/LibJS/Runtime/ShadowRealm.cpp index a3e9b536d02..8420c8e58ed 100644 --- a/Libraries/LibJS/Runtime/ShadowRealm.cpp +++ b/Libraries/LibJS/Runtime/ShadowRealm.cpp @@ -93,15 +93,37 @@ ThrowCompletionOr copy_name_and_length(VM& vm, FunctionObject& function, F } // 3.1.3 PerformShadowRealmEval ( sourceText: a String, callerRealm: a Realm Record, evalRealm: a Realm Record, ), https://tc39.es/proposal-shadowrealm/#sec-performshadowrealmeval -ThrowCompletionOr perform_shadow_realm_eval(VM& vm, StringView source_text, Realm& caller_realm, Realm& eval_realm) +ThrowCompletionOr perform_shadow_realm_eval(VM& vm, Value source, Realm& caller_realm, Realm& eval_realm) { - // 1. Perform ? HostEnsureCanCompileStrings(evalRealm, « », sourceText, false). - TRY(vm.host_ensure_can_compile_strings(eval_realm, {}, source_text, EvalMode::Indirect)); + // 1. Perform ? HostEnsureCanCompileStrings(evalRealm, « », sourceText, false). + // FIXME: ShadowRealm doesn't yet intersect with the Dynamic Code Brand Checks proposal. + // This is a close approximation, following the PerformEval implementation. + // See: https://github.com/tc39/proposal-dynamic-code-brand-checks/issues/19 + // https://github.com/tc39/proposal-shadowrealm/issues/414 + GC::Ptr source_text; + + if (source.is_string()) { + source_text = source.as_string(); + } else if (source.is_object()) { + auto code = vm.host_get_code_for_eval(source.as_object()); + + if (code) { + source_text = code; + } else { + return vm.throw_completion(ErrorType::NotAString, source); + } + } else { + return vm.throw_completion(ErrorType::NotAString, source); + } + + VERIFY(source_text); + + TRY(vm.host_ensure_can_compile_strings(eval_realm, {}, source_text->utf8_string_view(), source_text->utf8_string_view(), CompilationType::IndirectEval, {}, source)); // 2. Perform the following substeps in an implementation-defined order, possibly interleaving parsing and error detection: // a. Let script be ParseText(StringToCodePoints(sourceText), Script). - auto parser = Parser(Lexer(source_text), Program::Type::Script, Parser::EvalInitialState {}); + auto parser = Parser(Lexer(source_text->utf8_string_view()), Program::Type::Script, Parser::EvalInitialState {}); auto program = parser.parse_program(); // b. If script is a List of errors, throw a SyntaxError exception. diff --git a/Libraries/LibJS/Runtime/ShadowRealm.h b/Libraries/LibJS/Runtime/ShadowRealm.h index 17fe6e31b74..0daee2914dd 100644 --- a/Libraries/LibJS/Runtime/ShadowRealm.h +++ b/Libraries/LibJS/Runtime/ShadowRealm.h @@ -34,7 +34,7 @@ private: }; ThrowCompletionOr copy_name_and_length(VM&, FunctionObject& function, FunctionObject& target, Optional prefix = {}, Optional arg_count = {}); -ThrowCompletionOr perform_shadow_realm_eval(VM&, StringView source_text, Realm& caller_realm, Realm& eval_realm); +ThrowCompletionOr perform_shadow_realm_eval(VM&, Value source, Realm& caller_realm, Realm& eval_realm); ThrowCompletionOr shadow_realm_import_value(VM&, String specifier_string, String export_name_string, Realm& caller_realm, Realm& eval_realm); ThrowCompletionOr get_wrapped_value(VM&, Realm& caller_realm, Value); NonnullOwnPtr get_shadow_realm_context(Realm& shadow_realm, bool strict_eval, u32 registers_and_constants_and_locals_count); diff --git a/Libraries/LibJS/Runtime/ShadowRealmPrototype.cpp b/Libraries/LibJS/Runtime/ShadowRealmPrototype.cpp index ec04b797c92..6a3fdf92eca 100644 --- a/Libraries/LibJS/Runtime/ShadowRealmPrototype.cpp +++ b/Libraries/LibJS/Runtime/ShadowRealmPrototype.cpp @@ -41,8 +41,7 @@ JS_DEFINE_NATIVE_FUNCTION(ShadowRealmPrototype::evaluate) auto object = TRY(typed_this_object(vm)); // 3. If Type(sourceText) is not String, throw a TypeError exception. - if (!source_text.is_string()) - return vm.throw_completion(ErrorType::NotAString, source_text); + // FIXME: This step is intentionally skipped, see perform_shadow_realm_eval. // 4. Let callerRealm be the current Realm Record. auto* caller_realm = vm.current_realm(); @@ -51,7 +50,7 @@ JS_DEFINE_NATIVE_FUNCTION(ShadowRealmPrototype::evaluate) auto& eval_realm = object->shadow_realm(); // 6. Return ? PerformShadowRealmEval(sourceText, callerRealm, evalRealm). - return perform_shadow_realm_eval(vm, source_text.as_string().utf8_string_view(), *caller_realm, eval_realm); + return perform_shadow_realm_eval(vm, source_text, *caller_realm, eval_realm); } // 3.4.2 ShadowRealm.prototype.importValue ( specifier, exportName ), https://tc39.es/proposal-shadowrealm/#sec-shadowrealm.prototype.importvalue diff --git a/Libraries/LibJS/Runtime/VM.cpp b/Libraries/LibJS/Runtime/VM.cpp index 19471a812df..1fa937c8f84 100644 --- a/Libraries/LibJS/Runtime/VM.cpp +++ b/Libraries/LibJS/Runtime/VM.cpp @@ -124,8 +124,20 @@ VM::VM(ErrorMessages error_messages) return Vector { "type"_string }; }; - // 19.2.1.2 HostEnsureCanCompileStrings ( calleeRealm, parameterStrings, bodyString, direct ), https://tc39.es/ecma262/#sec-hostensurecancompilestrings - host_ensure_can_compile_strings = [](Realm&, ReadonlySpan, StringView, EvalMode) -> ThrowCompletionOr { + // 1 HostGetCodeForEval ( argument ), https://tc39.es/proposal-dynamic-code-brand-checks/#sec-hostgetcodeforeval + host_get_code_for_eval = [](Object const&) -> GC::Ptr { + // The host-defined abstract operation HostGetCodeForEval takes argument argument (an Object) and returns a + // String or NO-CODE. It allows host environments to return a String of code from argument to be used by eval, + // rather than eval returning argument. + // + // argument represents the Object to be checked for code. + // + // The default implementation of HostGetCodeForEval is to return NO-CODE. + return {}; + }; + + // 2 HostEnsureCanCompileStrings ( calleeRealm, parameterStrings, bodyString, codeString, compilationType, parameterArgs, bodyArg ), https://tc39.es/proposal-dynamic-code-brand-checks/#sec-hostensurecancompilestrings + host_ensure_can_compile_strings = [](Realm&, ReadonlySpan, StringView, StringView, CompilationType, ReadonlySpan, Value) -> ThrowCompletionOr { // The host-defined abstract operation HostEnsureCanCompileStrings takes arguments calleeRealm (a Realm Record), // parameterStrings (a List of Strings), bodyString (a String), and direct (a Boolean) and returns either a normal // completion containing unused or a throw completion. diff --git a/Libraries/LibJS/Runtime/VM.h b/Libraries/LibJS/Runtime/VM.h index c8ea2b81128..944630adfd1 100644 --- a/Libraries/LibJS/Runtime/VM.h +++ b/Libraries/LibJS/Runtime/VM.h @@ -45,6 +45,12 @@ enum class EvalMode { Indirect }; +enum class CompilationType { + DirectEval, + IndirectEval, + Function, +}; + class JS_API VM : public RefCounted { public: static NonnullRefPtr create(); @@ -284,7 +290,8 @@ public: Function host_enqueue_finalization_registry_cleanup_job; Function()>>, Realm*)> host_enqueue_promise_job; Function(FunctionObject&)> host_make_job_callback; - Function(Realm&, ReadonlySpan, StringView, EvalMode)> host_ensure_can_compile_strings; + Function(Object const&)> host_get_code_for_eval; + Function(Realm&, ReadonlySpan, StringView, StringView, CompilationType, ReadonlySpan, Value)> host_ensure_can_compile_strings; Function(Object&)> host_ensure_can_add_private_element; Function(ArrayBuffer&, size_t)> host_resize_array_buffer; Function host_unrecognized_date_string;