1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 22:37:35 +00:00

LibJS: Convert ScopeNode declaration functions to ThrowCompletionOr

This removes a number of vm.exception() checks which are now caught
directly by TRY. Make use of these checks in
{Global, Eval}DeclarationInstantiation and while we're here add spec
comments.
This commit is contained in:
davidot 2022-02-07 13:31:01 +01:00 committed by Linus Groh
parent cc7a117f0f
commit 4136cbdb09
6 changed files with 462 additions and 305 deletions

View file

@ -256,10 +256,21 @@ Completion FunctionDeclaration::execute(Interpreter& interpreter, GlobalObject&
if (m_is_hoisted) {
// Perform special annexB steps see step 3 of: https://tc39.es/ecma262/#sec-web-compat-functiondeclarationinstantiation
// i. Let genv be the running execution context's VariableEnvironment.
auto* variable_environment = interpreter.vm().running_execution_context().variable_environment;
// ii. Let benv be the running execution context's LexicalEnvironment.
auto* lexical_environment = interpreter.vm().running_execution_context().lexical_environment;
// iii. Let fobj be ! benv.GetBindingValue(F, false).
auto function_object = MUST(lexical_environment->get_binding_value(global_object, name(), false));
MUST(variable_environment->set_mutable_binding(global_object, name(), function_object, false));
// iv. Perform ? genv.SetMutableBinding(F, fobj, false).
TRY(variable_environment->set_mutable_binding(global_object, name(), function_object, false));
// v. Return NormalCompletion(empty).
return normal_completion({});
}
// 1. Return NormalCompletion(empty).
@ -2113,10 +2124,12 @@ void ClassDeclaration::dump(int indent) const
m_class_expression->dump(indent + 1);
}
void ClassDeclaration::for_each_bound_name(IteratorOrVoidFunction<FlyString const&> callback) const
ThrowCompletionOr<void> ClassDeclaration::for_each_bound_name(ThrowCompletionOrVoidCallback<FlyString const&>&& callback) const
{
if (!m_class_expression->name().is_empty())
callback(m_class_expression->name());
if (m_class_expression->name().is_empty())
return {};
return callback(m_class_expression->name());
}
void ClassExpression::dump(int indent) const
@ -2241,6 +2254,23 @@ bool BindingPattern::contains_expression() const
return false;
}
ThrowCompletionOr<void> BindingPattern::for_each_bound_name(ThrowCompletionOrVoidCallback<FlyString const&>&& callback) const
{
for (auto const& entry : entries) {
auto const& alias = entry.alias;
if (alias.has<NonnullRefPtr<Identifier>>()) {
TRY(callback(alias.get<NonnullRefPtr<Identifier>>()->string()));
} else if (alias.has<NonnullRefPtr<BindingPattern>>()) {
TRY(alias.get<NonnullRefPtr<BindingPattern>>()->for_each_bound_name(forward<decltype(callback)>(callback)));
} else {
auto const& name = entry.name;
if (name.has<NonnullRefPtr<Identifier>>())
TRY(callback(name.get<NonnullRefPtr<Identifier>>()->string()));
}
}
return {};
}
void BindingPattern::dump(int indent) const
{
print_indent(indent);
@ -2324,10 +2354,11 @@ void FunctionDeclaration::dump(int indent) const
FunctionNode::dump(indent, class_name());
}
void FunctionDeclaration::for_each_bound_name(IteratorOrVoidFunction<FlyString const&> callback) const
ThrowCompletionOr<void> FunctionDeclaration::for_each_bound_name(ThrowCompletionOrVoidCallback<const FlyString&>&& callback) const
{
if (!name().is_empty())
callback(name());
if (name().is_empty())
return {};
return callback(name());
}
void FunctionExpression::dump(int indent) const
@ -2861,19 +2892,21 @@ Completion VariableDeclarator::execute(Interpreter& interpreter, GlobalObject&)
VERIFY_NOT_REACHED();
}
void VariableDeclaration::for_each_bound_name(IteratorOrVoidFunction<FlyString const&> callback) const
ThrowCompletionOr<void> VariableDeclaration::for_each_bound_name(ThrowCompletionOrVoidCallback<FlyString const&>&& callback) const
{
for (auto& entry : declarations()) {
entry.target().template visit(
[&](const NonnullRefPtr<Identifier>& id) {
callback(id->string());
for (auto const& entry : declarations()) {
TRY(entry.target().visit(
[&](NonnullRefPtr<Identifier> const& id) {
return callback(id->string());
},
[&](const NonnullRefPtr<BindingPattern>& binding) {
binding->for_each_bound_name([&](const auto& name) {
callback(name);
[&](NonnullRefPtr<BindingPattern> const& binding) {
return binding->for_each_bound_name([&](auto const& name) {
return callback(name);
});
});
}));
}
return {};
}
void VariableDeclaration::dump(int indent) const
@ -4097,76 +4130,62 @@ Completion DebuggerStatement::execute(Interpreter& interpreter, GlobalObject&) c
// 3. Return result.
return result;
}
void ScopeNode::for_each_lexically_scoped_declaration(IteratorOrVoidFunction<Declaration const&>&& callback) const
ThrowCompletionOr<void> ScopeNode::for_each_lexically_scoped_declaration(ThrowCompletionOrVoidCallback<Declaration const&>&& callback) const
{
for (auto& declaration : m_lexical_declarations) {
if (callback(declaration) == IterationDecision::Break)
break;
}
for (auto& declaration : m_lexical_declarations)
TRY(callback(declaration));
return {};
}
void ScopeNode::for_each_lexically_declared_name(IteratorOrVoidFunction<FlyString const&>&& callback) const
ThrowCompletionOr<void> ScopeNode::for_each_lexically_declared_name(ThrowCompletionOrVoidCallback<FlyString const&>&& callback) const
{
auto running = true;
for (auto& declaration : m_lexical_declarations) {
declaration.for_each_bound_name([&](auto const& name) {
if (callback(name) == IterationDecision::Break) {
running = false;
return IterationDecision::Break;
}
return IterationDecision::Continue;
});
if (!running)
break;
for (auto const& declaration : m_lexical_declarations) {
TRY(declaration.for_each_bound_name([&](auto const& name) {
return callback(name);
}));
}
return {};
}
void ScopeNode::for_each_var_declared_name(IteratorOrVoidFunction<FlyString const&>&& callback) const
ThrowCompletionOr<void> ScopeNode::for_each_var_declared_name(ThrowCompletionOrVoidCallback<FlyString const&>&& callback) const
{
auto running = true;
for (auto& declaration : m_var_declarations) {
declaration.for_each_bound_name([&](auto const& name) {
if (callback(name) == IterationDecision::Break) {
running = false;
return IterationDecision::Break;
}
return IterationDecision::Continue;
});
if (!running)
break;
TRY(declaration.for_each_bound_name([&](auto const& name) {
return callback(name);
}));
}
return {};
}
void ScopeNode::for_each_var_function_declaration_in_reverse_order(IteratorOrVoidFunction<FunctionDeclaration const&>&& callback) const
ThrowCompletionOr<void> ScopeNode::for_each_var_function_declaration_in_reverse_order(ThrowCompletionOrVoidCallback<FunctionDeclaration const&>&& callback) const
{
for (ssize_t i = m_var_declarations.size() - 1; i >= 0; i--) {
auto& declaration = m_var_declarations[i];
if (is<FunctionDeclaration>(declaration)) {
if (callback(static_cast<FunctionDeclaration const&>(declaration)) == IterationDecision::Break)
break;
}
if (is<FunctionDeclaration>(declaration))
TRY(callback(static_cast<FunctionDeclaration const&>(declaration)));
}
return {};
}
void ScopeNode::for_each_var_scoped_variable_declaration(IteratorOrVoidFunction<VariableDeclaration const&>&& callback) const
ThrowCompletionOr<void> ScopeNode::for_each_var_scoped_variable_declaration(ThrowCompletionOrVoidCallback<VariableDeclaration const&>&& callback) const
{
for (auto& declaration : m_var_declarations) {
if (!is<FunctionDeclaration>(declaration)) {
VERIFY(is<VariableDeclaration>(declaration));
if (callback(static_cast<VariableDeclaration const&>(declaration)) == IterationDecision::Break)
break;
TRY(callback(static_cast<VariableDeclaration const&>(declaration)));
}
}
return {};
}
void ScopeNode::for_each_function_hoistable_with_annexB_extension(IteratorOrVoidFunction<FunctionDeclaration&>&& callback) const
ThrowCompletionOr<void> ScopeNode::for_each_function_hoistable_with_annexB_extension(ThrowCompletionOrVoidCallback<FunctionDeclaration&>&& callback) const
{
for (auto& function : m_functions_hoistable_with_annexB_extension) {
// We need const_cast here since it might have to set a property on function declaration.
if (callback(const_cast<FunctionDeclaration&>(function)) == IterationDecision::Break)
break;
TRY(callback(const_cast<FunctionDeclaration&>(function)));
}
return {};
}
void ScopeNode::add_lexical_declaration(NonnullRefPtr<Declaration> declaration)
@ -4362,6 +4381,7 @@ void ScopeNode::block_declaration_instantiation(GlobalObject& global_object, Env
// See also B.3.2.6 Changes to BlockDeclarationInstantiation, https://tc39.es/ecma262/#sec-web-compat-blockdeclarationinstantiation
VERIFY(environment);
auto* private_environment = global_object.vm().running_execution_context().private_environment;
// Note: All the calls here are ! and thus we do not need to TRY this callback.
for_each_lexically_scoped_declaration([&](Declaration const& declaration) {
auto is_constant_declaration = declaration.is_constant_declaration();
declaration.for_each_bound_name([&](auto const& name) {
@ -4385,150 +4405,196 @@ void ScopeNode::block_declaration_instantiation(GlobalObject& global_object, Env
// 16.1.7 GlobalDeclarationInstantiation ( script, env ), https://tc39.es/ecma262/#sec-globaldeclarationinstantiation
ThrowCompletionOr<void> Program::global_declaration_instantiation(Interpreter& interpreter, GlobalObject& global_object, GlobalEnvironment& global_environment) const
{
for_each_lexically_declared_name([&](FlyString const& name) {
if (global_environment.has_var_declaration(name) || global_environment.has_lexical_declaration(name)) {
interpreter.vm().throw_exception<SyntaxError>(global_object, ErrorType::TopLevelVariableAlreadyDeclared, name);
return IterationDecision::Break;
}
// 1. Let lexNames be the LexicallyDeclaredNames of script.
// 2. Let varNames be the VarDeclaredNames of script.
// 3. For each element name of lexNames, do
TRY(for_each_lexically_declared_name([&](FlyString const& name) -> ThrowCompletionOr<void> {
// a. If env.HasVarDeclaration(name) is true, throw a SyntaxError exception.
if (global_environment.has_var_declaration(name))
return interpreter.vm().throw_completion<SyntaxError>(global_object, ErrorType::TopLevelVariableAlreadyDeclared, name);
auto restricted_global_or_error = global_environment.has_restricted_global_property(name);
if (restricted_global_or_error.is_error())
return IterationDecision::Break;
auto restricted_global = restricted_global_or_error.release_value();
// b. If env.HasLexicalDeclaration(name) is true, throw a SyntaxError exception.
if (global_environment.has_lexical_declaration(name))
return interpreter.vm().throw_completion<SyntaxError>(global_object, ErrorType::TopLevelVariableAlreadyDeclared, name);
if (restricted_global)
interpreter.vm().throw_exception<SyntaxError>(global_object, ErrorType::RestrictedGlobalProperty, name);
// c. Let hasRestrictedGlobal be ? env.HasRestrictedGlobalProperty(name).
auto has_restricted_global = TRY(global_environment.has_restricted_global_property(name));
return IterationDecision::Continue;
});
// d. If hasRestrictedGlobal is true, throw a SyntaxError exception.
if (has_restricted_global)
return interpreter.vm().throw_completion<SyntaxError>(global_object, ErrorType::RestrictedGlobalProperty, name);
if (auto* exception = interpreter.exception())
return throw_completion(exception->value());
return {};
}));
for_each_var_declared_name([&](auto const& name) {
if (global_environment.has_lexical_declaration(name)) {
interpreter.vm().throw_exception<SyntaxError>(global_object, ErrorType::TopLevelVariableAlreadyDeclared, name);
return IterationDecision::Break;
}
// 4. For each element name of varNames, do
TRY(for_each_var_declared_name([&](auto const& name) -> ThrowCompletionOr<void> {
// a. If env.HasLexicalDeclaration(name) is true, throw a SyntaxError exception.
if (global_environment.has_lexical_declaration(name))
return interpreter.vm().throw_completion<SyntaxError>(global_object, ErrorType::TopLevelVariableAlreadyDeclared, name);
return IterationDecision::Continue;
});
return {};
}));
if (auto* exception = interpreter.exception())
return throw_completion(exception->value());
HashTable<FlyString> declared_function_names;
// 5. Let varDeclarations be the VarScopedDeclarations of script.
// 6. Let functionsToInitialize be a new empty List.
Vector<FunctionDeclaration const&> functions_to_initialize;
for_each_var_function_declaration_in_reverse_order([&](FunctionDeclaration const& function) {
// 7. Let declaredFunctionNames be a new empty List.
HashTable<FlyString> declared_function_names;
// 8. For each element d of varDeclarations, in reverse List order, do
TRY(for_each_var_function_declaration_in_reverse_order([&](FunctionDeclaration const& function) -> ThrowCompletionOr<void> {
// a. If d is neither a VariableDeclaration nor a ForBinding nor a BindingIdentifier, then
// i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration.
// Note: This is checked in for_each_var_function_declaration_in_reverse_order.
// ii. NOTE: If there are multiple function declarations for the same name, the last declaration is used.
// iii. Let fn be the sole element of the BoundNames of d.
// iv. If fn is not an element of declaredFunctionNames, then
if (declared_function_names.set(function.name()) != AK::HashSetResult::InsertedNewEntry)
return IterationDecision::Continue;
return {};
auto function_definable_or_error = global_environment.can_declare_global_function(function.name());
if (function_definable_or_error.is_error())
return IterationDecision::Break;
auto function_definable = function_definable_or_error.release_value();
// 1. Let fnDefinable be ? env.CanDeclareGlobalFunction(fn).
auto function_definable = TRY(global_environment.can_declare_global_function(function.name()));
if (!function_definable) {
interpreter.vm().throw_exception<TypeError>(global_object, ErrorType::CannotDeclareGlobalFunction, function.name());
return IterationDecision::Break;
}
// 2. If fnDefinable is false, throw a TypeError exception.
if (!function_definable)
return interpreter.vm().throw_completion<TypeError>(global_object, ErrorType::CannotDeclareGlobalFunction, function.name());
// 3. Append fn to declaredFunctionNames.
// Note: Already done in step iv. above.
// 4. Insert d as the first element of functionsToInitialize.
functions_to_initialize.append(function);
return IterationDecision::Continue;
});
if (auto* exception = interpreter.exception())
return throw_completion(exception->value());
return {};
}));
// 9. Let declaredVarNames be a new empty List.
HashTable<FlyString> declared_var_names;
for_each_var_scoped_variable_declaration([&](Declaration const& declaration) {
declaration.for_each_bound_name([&](auto const& name) {
// 10. For each element d of varDeclarations, do
TRY(for_each_var_scoped_variable_declaration([&](Declaration const& declaration) {
// a. If d is a VariableDeclaration, a ForBinding, or a BindingIdentifier, then
// Note: This is done in for_each_var_scoped_variable_declaration.
// i. For each String vn of the BoundNames of d, do
return declaration.for_each_bound_name([&](auto const& name) -> ThrowCompletionOr<void> {
// 1. If vn is not an element of declaredFunctionNames, then
if (declared_function_names.contains(name))
return IterationDecision::Continue;
return {};
auto var_definable_or_error = global_environment.can_declare_global_var(name);
if (var_definable_or_error.is_error())
return IterationDecision::Break;
auto var_definable = var_definable_or_error.release_value();
// a. Let vnDefinable be ? env.CanDeclareGlobalVar(vn).
auto var_definable = TRY(global_environment.can_declare_global_var(name));
if (!var_definable) {
interpreter.vm().throw_exception<TypeError>(global_object, ErrorType::CannotDeclareGlobalVariable, name);
return IterationDecision::Break;
}
// b. If vnDefinable is false, throw a TypeError exception.
if (!var_definable)
return interpreter.vm().throw_completion<TypeError>(global_object, ErrorType::CannotDeclareGlobalVariable, name);
// c. If vn is not an element of declaredVarNames, then
// i. Append vn to declaredVarNames.
declared_var_names.set(name);
return IterationDecision::Continue;
return {};
});
if (interpreter.exception())
return IterationDecision::Break;
return IterationDecision::Continue;
});
}));
if (auto* exception = interpreter.exception())
return throw_completion(exception->value());
// 11. NOTE: No abnormal terminations occur after this algorithm step if the global object is an ordinary object. However, if the global object is a Proxy exotic object it may exhibit behaviours that cause abnormal terminations in some of the following steps.
// 12. NOTE: Annex B.3.2.2 adds additional steps at this point.
// 12. Let strict be IsStrict of script.
// 13. If strict is false, then
if (!m_is_strict_mode) {
for_each_function_hoistable_with_annexB_extension([&](FunctionDeclaration& function_declaration) {
// a. Let declaredFunctionOrVarNames be the list-concatenation of declaredFunctionNames and declaredVarNames.
// b. For each FunctionDeclaration f that is directly contained in the StatementList of a Block, CaseClause, or DefaultClause Contained within script, do
TRY(for_each_function_hoistable_with_annexB_extension([&](FunctionDeclaration& function_declaration) -> ThrowCompletionOr<void> {
// i. Let F be StringValue of the BindingIdentifier of f.
auto& function_name = function_declaration.name();
// ii. If replacing the FunctionDeclaration f with a VariableStatement that has F as a BindingIdentifier would not produce any Early Errors for script, then
// Note: This step is already performed during parsing and for_each_function_hoistable_with_annexB_extension so this always passes here.
// 1. If env.HasLexicalDeclaration(F) is false, then
if (global_environment.has_lexical_declaration(function_name))
return IterationDecision::Continue;
return {};
auto function_definable_or_error = global_environment.can_declare_global_function(function_name);
if (function_definable_or_error.is_error())
return IterationDecision::Break;
auto function_definable = function_definable_or_error.release_value();
// a. Let fnDefinable be ? env.CanDeclareGlobalVar(F).
auto function_definable = TRY(global_environment.can_declare_global_function(function_name));
// b. If fnDefinable is true, then
if (!function_definable) {
interpreter.vm().throw_exception<TypeError>(global_object, ErrorType::CannotDeclareGlobalFunction, function_name);
return IterationDecision::Break;
}
if (!function_definable)
return {};
// i. NOTE: A var binding for F is only instantiated here if it is neither a VarDeclaredName nor the name of another FunctionDeclaration.
// ii. If declaredFunctionOrVarNames does not contain F, then
if (!declared_function_names.contains(function_name) && !declared_var_names.contains(function_name)) {
auto result = global_environment.create_global_var_binding(function_name, false);
if (result.is_error())
return IterationDecision::Break;
// i. Perform ? env.CreateGlobalVarBinding(F, false).
TRY(global_environment.create_global_var_binding(function_name, false));
// ii. Append F to declaredFunctionOrVarNames.
declared_function_names.set(function_name);
}
// iii. When the FunctionDeclaration f is evaluated, perform the following steps in place of the FunctionDeclaration Evaluation algorithm provided in 15.2.6:
// i. Let genv be the running execution context's VariableEnvironment.
// ii. Let benv be the running execution context's LexicalEnvironment.
// iii. Let fobj be ! benv.GetBindingValue(F, false).
// iv. Perform ? genv.SetMutableBinding(F, fobj, false).
// v. Return NormalCompletion(empty).
function_declaration.set_should_do_additional_annexB_steps();
return IterationDecision::Continue;
});
if (auto* exception = interpreter.exception())
return throw_completion(exception->value());
return {};
}));
// We should not use declared function names below here anymore since these functions are not in there in the spec.
declared_function_names.clear();
}
// 13. Let lexDeclarations be the LexicallyScopedDeclarations of script.
// 14. Let privateEnv be null.
PrivateEnvironment* private_environment = nullptr;
for_each_lexically_scoped_declaration([&](Declaration const& declaration) {
declaration.for_each_bound_name([&](auto const& name) {
if (declaration.is_constant_declaration())
(void)global_environment.create_immutable_binding(global_object, name, true);
else
(void)global_environment.create_mutable_binding(global_object, name, false);
if (interpreter.exception())
return IterationDecision::Break;
return IterationDecision::Continue;
});
if (interpreter.exception())
return IterationDecision::Break;
return IterationDecision::Continue;
});
// 15. For each element d of lexDeclarations, do
TRY(for_each_lexically_scoped_declaration([&](Declaration const& declaration) {
// a. NOTE: Lexically declared names are only instantiated here but not initialized.
// b. For each element dn of the BoundNames of d, do
return declaration.for_each_bound_name([&](auto const& name) -> ThrowCompletionOr<void> {
// i. If IsConstantDeclaration of d is true, then
if (declaration.is_constant_declaration()) {
// 1. Perform ? env.CreateImmutableBinding(dn, true).
TRY(global_environment.create_immutable_binding(global_object, name, true));
}
// ii. Else,
else {
// 1. Perform ? env.CreateMutableBinding(dn, false).
TRY(global_environment.create_mutable_binding(global_object, name, false));
}
return {};
});
}));
// 16. For each Parse Node f of functionsToInitialize, do
for (auto& declaration : functions_to_initialize) {
// a. Let fn be the sole element of the BoundNames of f.
// b. Let fo be InstantiateFunctionObject of f with arguments env and privateEnv.
auto* function = ECMAScriptFunctionObject::create(global_object, declaration.name(), declaration.source_text(), declaration.body(), declaration.parameters(), declaration.function_length(), &global_environment, private_environment, declaration.kind(), declaration.is_strict_mode(), declaration.might_need_arguments_object(), declaration.contains_direct_call_to_eval());
// c. Perform ? env.CreateGlobalFunctionBinding(fn, fo, false).
TRY(global_environment.create_global_function_binding(declaration.name(), function, false));
}
for (auto& var_name : declared_var_names)
// 17. For each String vn of declaredVarNames, do
for (auto& var_name : declared_var_names) {
// a. Perform ? env.CreateGlobalVarBinding(vn, false).
TRY(global_environment.create_global_var_binding(var_name, false));
}
// 18. Return NormalCompletion(empty).
return {};
}