1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 11:28:12 +00:00

LibJS: Add missing steps and spec comments to PerformEval

While adding spec comments to PerformEval, I noticed we were missing
multiple steps.

Namely, these were:
- Checking if the host will allow us to compile the string
  (allowing LibWeb to perform CSP for eval)
- The parser's initial state depending on the environment around us
  on direct eval:
   - Allowing new.target via eval in functions
   - Allowing super calls and super properties via eval in classes
   - Disallowing the use of the arguments object in class field
     initializers at eval's parse time
- Setting ScriptOrModule of eval's execution context

The spec allows us to apply the additional parsing steps in any order.
The method I have gone with is passing in a struct to the parser's
constructor, which overrides the parser's initial state to (dis)allow
the things stated above from the get-go.
This commit is contained in:
Luke Wilde 2022-04-10 00:55:45 +01:00 committed by Linus Groh
parent f4f850aaf2
commit 34f902fb52
8 changed files with 185 additions and 39 deletions

View file

@ -1631,7 +1631,7 @@ ThrowCompletionOr<ClassElement::ClassValue> ClassField::class_element_evaluation
// FIXME: A potential optimization is not creating the functions here since these are never directly accessible.
auto function_code = create_ast_node<ClassFieldInitializerStatement>(m_initializer->source_range(), copy_initializer.release_nonnull(), name);
initializer = make_handle(ECMAScriptFunctionObject::create(interpreter.global_object(), String::empty(), String::empty(), *function_code, {}, 0, interpreter.lexical_environment(), interpreter.vm().running_execution_context().private_environment, FunctionKind::Normal, true, false, m_contains_direct_call_to_eval, false));
initializer = make_handle(ECMAScriptFunctionObject::create(interpreter.global_object(), String::empty(), String::empty(), *function_code, {}, 0, interpreter.lexical_environment(), interpreter.vm().running_execution_context().private_environment, FunctionKind::Normal, true, false, m_contains_direct_call_to_eval, false, property_key_or_private_name));
initializer->make_method(target);
}

View file

@ -398,10 +398,16 @@ Parser::ParserState::ParserState(Lexer l, Program::Type program_type)
current_token = lexer.next();
}
Parser::Parser(Lexer lexer, Program::Type program_type)
Parser::Parser(Lexer lexer, Program::Type program_type, Optional<EvalInitialState> initial_state_for_eval)
: m_state(move(lexer), program_type)
, m_program_type(program_type)
{
if (initial_state_for_eval.has_value()) {
m_state.in_eval_function_context = initial_state_for_eval->in_eval_function_context;
m_state.allow_super_property_lookup = initial_state_for_eval->allow_super_property_lookup;
m_state.allow_super_constructor_call = initial_state_for_eval->allow_super_constructor_call;
m_state.in_class_field_initializer = initial_state_for_eval->in_class_field_initializer;
}
}
Associativity Parser::operator_associativity(TokenType type) const
@ -1448,7 +1454,7 @@ Parser::PrimaryExpressionParseResult Parser::parse_primary_expression()
auto new_start = position();
auto new_target_result = try_parse_new_target_expression();
if (!new_target_result.is_null()) {
if (!m_state.in_function_context && !m_state.in_class_static_init_block)
if (!m_state.in_function_context && !m_state.in_eval_function_context && !m_state.in_class_static_init_block)
syntax_error("'new.target' not allowed outside of a function", new_start);
return { new_target_result.release_nonnull() };
}

View file

@ -43,7 +43,14 @@ class ScopePusher;
class Parser {
public:
explicit Parser(Lexer lexer, Program::Type program_type = Program::Type::Script);
struct EvalInitialState {
bool in_eval_function_context { false };
bool allow_super_property_lookup { false };
bool allow_super_constructor_call { false };
bool in_class_field_initializer { false };
};
explicit Parser(Lexer lexer, Program::Type program_type = Program::Type::Script, Optional<EvalInitialState> initial_state_for_eval = {});
NonnullRefPtr<Program> parse_program(bool starts_in_strict_mode = false);
@ -300,6 +307,7 @@ private:
bool allow_super_property_lookup { false };
bool allow_super_constructor_call { false };
bool in_function_context { false };
bool in_eval_function_context { false }; // This controls if we allow new.target or not. Note that eval("return") is not allowed, so we have to have a separate state variable for eval.
bool in_formal_parameter_context { false };
bool in_generator_function_context { false };
bool await_expression_is_valid { false };

View file

@ -514,17 +514,86 @@ ThrowCompletionOr<Reference> make_super_property_reference(GlobalObject& global_
// 19.2.1.1 PerformEval ( x, callerRealm, strictCaller, direct ), https://tc39.es/ecma262/#sec-performeval
ThrowCompletionOr<Value> perform_eval(Value x, GlobalObject& caller_realm, 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& vm = caller_realm.vm();
auto& eval_realm = vm.running_execution_context().realm;
// 3. Let evalRealm be the current Realm Record.
auto& eval_realm = *vm.running_execution_context().realm;
// 4. Perform ? HostEnsureCanCompileStrings(callerRealm, evalRealm).
TRY(vm.host_ensure_can_compile_strings(*caller_realm.associated_realm(), eval_realm));
// 5. Let inFunction be false.
bool in_function = false;
// 6. Let inMethod be false.
bool in_method = false;
// 7. Let inDerivedConstructor be false.
bool in_derived_constructor = false;
// 8. Let inClassFieldInitializer be false.
bool in_class_field_initializer = false;
// 9. If direct is true, then
if (direct == EvalMode::Direct) {
// a. Let thisEnvRec be GetThisEnvironment().
auto& this_environment_record = get_this_environment(vm);
// b. If thisEnvRec is a function Environment Record, then
if (is<FunctionEnvironment>(this_environment_record)) {
auto& this_function_environment_record = static_cast<FunctionEnvironment&>(this_environment_record);
// i. Let F be thisEnvRec.[[FunctionObject]].
auto& function = this_function_environment_record.function_object();
// ii. Set inFunction to true.
in_function = true;
// iii. Set inMethod to thisEnvRec.HasSuperBinding().
in_method = this_function_environment_record.has_super_binding();
// iv. If F.[[ConstructorKind]] is derived, set inDerivedConstructor to true.
if (function.constructor_kind() == ECMAScriptFunctionObject::ConstructorKind::Derived)
in_derived_constructor = true;
// v. Let classFieldInitializerName be F.[[ClassFieldInitializerName]].
auto& class_field_initializer_name = function.class_field_initializer_name();
// vi. If classFieldInitializerName is not empty, set inClassFieldInitializer to true.
if (!class_field_initializer_name.has<Empty>())
in_class_field_initializer = true;
}
}
// 10. 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.
// NOTE: We do these next steps by passing initial state to the parser.
// e. If inFunction is false, and body Contains NewTarget, throw a SyntaxError exception.
// f. If inMethod is false, and body Contains SuperProperty, throw a SyntaxError exception.
// g. If inDerivedConstructor is false, and body Contains SuperCall, throw a SyntaxError exception.
// h. If inClassFieldInitializer is true, and ContainsArguments of body is true, throw a SyntaxError exception.
auto& code_string = x.as_string();
Parser parser { Lexer { code_string.string() } };
Parser::EvalInitialState initial_state {
.in_eval_function_context = in_function,
.allow_super_property_lookup = in_method,
.allow_super_constructor_call = in_derived_constructor,
.in_class_field_initializer = in_class_field_initializer,
};
Parser parser { Lexer { code_string.string() }, 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.
if (parser.has_errors()) {
auto& error = parser.errors()[0];
return vm.throw_completion<SyntaxError>(caller_realm, error.to_string());
@ -534,21 +603,38 @@ ThrowCompletionOr<Value> perform_eval(Value x, GlobalObject& caller_realm, Calle
if (program->is_strict_mode())
strict_eval = true;
// 13. Let runningContext be the running execution context.
// 14. 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;
// 15. 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);
// b. Let varEnv be runningContext's VariableEnvironment.
variable_environment = running_context.variable_environment;
// c. Let privateEnv be runningContext's PrivateEnvironment.
private_environment = running_context.private_environment;
} else {
lexical_environment = new_declarative_environment(eval_realm->global_environment());
variable_environment = &eval_realm->global_environment();
}
// 16. Else,
else {
// a. Let lexEnv be NewDeclarativeEnvironment(evalRealm.[[GlobalEnv]]).
lexical_environment = new_declarative_environment(eval_realm.global_environment());
// b. Let varEnv be evalRealm.[[GlobalEnv]].
variable_environment = &eval_realm.global_environment();
// c. Let privateEnv be null.
private_environment = nullptr;
}
// 17. If strictEval is true, set varEnv to lexEnv.
if (strict_eval)
variable_environment = lexical_environment;
@ -562,23 +648,48 @@ ThrowCompletionOr<Value> perform_eval(Value x, GlobalObject& caller_realm, Calle
// 18. If runningContext is not already suspended, suspend runningContext.
// FIXME: We don't have this concept yet.
// 19. Let evalContext be a new ECMAScript code execution context.
ExecutionContext eval_context(vm.heap());
eval_context.realm = eval_realm;
eval_context.variable_environment = variable_environment;
eval_context.lexical_environment = lexical_environment;
eval_context.private_environment = private_environment;
TRY(vm.push_execution_context(eval_context, eval_realm->global_object()));
// 20. Set evalContext's Function to null.
// NOTE: This was done in the construction of eval_context.
// 21. Set evalContext's Realm to evalRealm.
eval_context.realm = &eval_realm;
// 22. Set evalContext's ScriptOrModule to runningContext's ScriptOrModule.
eval_context.script_or_module = running_context.script_or_module;
// 23. Set evalContext's VariableEnvironment to varEnv.
eval_context.variable_environment = variable_environment;
// 24. Set evalContext's LexicalEnvironment to lexEnv.
eval_context.lexical_environment = lexical_environment;
// 25. 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;
// 26. Push evalContext onto the execution context stack; evalContext is now the running execution context.
TRY(vm.push_execution_context(eval_context, eval_realm.global_object()));
// 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 = [&] {
// FIXME: 30. Suspend evalContext and remove it from the execution context stack.
// 31. Resume the context that is now on the top of the execution context stack as the running execution context.
vm.pop_execution_context();
};
TRY(eval_declaration_instantiation(vm, eval_realm->global_object(), program, variable_environment, lexical_environment, private_environment, strict_eval));
TemporaryChange scope_change_strict(vm.running_execution_context().is_strict_mode, strict_eval);
// 27. Let result be Completion(EvalDeclarationInstantiation(body, varEnv, lexEnv, privateEnv, strictEval)).
TRY(eval_declaration_instantiation(vm, eval_realm.global_object(), program, variable_environment, lexical_environment, private_environment, strict_eval));
Optional<Value> eval_result;
// 28. If result.[[Type]] is normal, then
// a. Set result to the result of evaluating body.
if (auto* bytecode_interpreter = Bytecode::Interpreter::current()) {
auto executable_result = Bytecode::Generator::generate(program);
if (executable_result.is_error())
@ -597,6 +708,11 @@ ThrowCompletionOr<Value> perform_eval(Value x, GlobalObject& caller_realm, Calle
eval_result = TRY(program->execute(ast_interpreter, caller_realm));
}
// 29. If result.[[Type]] is normal and result.[[Value]] is empty, then
// a. Set result to NormalCompletion(undefined).
// NOTE: Step 30 and 31 is handled by `pop_guard` above.
// 32. Return ? result.
// NOTE: Step 32 is also performed with each use of `TRY` above.
return eval_result.value_or(js_undefined());
}

View file

@ -29,7 +29,7 @@
namespace JS {
ECMAScriptFunctionObject* ECMAScriptFunctionObject::create(GlobalObject& global_object, FlyString name, String source_text, Statement const& ecmascript_code, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, Environment* parent_scope, PrivateEnvironment* private_scope, FunctionKind kind, bool is_strict, bool might_need_arguments_object, bool contains_direct_call_to_eval, bool is_arrow_function)
ECMAScriptFunctionObject* ECMAScriptFunctionObject::create(GlobalObject& global_object, FlyString name, String source_text, Statement const& ecmascript_code, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, Environment* parent_scope, PrivateEnvironment* private_scope, FunctionKind kind, bool is_strict, bool might_need_arguments_object, bool contains_direct_call_to_eval, bool is_arrow_function, Variant<PropertyKey, PrivateName, Empty> class_field_initializer_name)
{
Object* prototype = nullptr;
switch (kind) {
@ -46,15 +46,15 @@ ECMAScriptFunctionObject* ECMAScriptFunctionObject::create(GlobalObject& global_
prototype = global_object.async_generator_function_prototype();
break;
}
return global_object.heap().allocate<ECMAScriptFunctionObject>(global_object, move(name), move(source_text), ecmascript_code, move(parameters), m_function_length, parent_scope, private_scope, *prototype, kind, is_strict, might_need_arguments_object, contains_direct_call_to_eval, is_arrow_function);
return global_object.heap().allocate<ECMAScriptFunctionObject>(global_object, move(name), move(source_text), ecmascript_code, move(parameters), m_function_length, parent_scope, private_scope, *prototype, kind, is_strict, might_need_arguments_object, contains_direct_call_to_eval, is_arrow_function, move(class_field_initializer_name));
}
ECMAScriptFunctionObject* ECMAScriptFunctionObject::create(GlobalObject& global_object, FlyString name, Object& prototype, String source_text, Statement const& ecmascript_code, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, Environment* parent_scope, PrivateEnvironment* private_scope, FunctionKind kind, bool is_strict, bool might_need_arguments_object, bool contains_direct_call_to_eval, bool is_arrow_function)
ECMAScriptFunctionObject* ECMAScriptFunctionObject::create(GlobalObject& global_object, FlyString name, Object& prototype, String source_text, Statement const& ecmascript_code, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, Environment* parent_scope, PrivateEnvironment* private_scope, FunctionKind kind, bool is_strict, bool might_need_arguments_object, bool contains_direct_call_to_eval, bool is_arrow_function, Variant<PropertyKey, PrivateName, Empty> class_field_initializer_name)
{
return global_object.heap().allocate<ECMAScriptFunctionObject>(global_object, move(name), move(source_text), ecmascript_code, move(parameters), m_function_length, parent_scope, private_scope, prototype, kind, is_strict, might_need_arguments_object, contains_direct_call_to_eval, is_arrow_function);
return global_object.heap().allocate<ECMAScriptFunctionObject>(global_object, move(name), move(source_text), ecmascript_code, move(parameters), m_function_length, parent_scope, private_scope, prototype, kind, is_strict, might_need_arguments_object, contains_direct_call_to_eval, is_arrow_function, move(class_field_initializer_name));
}
ECMAScriptFunctionObject::ECMAScriptFunctionObject(FlyString name, String source_text, Statement const& ecmascript_code, Vector<FunctionNode::Parameter> formal_parameters, i32 function_length, Environment* parent_scope, PrivateEnvironment* private_scope, Object& prototype, FunctionKind kind, bool strict, bool might_need_arguments_object, bool contains_direct_call_to_eval, bool is_arrow_function)
ECMAScriptFunctionObject::ECMAScriptFunctionObject(FlyString name, String source_text, Statement const& ecmascript_code, Vector<FunctionNode::Parameter> formal_parameters, i32 function_length, Environment* parent_scope, PrivateEnvironment* private_scope, Object& prototype, FunctionKind kind, bool strict, bool might_need_arguments_object, bool contains_direct_call_to_eval, bool is_arrow_function, Variant<PropertyKey, PrivateName, Empty> class_field_initializer_name)
: FunctionObject(prototype)
, m_name(move(name))
, m_function_length(function_length)
@ -64,6 +64,7 @@ ECMAScriptFunctionObject::ECMAScriptFunctionObject(FlyString name, String source
, m_ecmascript_code(ecmascript_code)
, m_realm(global_object().associated_realm())
, m_source_text(move(source_text))
, m_class_field_initializer_name(move(class_field_initializer_name))
, m_strict(strict)
, m_might_need_arguments_object(might_need_arguments_object)
, m_contains_direct_call_to_eval(contains_direct_call_to_eval)

View file

@ -32,10 +32,10 @@ public:
Global,
};
static ECMAScriptFunctionObject* create(GlobalObject&, FlyString name, String source_text, Statement const& ecmascript_code, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, Environment* parent_scope, PrivateEnvironment* private_scope, FunctionKind, bool is_strict, bool might_need_arguments_object = true, bool contains_direct_call_to_eval = true, bool is_arrow_function = false);
static ECMAScriptFunctionObject* create(GlobalObject&, FlyString name, Object& prototype, String source_text, Statement const& ecmascript_code, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, Environment* parent_scope, PrivateEnvironment* private_scope, FunctionKind, bool is_strict, bool might_need_arguments_object = true, bool contains_direct_call_to_eval = true, bool is_arrow_function = false);
static ECMAScriptFunctionObject* create(GlobalObject&, FlyString name, String source_text, Statement const& ecmascript_code, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, Environment* parent_scope, PrivateEnvironment* private_scope, FunctionKind, bool is_strict, bool might_need_arguments_object = true, bool contains_direct_call_to_eval = true, bool is_arrow_function = false, Variant<PropertyKey, PrivateName, Empty> class_field_initializer_name = {});
static ECMAScriptFunctionObject* create(GlobalObject&, FlyString name, Object& prototype, String source_text, Statement const& ecmascript_code, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, Environment* parent_scope, PrivateEnvironment* private_scope, FunctionKind, bool is_strict, bool might_need_arguments_object = true, bool contains_direct_call_to_eval = true, bool is_arrow_function = false, Variant<PropertyKey, PrivateName, Empty> class_field_initializer_name = {});
ECMAScriptFunctionObject(FlyString name, String source_text, Statement const& ecmascript_code, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, Environment* parent_scope, PrivateEnvironment* private_scope, Object& prototype, FunctionKind, bool is_strict, bool might_need_arguments_object, bool contains_direct_call_to_eval, bool is_arrow_function);
ECMAScriptFunctionObject(FlyString name, String source_text, Statement const& ecmascript_code, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, Environment* parent_scope, PrivateEnvironment* private_scope, Object& prototype, FunctionKind, bool is_strict, bool might_need_arguments_object, bool contains_direct_call_to_eval, bool is_arrow_function, Variant<PropertyKey, PrivateName, Empty> class_field_initializer_name);
virtual void initialize(GlobalObject&) override;
virtual ~ECMAScriptFunctionObject() override = default;
@ -91,6 +91,8 @@ public:
// https://html.spec.whatwg.org/multipage/webappapis.html#getting-the-current-value-of-the-event-handler Step 3.11
void set_script_or_module(ScriptOrModule script_or_module) { m_script_or_module = move(script_or_module); }
Variant<PropertyKey, PrivateName, Empty> const& class_field_initializer_name() const { return m_class_field_initializer_name; }
protected:
virtual bool is_strict_mode() const final { return m_strict; }
@ -123,6 +125,7 @@ private:
String m_source_text; // [[SourceText]]
Vector<InstanceField> m_fields; // [[Fields]]
Vector<PrivateElement> m_private_methods; // [[PrivateMethods]]
Variant<PropertyKey, PrivateName, Empty> m_class_field_initializer_name; // [[ClassFieldInitializerName]]
ConstructorKind m_constructor_kind : 1 { ConstructorKind::Base }; // [[ConstructorKind]]
bool m_strict : 1 { false }; // [[Strict]]
bool m_is_class_constructor : 1 { false }; // [[IsClassConstructor]]

View file

@ -131,6 +131,17 @@ VM::VM(OwnPtr<CustomData> custom_data)
return HostResizeArrayBufferResult::Unhandled;
};
// 19.2.1.2 HostEnsureCanCompileStrings ( callerRealm, calleeRealm ), https://tc39.es/ecma262/#sec-hostensurecancompilestrings
host_ensure_can_compile_strings = [](Realm&, Realm&) -> ThrowCompletionOr<void> {
// The host-defined abstract operation HostEnsureCanCompileStrings takes arguments callerRealm (a Realm Record) and calleeRealm (a Realm Record)
// and returns either a normal completion containing unused or an abrupt completion.
// It allows host environments to block certain ECMAScript functions which allow developers to compile strings into ECMAScript code.
// An implementation of HostEnsureCanCompileStrings must conform to the following requirements:
// - If the returned Completion Record is a normal completion, it must be a normal completion containing unused.
// The default implementation of HostEnsureCanCompileStrings is to return NormalCompletion(unused).
return {};
};
#define __JS_ENUMERATE(SymbolName, snake_name) \
m_well_known_symbol_##snake_name = js_symbol(*this, "Symbol." #SymbolName, false);
JS_ENUMERATE_WELL_KNOWN_SYMBOLS

View file

@ -234,6 +234,7 @@ public:
Function<void(Function<ThrowCompletionOr<Value>()>, Realm*)> host_enqueue_promise_job;
Function<JobCallback(FunctionObject&)> host_make_job_callback;
Function<ThrowCompletionOr<HostResizeArrayBufferResult>(GlobalObject&, size_t)> host_resize_array_buffer;
Function<ThrowCompletionOr<void>(Realm&, Realm&)> host_ensure_can_compile_strings;
private:
explicit VM(OwnPtr<CustomData>);