ladybird/Libraries/LibJS/ScopeCollector.cpp

655 lines
29 KiB
C++
Raw Normal View History

/*
* Copyright (c) 2026, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/QuickSort.h>
#include <AK/String.h>
#include <LibJS/Parser.h>
#include <LibJS/ScopeCollector.h>
namespace JS {
ScopeCollector::ScopeCollector(Parser& parser)
: m_parser(parser)
{
}
void ScopeCollector::open_scope(ScopeRecord::ScopeType type, ScopeNode* node, ScopeRecord::ScopeLevel level)
{
auto record = make<ScopeRecord>();
record->type = type;
record->level = level;
record->parent = m_current;
if (type != ScopeRecord::ScopeType::Function) {
VERIFY(node || (m_current && level == ScopeRecord::ScopeLevel::NotTopLevel));
if (!node)
record->ast_node = m_current->ast_node;
else
record->ast_node = node;
}
if (level == ScopeRecord::ScopeLevel::NotTopLevel)
record->top_level = m_current->top_level;
else
record->top_level = record.ptr();
auto* record_ptr = record.ptr();
if (m_current) {
m_current->children.append(move(record));
} else {
m_root = move(record);
}
m_current = record_ptr;
}
void ScopeCollector::close_scope()
{
VERIFY(m_current);
// Propagate flags needed during parsing to parent (stops at function boundaries).
if (m_current->parent && !m_current->function_parameters) {
m_current->parent->contains_access_to_arguments_object_in_non_strict_mode |= m_current->contains_access_to_arguments_object_in_non_strict_mode;
m_current->parent->contains_direct_call_to_eval |= m_current->contains_direct_call_to_eval;
m_current->parent->contains_await_expression |= m_current->contains_await_expression;
}
m_current = m_current->parent;
}
ScopeCollector::ScopeHandle ScopeCollector::open_program_scope(Program& program)
{
open_scope(ScopeRecord::ScopeType::Program, &program,
program.type() == Program::Type::Script ? ScopeRecord::ScopeLevel::ScriptTopLevel : ScopeRecord::ScopeLevel::ModuleTopLevel);
return ScopeHandle(*this);
}
ScopeCollector::ScopeHandle ScopeCollector::open_function_scope(RefPtr<Identifier const> function_name)
{
open_scope(ScopeRecord::ScopeType::Function, nullptr, ScopeRecord::ScopeLevel::FunctionTopLevel);
if (function_name)
m_current->variables.ensure(function_name->string()).flags |= ScopeVariable::IsBound;
return ScopeHandle(*this);
}
ScopeCollector::ScopeHandle ScopeCollector::open_block_scope(ScopeNode& node)
{
open_scope(ScopeRecord::ScopeType::Block, &node, ScopeRecord::ScopeLevel::NotTopLevel);
return ScopeHandle(*this);
}
ScopeCollector::ScopeHandle ScopeCollector::open_for_loop_scope(ScopeNode& node)
{
open_scope(ScopeRecord::ScopeType::ForLoop, &node, ScopeRecord::ScopeLevel::NotTopLevel);
return ScopeHandle(*this);
}
ScopeCollector::ScopeHandle ScopeCollector::open_with_scope(ScopeNode& node)
{
open_scope(ScopeRecord::ScopeType::With, &node, ScopeRecord::ScopeLevel::NotTopLevel);
return ScopeHandle(*this);
}
ScopeCollector::ScopeHandle ScopeCollector::open_catch_scope()
{
open_scope(ScopeRecord::ScopeType::Catch, nullptr, ScopeRecord::ScopeLevel::NotTopLevel);
return ScopeHandle(*this);
}
ScopeCollector::ScopeHandle ScopeCollector::open_static_init_scope(ScopeNode& node)
{
open_scope(ScopeRecord::ScopeType::ClassStaticInit, &node, ScopeRecord::ScopeLevel::StaticInitTopLevel);
return ScopeHandle(*this);
}
ScopeCollector::ScopeHandle ScopeCollector::open_class_field_scope(ScopeNode& node)
{
open_scope(ScopeRecord::ScopeType::ClassField, &node, ScopeRecord::ScopeLevel::NotTopLevel);
return ScopeHandle(*this);
}
ScopeCollector::ScopeHandle ScopeCollector::open_class_declaration_scope(RefPtr<Identifier const> class_name)
{
open_scope(ScopeRecord::ScopeType::ClassDeclaration, nullptr, ScopeRecord::ScopeLevel::NotTopLevel);
if (class_name)
m_current->variables.ensure(class_name->string()).flags |= ScopeVariable::IsBound;
return ScopeHandle(*this);
}
void ScopeCollector::add_catch_parameter(RefPtr<BindingPattern const> const& pattern, RefPtr<Identifier const> const& parameter)
{
if (pattern) {
// NOTE: Nothing in the callback throws an exception.
MUST(pattern->for_each_bound_identifier([&](auto const& identifier) {
auto& var = m_current->variables.ensure(identifier.string());
var.flags |= ScopeVariable::IsForbiddenVar | ScopeVariable::IsBound | ScopeVariable::IsCatchParameter;
}));
} else if (parameter) {
auto& var = m_current->variables.ensure(parameter->string());
var.flags |= ScopeVariable::IsVar | ScopeVariable::IsBound | ScopeVariable::IsCatchParameter;
var.var_identifier = parameter.ptr();
}
}
void ScopeCollector::add_declaration(NonnullRefPtr<Declaration const> declaration)
{
if (declaration->is_lexical_declaration()) {
// NOTE: Nothing in the callback throws an exception.
MUST(declaration->for_each_bound_identifier([&](auto const& identifier) {
auto const& name = identifier.string();
auto& var = m_current->variables.ensure(name);
if (var.flags & (ScopeVariable::IsVar | ScopeVariable::IsForbiddenLexical | ScopeVariable::IsFunction | ScopeVariable::IsLexical))
throw_identifier_declared(name, declaration);
var.flags |= ScopeVariable::IsLexical;
}));
m_current->ast_node->add_lexical_declaration(move(declaration));
} else if (!declaration->is_function_declaration()) {
// NOTE: Nothing in the callback throws an exception.
MUST(declaration->for_each_bound_identifier([&](auto const& identifier) {
auto const& name = identifier.string();
auto* scope = m_current;
while (true) {
auto& var = scope->variables.ensure(name);
if (var.flags & (ScopeVariable::IsLexical | ScopeVariable::IsFunction | ScopeVariable::IsForbiddenVar))
throw_identifier_declared(name, declaration);
var.flags |= ScopeVariable::IsVar;
var.var_identifier = &identifier;
if (scope->is_top_level())
break;
VERIFY(scope->parent != nullptr);
scope = scope->parent;
}
}));
VERIFY(m_current->top_level);
m_current->top_level->ast_node->add_var_scoped_declaration(move(declaration));
} else {
if (m_current->level != ScopeRecord::ScopeLevel::NotTopLevel && m_current->level != ScopeRecord::ScopeLevel::ModuleTopLevel) {
// Only non-top levels and Module don't var declare the top functions
// NOTE: Nothing in the callback throws an exception.
MUST(declaration->for_each_bound_identifier([&](auto const& identifier) {
auto& var = m_current->variables.ensure(identifier.string());
var.flags |= ScopeVariable::IsVar;
var.var_identifier = &identifier;
}));
m_current->ast_node->add_var_scoped_declaration(move(declaration));
} else {
VERIFY(is<FunctionDeclaration>(*declaration));
auto function_declaration = static_ptr_cast<FunctionDeclaration const>(declaration);
auto function_name = function_declaration->name();
auto& var = m_current->variables.ensure(function_name);
if (var.flags & (ScopeVariable::IsVar | ScopeVariable::IsLexical))
throw_identifier_declared(function_name, declaration);
if (function_declaration->kind() != FunctionKind::Normal || m_parser.m_state.strict_mode) {
if (var.flags & ScopeVariable::IsFunction)
throw_identifier_declared(function_name, declaration);
var.flags |= ScopeVariable::IsLexical;
m_current->ast_node->add_lexical_declaration(move(declaration));
return;
}
if (!(var.flags & ScopeVariable::IsLexical))
m_current->functions_to_hoist.append(function_declaration);
var.flags |= ScopeVariable::IsFunction;
var.function_declaration = function_declaration;
m_current->ast_node->add_lexical_declaration(move(declaration));
}
}
}
void ScopeCollector::register_identifier(NonnullRefPtr<Identifier> id, Optional<DeclarationKind> declaration_kind)
{
if (auto maybe_identifier_group = m_current->identifier_groups.get(id->string()); maybe_identifier_group.has_value()) {
maybe_identifier_group.value().identifiers.append(id);
if (declaration_kind.has_value() && !maybe_identifier_group.value().declaration_kind.has_value())
maybe_identifier_group.value().declaration_kind = declaration_kind;
} else {
m_current->identifier_groups.set(id->string(), IdentifierGroup {
.captured_by_nested_function = false,
.identifiers = { id },
.declaration_kind = declaration_kind,
});
}
}
void ScopeCollector::set_function_parameters(NonnullRefPtr<FunctionParameters const> parameters)
{
m_current->function_parameters = move(parameters);
for (auto& parameter : m_current->function_parameters->parameters()) {
if (parameter.default_value)
m_current->has_parameter_expressions = true;
parameter.binding.visit(
[&](Identifier const& identifier) {
register_identifier(fixme_launder_const_through_pointer_cast(identifier));
auto& var = m_current->variables.ensure(identifier.string());
var.flags |= ScopeVariable::IsParameterCandidate | ScopeVariable::IsForbiddenLexical;
},
[&](NonnullRefPtr<BindingPattern const> const& binding_pattern) {
if (binding_pattern->contains_expression())
m_current->has_parameter_expressions = true;
// NOTE: Nothing in the callback throws an exception.
MUST(binding_pattern->for_each_bound_identifier([&](auto const& identifier) {
register_identifier(fixme_launder_const_through_pointer_cast(identifier));
auto& var = m_current->variables.ensure(identifier.string());
var.flags |= ScopeVariable::IsParameterCandidate | ScopeVariable::IsForbiddenLexical;
}));
});
}
// Mark non-parameter names that were referenced during formal parameter
// parsing (i.e. in default value expressions). If a body var later
// declares the same name, it must not be optimized to a local, since the
// default expression needs to resolve it from the outer scope.
if (m_current->has_parameter_expressions) {
for (auto& [name, group] : m_current->identifier_groups) {
if (!m_current->has_variable_with_flags(name, ScopeVariable::IsForbiddenLexical))
m_current->variables.ensure(name).flags |= ScopeVariable::IsReferencedInFormalParameters;
}
}
}
void ScopeCollector::set_scope_node(ScopeNode* node) { m_current->ast_node = node; }
void ScopeCollector::set_contains_direct_call_to_eval()
{
m_current->contains_direct_call_to_eval = true;
m_current->screwed_by_eval_in_scope_chain = true;
m_current->eval_in_current_function = true;
}
void ScopeCollector::set_contains_access_to_arguments_object_in_non_strict_mode()
{
m_current->contains_access_to_arguments_object_in_non_strict_mode = true;
}
void ScopeCollector::set_contains_await_expression() { m_current->contains_await_expression = true; }
void ScopeCollector::set_uses_this()
{
auto const* closest_function_scope = m_current->last_function_scope();
auto this_from_env = closest_function_scope && closest_function_scope->is_arrow_function;
for (auto* scope = m_current; scope; scope = scope->parent) {
if (scope->type == ScopeRecord::ScopeType::Function) {
scope->uses_this = true;
if (this_from_env)
scope->uses_this_from_environment = true;
}
}
}
void ScopeCollector::set_uses_new_target()
{
for (auto* scope = m_current; scope; scope = scope->parent) {
if (scope->type == ScopeRecord::ScopeType::Function) {
scope->uses_this = true;
scope->uses_this_from_environment = true;
}
}
}
void ScopeCollector::set_is_arrow_function() { m_current->is_arrow_function = true; }
void ScopeCollector::set_is_function_declaration() { m_current->is_function_declaration = true; }
Vector<ScopeCollector::SavedAncestorFlags> ScopeCollector::save_ancestor_flags() const
{
Vector<SavedAncestorFlags> saved;
for (auto* scope = m_current; scope; scope = scope->parent) {
if (scope->type == ScopeRecord::ScopeType::Function) {
saved.append({ scope, scope->uses_this, scope->uses_this_from_environment });
}
}
return saved;
}
void ScopeCollector::restore_ancestor_flags(Vector<SavedAncestorFlags> const& saved)
{
for (auto const& entry : saved) {
entry.record->uses_this = entry.uses_this;
entry.record->uses_this_from_environment = entry.uses_this_from_environment;
}
}
bool ScopeCollector::contains_direct_call_to_eval() const { return m_current->contains_direct_call_to_eval; }
bool ScopeCollector::uses_this_from_environment() const { return m_current->uses_this_from_environment; }
bool ScopeCollector::uses_this() const { return m_current->uses_this; }
bool ScopeCollector::contains_await_expression() const { return m_current->contains_await_expression; }
bool ScopeCollector::can_have_using_declaration() const
{
return m_current->level != ScopeRecord::ScopeLevel::ScriptTopLevel;
}
ScopeRecord::ScopeType ScopeCollector::type() const { return m_current->type; }
bool ScopeCollector::has_declaration(Utf16FlyString const& name) const
{
if (m_current->has_variable_with_flags(name, ScopeVariable::IsLexical | ScopeVariable::IsVar))
return true;
return m_current->functions_to_hoist.contains([&name](auto& function) { return function->name() == name; });
}
ScopeRecord const* ScopeCollector::last_function_scope() const { return m_current->last_function_scope(); }
ScopeRecord* ScopeCollector::parent_scope() { return m_current->parent; }
FunctionParameters const& ScopeCollector::function_parameters() const { return *m_current->function_parameters; }
bool ScopeCollector::has_declaration_in_current_function(Utf16FlyString const& name) const
{
auto const* function_scope = m_current->last_function_scope();
auto const* stop = function_scope ? function_scope->parent : nullptr;
for (auto const* scope = m_current; scope != stop; scope = scope->parent) {
if (scope->has_variable_with_flags(name, ScopeVariable::IsLexical | ScopeVariable::IsVar | ScopeVariable::IsParameterCandidate))
return true;
if (scope->functions_to_hoist.contains([&name](auto& function) { return function->name() == name; }))
return true;
}
return false;
}
void ScopeCollector::throw_identifier_declared(Utf16FlyString const& name, NonnullRefPtr<Declaration const> const& declaration)
{
m_parser.syntax_error(MUST(String::formatted("Identifier '{}' already declared", name)), declaration->source_range().start);
}
// --- Post-parse analysis ---
void ScopeCollector::analyze(bool suppress_globals)
{
if (m_root)
analyze_recursive(*m_root, suppress_globals);
}
void ScopeCollector::analyze_recursive(ScopeRecord& scope, bool suppress_globals)
{
// Process children first (bottom-up).
for (auto& child : scope.children)
analyze_recursive(*child, suppress_globals);
if (!scope.ast_node)
return;
propagate_eval_poisoning(scope);
resolve_identifiers(scope, m_parser.m_state.initiated_by_eval, suppress_globals);
hoist_functions(scope);
if (scope.type == ScopeRecord::ScopeType::Function && scope.function_parameters)
build_function_scope_data(scope);
}
void ScopeCollector::propagate_eval_poisoning(ScopeRecord& scope)
{
if (scope.parent && (scope.contains_direct_call_to_eval || scope.screwed_by_eval_in_scope_chain)) {
scope.parent->screwed_by_eval_in_scope_chain = true;
}
// Propagate eval-in-current-function only through block scopes, not across function boundaries.
// This is used for global identifier marking - eval can only inject vars into its containing
// function's scope, not into parent function scopes.
if (scope.parent && scope.eval_in_current_function && scope.type != ScopeRecord::ScopeType::Function) {
scope.parent->eval_in_current_function = true;
}
}
void ScopeCollector::resolve_identifiers(ScopeRecord& scope, bool initiated_by_eval, bool suppress_globals)
{
// NB: ScopeRecord::identifier_groups is a HashMap, so its iteration order
// is non-deterministic. We sort the keys alphabetically here to ensure
// that local variable indices are assigned in a deterministic order.
// Without this, the generated bytecode could vary between runs depending
// on HashMap bucketing, making it impossible to compare outputs from
// different compilation pipelines (e.g. C++ vs Rust).
Vector<Utf16FlyString> sorted_keys;
for (auto& it : scope.identifier_groups)
sorted_keys.append(it.key);
quick_sort(sorted_keys, [](auto const& a, auto const& b) { return a.view() < b.view(); });
for (auto const& identifier_group_name : sorted_keys) {
auto& identifier_group = scope.identifier_groups.get(identifier_group_name).value();
if (identifier_group.declaration_kind.has_value()) {
for (auto& identifier : identifier_group.identifiers) {
identifier->set_declaration_kind(identifier_group.declaration_kind.value());
}
}
auto var_it = scope.variables.find(identifier_group_name);
u16 var_flags = (var_it != scope.variables.end()) ? var_it->value.flags : 0;
Optional<LocalVariable::DeclarationKind> local_variable_declaration_kind;
if (scope.is_top_level() && (var_flags & ScopeVariable::IsVar)) {
local_variable_declaration_kind = LocalVariable::DeclarationKind::Var;
} else if (var_flags & ScopeVariable::IsLexical) {
local_variable_declaration_kind = LocalVariable::DeclarationKind::LetOrConst;
} else if (var_flags & ScopeVariable::IsFunction) {
local_variable_declaration_kind = LocalVariable::DeclarationKind::Function;
}
if (scope.type == ScopeRecord::ScopeType::Function && !scope.is_arrow_function && identifier_group_name == "arguments"sv) {
local_variable_declaration_kind = LocalVariable::DeclarationKind::ArgumentsObject;
}
if (scope.type == ScopeRecord::ScopeType::Catch && (var_flags & ScopeVariable::IsCatchParameter)) {
local_variable_declaration_kind = LocalVariable::DeclarationKind::CatchClauseParameter;
}
// When a function has parameter expressions (default values, etc.), body
// var declarations live in a separate Variable Environment from the
// parameter scope. If the same name is also referenced in a default
// parameter expression, it must not be a local: the default expression
// needs to resolve it from the outer scope via the environment chain,
// not read the (uninitialized) local.
// We also mark the name as captured in the parent scope, so that the
// outer binding is not optimized to a local register either.
if ((var_flags & ScopeVariable::IsReferencedInFormalParameters)
&& (var_flags & ScopeVariable::IsVar)
&& !(var_flags & ScopeVariable::IsForbiddenLexical)) {
if (scope.parent)
scope.parent->identifier_groups.ensure(identifier_group_name).captured_by_nested_function = true;
continue;
}
bool hoistable_function_declaration = scope.functions_to_hoist.contains([&](auto const& function_declaration) {
return function_declaration->name() == identifier_group_name;
});
if (scope.type == ScopeRecord::ScopeType::ClassDeclaration && (var_flags & ScopeVariable::IsBound)) {
continue;
}
if (scope.type == ScopeRecord::ScopeType::Function && !scope.is_function_declaration && (var_flags & ScopeVariable::IsBound)) {
for (auto& identifier : identifier_group.identifiers)
identifier->set_is_inside_scope_with_eval();
}
if (scope.type == ScopeRecord::ScopeType::ClassDeclaration) {
local_variable_declaration_kind.clear();
}
bool is_function_parameter = false;
if (scope.type == ScopeRecord::ScopeType::Function) {
if ((var_flags & ScopeVariable::IsParameterCandidate)
&& (!scope.contains_access_to_arguments_object_in_non_strict_mode
|| (scope.function_parameters && scope.function_parameters->has_rest_parameter_with_name(identifier_group_name)))) {
// Rest parameters don't participate in the sloppy-mode
// arguments-parameter linkage, so they can always be optimized.
is_function_parameter = true;
} else if (var_flags & ScopeVariable::IsForbiddenLexical) {
continue;
}
}
if (scope.type == ScopeRecord::ScopeType::Function && hoistable_function_declaration) {
continue;
}
if (scope.type == ScopeRecord::ScopeType::Program) {
auto can_use_global_for_identifier = !(suppress_globals || identifier_group.used_inside_with_statement || initiated_by_eval);
if (can_use_global_for_identifier) {
for (auto& identifier : identifier_group.identifiers) {
if (!identifier->is_inside_scope_with_eval())
identifier->set_is_global();
}
}
} else if (local_variable_declaration_kind.has_value() || is_function_parameter) {
if (hoistable_function_declaration)
continue;
// When a function has parameter expressions and a nested function in a
// default expression captures a name that is also a body var, propagate
// the capture to the parent scope so the outer binding stays in the
// environment (not optimized to a local register).
if (scope.has_parameter_expressions
&& identifier_group.captured_by_nested_function
&& (var_flags & ScopeVariable::IsVar)
&& !(var_flags & ScopeVariable::IsForbiddenLexical)
&& scope.parent) {
scope.parent->identifier_groups.ensure(identifier_group_name).captured_by_nested_function = true;
}
if (!identifier_group.captured_by_nested_function && !identifier_group.used_inside_with_statement) {
if (scope.screwed_by_eval_in_scope_chain)
continue;
auto local_scope = scope.last_function_scope();
if (!local_scope) {
if (identifier_group.declaration_kind == DeclarationKind::Var)
continue;
local_scope = scope.top_level;
}
if (is_function_parameter) {
auto argument_index = local_scope->function_parameters->get_index_of_parameter_name(identifier_group_name);
if (argument_index.has_value()) {
for (auto& identifier : identifier_group.identifiers)
identifier->set_argument_index(argument_index.value());
} else {
// Destructured parameter binding: the argument slot holds the
// whole object/array, so the individual binding goes into a
// local variable slot instead.
auto local_variable_index = local_scope->ast_node->add_local_variable(identifier_group_name, LocalVariable::DeclarationKind::Var);
for (auto& identifier : identifier_group.identifiers)
identifier->set_local_variable_index(local_variable_index);
}
} else {
auto local_variable_index = local_scope->ast_node->add_local_variable(identifier_group_name, *local_variable_declaration_kind);
for (auto& identifier : identifier_group.identifiers)
identifier->set_local_variable_index(local_variable_index);
}
}
} else {
if (scope.function_parameters || scope.type == ScopeRecord::ScopeType::ClassField || scope.type == ScopeRecord::ScopeType::ClassStaticInit) {
identifier_group.captured_by_nested_function = true;
}
if (scope.type == ScopeRecord::ScopeType::With)
identifier_group.used_inside_with_statement = true;
if (scope.eval_in_current_function) {
for (auto& identifier : identifier_group.identifiers)
identifier->set_is_inside_scope_with_eval();
}
if (scope.parent) {
if (auto maybe_parent_group = scope.parent->identifier_groups.get(identifier_group_name); maybe_parent_group.has_value()) {
maybe_parent_group.value().identifiers.extend(identifier_group.identifiers);
if (identifier_group.captured_by_nested_function)
maybe_parent_group.value().captured_by_nested_function = true;
if (identifier_group.used_inside_with_statement)
maybe_parent_group.value().used_inside_with_statement = true;
} else {
scope.parent->identifier_groups.set(identifier_group_name, identifier_group);
}
}
}
}
}
void ScopeCollector::hoist_functions(ScopeRecord& scope)
{
for (size_t i = 0; i < scope.functions_to_hoist.size(); i++) {
auto const& function_declaration = scope.functions_to_hoist[i];
if (scope.has_variable_with_flags(function_declaration->name(), ScopeVariable::IsLexical | ScopeVariable::IsForbiddenVar))
continue;
if (scope.is_top_level()) {
scope.ast_node->add_hoisted_function(move(scope.functions_to_hoist[i]));
} else {
if (!scope.parent->has_variable_with_flags(function_declaration->name(), ScopeVariable::IsLexical | ScopeVariable::IsFunction))
scope.parent->functions_to_hoist.append(move(scope.functions_to_hoist[i]));
}
}
}
void ScopeCollector::build_function_scope_data(ScopeRecord& scope)
{
auto data = make<FunctionScopeData>();
HashTable<Utf16FlyString> seen_function_names;
for (ssize_t i = scope.ast_node->var_declaration_count() - 1; i >= 0; i--) {
auto const& declaration = scope.ast_node->var_declarations()[i];
if (is<FunctionDeclaration>(declaration)) {
auto& function_decl = static_cast<FunctionDeclaration const&>(*declaration);
if (seen_function_names.set(function_decl.name()) == AK::HashSetResult::InsertedNewEntry)
data->functions_to_initialize.append(static_ptr_cast<FunctionDeclaration const>(declaration));
}
}
data->has_function_named_arguments = seen_function_names.contains("arguments"_utf16_fly_string);
data->has_argument_parameter = scope.has_variable_with_flags("arguments"_utf16_fly_string, ScopeVariable::IsForbiddenLexical);
MUST(scope.ast_node->for_each_lexically_declared_identifier([&](auto const& identifier) {
if (identifier.string() == "arguments"_utf16_fly_string)
data->has_lexically_declared_arguments = true;
}));
for (auto& [name, var] : scope.variables) {
if (!(var.flags & ScopeVariable::IsVar))
continue;
bool is_parameter = var.flags & ScopeVariable::IsForbiddenLexical;
bool is_non_local = !var.var_identifier->is_local();
data->vars_to_initialize.append({
.identifier = *var.var_identifier,
.is_parameter = is_parameter,
.is_function_name = seen_function_names.contains(name),
});
data->var_names.set(name);
if (is_non_local) {
data->non_local_var_count_for_parameter_expressions++;
if (!is_parameter)
data->non_local_var_count++;
}
}
// NB: ScopeRecord::variables is a HashMap, so vars_to_initialize was
// populated in non-deterministic order. Sort by name to ensure the
// function declaration instantiation (FDI) bytecode is deterministic.
Vector<size_t> indices;
indices.ensure_capacity(data->vars_to_initialize.size());
for (size_t i = 0; i < data->vars_to_initialize.size(); ++i)
indices.append(i);
quick_sort(indices, [&](auto a, auto b) {
return data->vars_to_initialize[a].identifier.string() < data->vars_to_initialize[b].identifier.string();
});
Vector<VarToInitialize> sorted;
sorted.ensure_capacity(indices.size());
for (auto i : indices)
sorted.append(data->vars_to_initialize[i]);
data->vars_to_initialize = move(sorted);
scope.ast_node->set_function_scope_data(move(data));
}
}