mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-26 01:35:08 +02:00
Replace the ScopePusher RAII class (which performed scope analysis in its destructor chain during parsing) with a two-phase approach: 1. ScopeCollector builds a tree of ScopeRecord nodes during parsing via RAII ScopeHandle objects. It records declarations, identifier references, and flags, but does not resolve anything. 2. After parsing completes, ScopeCollector::analyze() walks the tree bottom-up and performs all resolution: propagate eval/with poisoning, resolve identifiers to locals/globals/arguments, hoist functions (Annex B.3.3), and build FunctionScopeData. Key design decisions: - ScopeRecord::ast_node is a RefPtr<ScopeNode> to prevent use-after-free when synthesize_binding_pattern re-parses an expression as a binding pattern (the original parse's scope records survive with stale AST node pointers). - Parser::scope_collector() returns the override collector if set (for synthesize_binding_pattern's nested parser), ensuring all scope operations route to the outer parser's scope tree. - FunctionNode::local_variables_names() delegates to its body's ScopeNode rather than copying at parse time, since analysis runs after parsing.
115 lines
3.8 KiB
C++
115 lines
3.8 KiB
C++
/*
|
|
* Copyright (c) 2026, Andreas Kling <andreas@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <AK/OwnPtr.h>
|
|
#include <LibJS/ScopeRecord.h>
|
|
|
|
namespace JS {
|
|
|
|
class Parser;
|
|
|
|
class ScopeCollector {
|
|
public:
|
|
class ScopeHandle {
|
|
public:
|
|
ScopeHandle() = default;
|
|
|
|
~ScopeHandle()
|
|
{
|
|
if (m_collector)
|
|
m_collector->close_scope();
|
|
}
|
|
|
|
ScopeHandle(ScopeHandle&& other)
|
|
: m_collector(exchange(other.m_collector, nullptr))
|
|
{
|
|
}
|
|
|
|
ScopeHandle& operator=(ScopeHandle&& other)
|
|
{
|
|
if (this != &other) {
|
|
if (m_collector)
|
|
m_collector->close_scope();
|
|
m_collector = exchange(other.m_collector, nullptr);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
ScopeHandle(ScopeHandle const&) = delete;
|
|
ScopeHandle& operator=(ScopeHandle const&) = delete;
|
|
|
|
private:
|
|
friend class ScopeCollector;
|
|
explicit ScopeHandle(ScopeCollector& collector)
|
|
: m_collector(&collector)
|
|
{
|
|
}
|
|
ScopeCollector* m_collector { nullptr };
|
|
};
|
|
|
|
explicit ScopeCollector(Parser& parser);
|
|
|
|
[[nodiscard]] ScopeHandle open_program_scope(Program& program);
|
|
[[nodiscard]] ScopeHandle open_function_scope(RefPtr<Identifier const> function_name = nullptr);
|
|
[[nodiscard]] ScopeHandle open_block_scope(ScopeNode& node);
|
|
[[nodiscard]] ScopeHandle open_for_loop_scope(ScopeNode& node);
|
|
[[nodiscard]] ScopeHandle open_with_scope(ScopeNode& node);
|
|
[[nodiscard]] ScopeHandle open_catch_scope();
|
|
[[nodiscard]] ScopeHandle open_static_init_scope(ScopeNode& node);
|
|
[[nodiscard]] ScopeHandle open_class_field_scope(ScopeNode& node);
|
|
[[nodiscard]] ScopeHandle open_class_declaration_scope(RefPtr<Identifier const> class_name);
|
|
|
|
void add_declaration(NonnullRefPtr<Declaration const> declaration);
|
|
void add_catch_parameter(RefPtr<BindingPattern const> const& pattern, RefPtr<Identifier const> const& parameter);
|
|
void register_identifier(NonnullRefPtr<Identifier> id, Optional<DeclarationKind> declaration_kind = {});
|
|
void set_function_parameters(NonnullRefPtr<FunctionParameters const> parameters);
|
|
void set_scope_node(ScopeNode* node);
|
|
void set_contains_direct_call_to_eval();
|
|
void set_contains_access_to_arguments_object_in_non_strict_mode();
|
|
void set_contains_await_expression();
|
|
void set_uses_this();
|
|
void set_uses_new_target();
|
|
void set_is_arrow_function();
|
|
void set_is_function_declaration();
|
|
|
|
bool contains_direct_call_to_eval() const;
|
|
bool uses_this_from_environment() const;
|
|
bool uses_this() const;
|
|
bool contains_await_expression() const;
|
|
bool can_have_using_declaration() const;
|
|
ScopeRecord::ScopeType type() const;
|
|
bool has_declaration(Utf16FlyString const& name) const;
|
|
ScopeRecord const* last_function_scope() const;
|
|
ScopeRecord* parent_scope();
|
|
FunctionParameters const& function_parameters() const;
|
|
|
|
bool has_current_scope() const { return m_current != nullptr; }
|
|
|
|
bool has_declaration_in_current_function(Utf16FlyString const& name) const;
|
|
|
|
void analyze();
|
|
|
|
private:
|
|
void open_scope(ScopeRecord::ScopeType type, ScopeNode* node, ScopeRecord::ScopeLevel level);
|
|
void close_scope();
|
|
|
|
void throw_identifier_declared(Utf16FlyString const& name, NonnullRefPtr<Declaration const> const& declaration);
|
|
|
|
static void propagate_eval_poisoning(ScopeRecord& scope);
|
|
static void resolve_identifiers(ScopeRecord& scope, bool initiated_by_eval);
|
|
static void hoist_functions(ScopeRecord& scope);
|
|
static void build_function_scope_data(ScopeRecord& scope);
|
|
void analyze_recursive(ScopeRecord& scope);
|
|
|
|
Parser& m_parser;
|
|
ScopeRecord* m_current { nullptr };
|
|
OwnPtr<ScopeRecord> m_root;
|
|
};
|
|
|
|
}
|