From 488595cf0a3288283bced0eefd2290231d726a1f Mon Sep 17 00:00:00 2001 From: Orlovsky Maxim Date: Fri, 27 Oct 2023 12:27:23 +0300 Subject: [PATCH 1/2] Title: Refactored ASTVerifier for more extensibility, added corresponding tests. Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/I8G540 Testing: C++ unit tests Change-Id: Ib9423f97e288f423e0e3d01bf7fa56d4d56b3bfa Signed-off-by: Orlovsky Maxim --- ets2panda/CMakeLists.txt | 3 + ets2panda/compiler/core/ASTVerifier.cpp | 167 ++++++++++---------- ets2panda/compiler/core/ASTVerifier.h | 69 ++++++-- ets2panda/compiler/lowering/phase.cpp | 30 +++- ets2panda/test/CMakeLists.txt | 3 +- ets2panda/test/public/ast_verifier_test.cpp | 88 ++++++++--- 6 files changed, 238 insertions(+), 122 deletions(-) diff --git a/ets2panda/CMakeLists.txt b/ets2panda/CMakeLists.txt index 75f77a2394..8b26781587 100644 --- a/ets2panda/CMakeLists.txt +++ b/ets2panda/CMakeLists.txt @@ -18,6 +18,9 @@ include(cmake/coverage.cmake) project (es2panda) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(ES2PANDA_ROOT ${CMAKE_CURRENT_SOURCE_DIR}) +set(ES2PANDA_BINARY_ROOT ${CMAKE_CURRENT_BINARY_DIR}) + set(OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}) set(GENERATED_DIR ${OUTPUT_DIR}/generated) set(GENERATED_STAMP ${OUTPUT_DIR}/gen_dir.stamp) diff --git a/ets2panda/compiler/core/ASTVerifier.cpp b/ets2panda/compiler/core/ASTVerifier.cpp index fce72cee39..dd3e309bd3 100644 --- a/ets2panda/compiler/core/ASTVerifier.cpp +++ b/ets2panda/compiler/core/ASTVerifier.cpp @@ -14,6 +14,8 @@ */ #include "ASTVerifier.h" +#include +#include #include "es2panda.h" #include "varbinder/variableFlags.h" @@ -31,6 +33,7 @@ #include "ir/ets/etsTypeReference.h" #include "ir/ets/etsTypeReferencePart.h" #include "ir/expressions/callExpression.h" +#include "ir/expressions/sequenceExpression.h" #include "ir/expressions/functionExpression.h" #include "ir/expressions/identifier.h" #include "ir/expressions/memberExpression.h" @@ -47,37 +50,93 @@ #include "ir/ts/tsTypeParameter.h" #include "ir/ts/tsTypeParameterDeclaration.h" #include "ir/ts/tsTypeParameterInstantiation.h" +#include "util/ustring.h" +#include "utils/arena_containers.h" namespace panda::es2panda::compiler { -bool ASTVerifier::IsCorrectProgram(const parser::Program *program) +template +ASTVerifier::CheckFunction RecursiveCheck(const Func &func) { - bool is_correct = true; - error_messages_.clear(); + return [func](const ir::AstNode *ast) -> bool { + bool has_parent = func(ast); + ast->IterateRecursively([func, &has_parent](ir::AstNode *child) { has_parent &= func(child); }); + return has_parent; + }; +} - for (auto *statement : program->Ast()->Statements()) { - is_correct &= HaveParents(statement); +// NOLINTBEGIN(cppcoreguidelines-macro-usage) +#define ADD_CHECK(Name) \ + { \ + const auto check = [this](const ir::AstNode *ast) -> bool { return this->Name(ast); }; \ + checks_.emplace_back(NamedCheck {#Name, check}); \ + all_checks_.insert(#Name); \ + checks_.emplace_back(NamedCheck {#Name "Recursive", RecursiveCheck(check)}); \ + all_checks_.insert(#Name "Recursive"); \ } - is_correct &= HaveParents(program->GlobalClass()); - - for (auto *statement : program->Ast()->Statements()) { - is_correct &= HaveTypes(statement); +// NOLINTEND(cppcoreguidelines-macro-usage) + +ASTVerifier::ASTVerifier(ArenaAllocator *allocator, util::StringView source_code) + : allocator_ {allocator}, + named_errors_ {allocator_->Adapter()}, + encountered_errors_ {allocator_->Adapter()}, + checks_ {allocator_->Adapter()}, + all_checks_(allocator_->Adapter()) +{ + if (!source_code.Empty()) { + index_.emplace(source_code); } - is_correct &= HaveTypes(program->GlobalClass()); - for (auto *statement : program->Ast()->Statements()) { - is_correct &= HaveVariables(statement); + ADD_CHECK(HasParent); + ADD_CHECK(HasType); + ADD_CHECK(HasVariable); + ADD_CHECK(HasScope); +} + +bool ASTVerifier::VerifyFull(const ir::AstNode *ast) +{ + return Verify(ast, all_checks_); +} + +bool ASTVerifier::Verify(const ir::AstNode *ast, const CheckSet &check_set) +{ + bool is_correct = true; + auto check_and_report = [&is_correct, this](util::StringView name, const CheckFunction &check, + const ir::AstNode *node) { + if (node == nullptr) { + return; + } + + is_correct &= check(node); + if (!is_correct) { + for (const auto &error : encountered_errors_) { + named_errors_.emplace_back(NamedError {name, error}); + } + encountered_errors_.clear(); + } + }; + + const auto contains_checks = + std::includes(all_checks_.begin(), all_checks_.end(), check_set.begin(), check_set.end()); + if (!contains_checks) { + auto invalid_checks = CheckSet {allocator_->Adapter()}; + for (const auto &check : check_set) { + if (all_checks_.find(check) == all_checks_.end()) { + invalid_checks.insert(check); + } + } + for (const auto &check : invalid_checks) { + const auto &message = check.Mutf8() + " check is not found"; + named_errors_.emplace_back(NamedError {"Check", Error {message, lexer::SourceLocation {}}}); + } } - is_correct &= HaveVariables(program->GlobalClass()); - for (auto *statement : program->Ast()->Statements()) { - is_correct &= HaveScopes(statement); + for (const auto &[name, check] : checks_) { + if (check_set.find(name) != check_set.end()) { + check_and_report(name, check, ast); + } } - is_correct &= HaveScopes(program->GlobalClass()); -#ifndef NDEBUG - std::for_each(error_messages_.begin(), error_messages_.end(), [](auto const msg) { LOG(INFO, COMMON) << msg; }); -#endif // NDEBUG return is_correct; } @@ -304,106 +363,48 @@ std::string ToStringHelper(const ir::AstNode *ast) bool ASTVerifier::HasParent(const ir::AstNode *ast) { - if (ast == nullptr) { - return false; - } - if (ast->Parent() == nullptr) { - error_messages_.push_back("NULL_PARENT: " + ToStringHelper(ast)); + AddError("NULL_PARENT: " + ToStringHelper(ast), ast->Start()); return false; } return true; } -bool ASTVerifier::HaveParents(const ir::AstNode *ast) -{ - if (ast == nullptr) { - return false; - } - - bool has_parent = HasParent(ast); - ast->IterateRecursively([this, &has_parent](ir::AstNode *child) { has_parent &= HasParent(child); }); - return has_parent; -} - bool ASTVerifier::HasType(const ir::AstNode *ast) { - if (ast == nullptr) { - return false; - } - if (ast->IsTyped() && static_cast(ast)->TsType() == nullptr) { - error_messages_.push_back("NULL_TS_TYPE: " + ToStringHelper(ast)); + AddError("NULL_TS_TYPE: " + ToStringHelper(ast), ast->Start()); return false; } return true; } -bool ASTVerifier::HaveTypes(const ir::AstNode *ast) -{ - if (ast == nullptr) { - return false; - } - - bool has_type = HasType(ast); - ast->IterateRecursively([this, &has_type](ir::AstNode *child) { has_type &= HasType(child); }); - return has_type; -} - bool ASTVerifier::HasVariable(const ir::AstNode *ast) { - if (ast == nullptr) { - return false; - } - if (!ast->IsIdentifier() || ast->AsIdentifier()->Variable() != nullptr) { return true; } - error_messages_.push_back("NULL_VARIABLE: " + ToStringHelper(ast->AsIdentifier())); + const auto *id = ast->AsIdentifier(); + AddError("NULL_VARIABLE: " + ToStringHelper(id), id->Start()); return false; } -bool ASTVerifier::HaveVariables(const ir::AstNode *ast) -{ - if (ast == nullptr) { - return false; - } - - bool has_variable = HasVariable(ast); - ast->IterateRecursively([this, &has_variable](ir::AstNode *child) { has_variable &= HasVariable(child); }); - return has_variable; -} - bool ASTVerifier::HasScope(const ir::AstNode *ast) { - if (ast == nullptr) { - return false; - } - if (!ast->IsIdentifier()) { return true; // we will check only Identifier } // we will check only local variables of identifiers if (HasVariable(ast) && ast->AsIdentifier()->Variable()->IsLocalVariable() && ast->AsIdentifier()->Variable()->AsLocalVariable()->GetScope() == nullptr) { - error_messages_.push_back("NULL_SCOPE_LOCAL_VAR: " + ToStringHelper(ast)); + const auto *id = ast->AsIdentifier(); + AddError("NULL_SCOPE_LOCAL_VAR: " + ToStringHelper(ast), id->Start()); return false; } // NOTE(tatiana): Add check that the scope enclose this identifier return true; } -bool ASTVerifier::HaveScopes(const ir::AstNode *ast) -{ - if (ast == nullptr) { - return false; - } - - bool has_scope = HasScope(ast); - ast->IterateRecursively([this, &has_scope](ir::AstNode *child) { has_scope &= HasScope(child); }); - return has_scope; -} - } // namespace panda::es2panda::compiler diff --git a/ets2panda/compiler/core/ASTVerifier.h b/ets2panda/compiler/core/ASTVerifier.h index d5cbb7f2d5..0fce8e4f36 100644 --- a/ets2panda/compiler/core/ASTVerifier.h +++ b/ets2panda/compiler/core/ASTVerifier.h @@ -16,36 +16,81 @@ #ifndef ES2PANDA_COMPILER_CORE_ASTVERIFIER_H #define ES2PANDA_COMPILER_CORE_ASTVERIFIER_H +#include "ir/astNode.h" +#include "lexer/token/sourceLocation.h" #include "parser/program/program.h" +#include "util/ustring.h" +#include "utils/arena_containers.h" namespace panda::es2panda::compiler { -class ASTVerifier { +class ASTVerifier final { public: - using ErrorMessages = std::vector; + struct Error { + std::string message; + lexer::SourceLocation location; + }; + struct NamedError { + util::StringView check_name; + Error error; + }; + using Errors = ArenaVector; + + using CheckFunction = std::function; + struct NamedCheck { + util::StringView check_name; + CheckFunction check; + }; + using Checks = ArenaVector; + NO_COPY_SEMANTIC(ASTVerifier); NO_MOVE_SEMANTIC(ASTVerifier); - ASTVerifier() = default; + explicit ASTVerifier(ArenaAllocator *allocator, util::StringView source_code = ""); ~ASTVerifier() = default; - bool IsCorrectProgram(const parser::Program *program); - bool HaveParents(const ir::AstNode *ast); + using CheckSet = ArenaSet; + /** + * @brief Run all existing checks on some ast node (and consequently it's children) + * @param ast AstNode which will be analyzed + * @return bool Result of analysis + */ + bool VerifyFull(const ir::AstNode *ast); + + /** + * @brief Run some particular checks on some ast node + * @note Checks must be supplied as strings to check_set, additionally check + * name can be suffixed by `Recursive` string to include recursive analysis of provided node + * @param ast AstNode which will be analyzed + * @param check_set Set of strings which will be used as check names + * @return bool Result of analysis + */ + bool Verify(const ir::AstNode *ast, const CheckSet &check_set); + + Errors GetErrors() const + { + return named_errors_; + } + +private: bool HasParent(const ir::AstNode *ast); - bool HaveTypes(const ir::AstNode *ast); bool HasType(const ir::AstNode *ast); - bool HaveVariables(const ir::AstNode *ast); bool HasVariable(const ir::AstNode *ast); bool HasScope(const ir::AstNode *ast); - bool HaveScopes(const ir::AstNode *ast); - ErrorMessages GetErrorMessages() + void AddError(const std::string &message, const lexer::SourcePosition &from) { - return error_messages_; + const auto loc = index_.has_value() ? index_->GetLocation(from) : lexer::SourceLocation {}; + encountered_errors_.emplace_back(Error {message, loc}); } -private: - ErrorMessages error_messages_; + std::optional index_; + + ArenaAllocator *allocator_; + Errors named_errors_; + ArenaVector encountered_errors_; + Checks checks_; + CheckSet all_checks_; }; std::string ToStringHelper(const ir::AstNode *ast); diff --git a/ets2panda/compiler/lowering/phase.cpp b/ets2panda/compiler/lowering/phase.cpp index f3b2ff83a6..389d4d7d9e 100644 --- a/ets2panda/compiler/lowering/phase.cpp +++ b/ets2panda/compiler/lowering/phase.cpp @@ -50,6 +50,26 @@ std::vector GetETSPhaseList() bool Phase::Apply(CompilerContext *ctx, parser::Program *program) { +#ifndef NDEBUG + const auto check_program = [](const parser::Program *p) { + ASTVerifier verifier {p->Allocator(), p->SourceCode()}; + ArenaVector to_check {p->Allocator()->Adapter()}; + to_check.push_back(p->Ast()); + for (const auto &external_source : p->ExternalSources()) { + for (const auto external : external_source.second) { + to_check.push_back(external->Ast()); + } + } + + for (const auto *ast : to_check) { + if (!verifier.VerifyFull(ast)) { + return false; + } + } + return true; + }; +#endif + const auto *options = ctx->Options(); if (options->skip_phases.count(Name()) > 0) { return true; @@ -61,10 +81,6 @@ bool Phase::Apply(CompilerContext *ctx, parser::Program *program) } #ifndef NDEBUG - ASTVerifier ast_before; - if (!ast_before.IsCorrectProgram(program)) { - // NOTE(tatiana): Add some error processing - } if (!Precondition(ctx, program)) { ctx->Checker()->ThrowTypeError({"Precondition check failed for ", util::StringView {Name()}}, lexer::SourcePosition {}); @@ -81,10 +97,8 @@ bool Phase::Apply(CompilerContext *ctx, parser::Program *program) } #ifndef NDEBUG - ASTVerifier ast_after; - if (!ast_after.IsCorrectProgram(program)) { - // NOTE(tatiana): Add some error processing - } + check_program(program); + if (!Postcondition(ctx, program)) { ctx->Checker()->ThrowTypeError({"Postcondition check failed for ", util::StringView {Name()}}, lexer::SourcePosition {}); diff --git a/ets2panda/test/CMakeLists.txt b/ets2panda/test/CMakeLists.txt index ff14df7c4a..204b40b833 100644 --- a/ets2panda/test/CMakeLists.txt +++ b/ets2panda/test/CMakeLists.txt @@ -82,7 +82,7 @@ if(PANDA_WITH_ETS) LIBRARIES es2panda-public es2panda-lib arkassembler arkbytecodeopt INCLUDE_DIRS - ${CMAKE_CURRENT_SOURCE_DIR}/.. + ${ES2PANDA_ROOT} SANITIZERS ${PANDA_SANITIZERS_LIST} ) @@ -100,6 +100,7 @@ if(PANDA_WITH_ETS) es2panda-lib INCLUDE_DIRS ${ES2PANDA_PATH} + ${ES2PANDA_BINARY_ROOT} SANITIZERS ${PANDA_SANITIZERS_LIST} ) diff --git a/ets2panda/test/public/ast_verifier_test.cpp b/ets2panda/test/public/ast_verifier_test.cpp index dd36a19379..14766be293 100644 --- a/ets2panda/test/public/ast_verifier_test.cpp +++ b/ets2panda/test/public/ast_verifier_test.cpp @@ -15,57 +15,109 @@ #include #include +#include "checker/ETSAnalyzer.h" +#include "ir/expressions/literals/numberLiteral.h" +#include "ir/expressions/sequenceExpression.h" #include "macros.h" #include "compiler/core/ASTVerifier.h" +#include "varbinder/ETSBinder.h" +#include "checker/ETSchecker.h" #include "ir/astDump.h" #include "ir/expressions/literals/stringLiteral.h" +// NOLINTBEGIN(cppcoreguidelines-macro-usage) +#define TREE(node) \ + ([&]() { \ + using namespace panda::es2panda::ir; \ + return node; \ + }()) + +#define NODE(Type, ...) allocator->New(__VA_ARGS__) +#define NODES(Type, ...) \ + ([&]() -> ArenaVector { \ + auto v = ArenaVector {allocator->Adapter()}; \ + v.insert(v.end(), {__VA_ARGS__}); \ + return v; \ + }()) +// NOLINTEND(cppcoreguidelines-macro-usage) + +namespace panda::es2panda { + class ASTVerifierTest : public testing::Test { public: - ASTVerifierTest() = default; + ASTVerifierTest() + { + allocator_ = std::make_unique(SpaceType::SPACE_TYPE_COMPILER); + } ~ASTVerifierTest() override = default; + static void SetUpTestCase() + { + constexpr auto COMPILER_SIZE = 256_MB; + + mem::MemConfig::Initialize(0, 0, COMPILER_SIZE, 0, 0, 0); + PoolManager::Initialize(); + } + + ArenaAllocator *Allocator() + { + return allocator_.get(); + } + NO_COPY_SEMANTIC(ASTVerifierTest); NO_MOVE_SEMANTIC(ASTVerifierTest); private: + std::unique_ptr allocator_; }; TEST_F(ASTVerifierTest, NullParent) { - panda::es2panda::compiler::ASTVerifier verifier {}; - panda::es2panda::ir::StringLiteral empty_node; + compiler::ASTVerifier verifier {Allocator()}; + ir::StringLiteral empty_node; - bool has_parent = verifier.HasParent(&empty_node); - auto messages = verifier.GetErrorMessages(); + auto checks = compiler::ASTVerifier::CheckSet {Allocator()->Adapter()}; + checks.insert("HasParent"); + bool has_parent = verifier.Verify(&empty_node, checks); + const auto &errors = verifier.GetErrors(); + const auto [name, error] = errors[0]; ASSERT_EQ(has_parent, false); - ASSERT_NE(messages.size(), 0); - ASSERT_EQ(messages[0], "NULL_PARENT: STR_LITERAL "); + ASSERT_NE(errors.size(), 0); + ASSERT_EQ(name, "HasParent"); + ASSERT_EQ(error.message, "NULL_PARENT: STR_LITERAL "); } TEST_F(ASTVerifierTest, NullType) { - panda::es2panda::compiler::ASTVerifier verifier {}; - panda::es2panda::ir::StringLiteral empty_node; + compiler::ASTVerifier verifier {Allocator()}; + ir::StringLiteral empty_node; - bool has_type = verifier.HasType(&empty_node); - auto messages = verifier.GetErrorMessages(); + auto checks = compiler::ASTVerifier::CheckSet {Allocator()->Adapter()}; + checks.insert("HasType"); + bool has_type = verifier.Verify(&empty_node, checks); + const auto &errors = verifier.GetErrors(); + const auto [name, error] = errors[0]; ASSERT_EQ(has_type, false); - ASSERT_NE(messages.size(), 0); - ASSERT_EQ(messages[0], "NULL_TS_TYPE: STR_LITERAL "); + ASSERT_NE(errors.size(), 0); + ASSERT_EQ(name, "HasType"); + ASSERT_EQ(error.message, "NULL_TS_TYPE: STR_LITERAL "); } TEST_F(ASTVerifierTest, WithoutScope) { - panda::es2panda::compiler::ASTVerifier verifier {}; - panda::es2panda::ir::StringLiteral empty_node; + compiler::ASTVerifier verifier {Allocator()}; + ir::StringLiteral empty_node; - bool has_scope = verifier.HasScope(&empty_node); - auto messages = verifier.GetErrorMessages(); + auto checks = compiler::ASTVerifier::CheckSet {Allocator()->Adapter()}; + checks.insert("HasScope"); + bool has_scope = verifier.Verify(&empty_node, checks); + const auto &errors = verifier.GetErrors(); ASSERT_EQ(has_scope, true); - ASSERT_EQ(messages.size(), 0); + ASSERT_EQ(errors.size(), 0); } + +} // namespace panda::es2panda -- Gitee From 6442e3b1a67ded358aa89f13dbd82b14d7e63168 Mon Sep 17 00:00:00 2001 From: aleksisch Date: Thu, 23 Nov 2023 13:18:49 +0300 Subject: [PATCH 2/2] scope embedding check & scope enclose variable Change-Id: I61ab53d149323438be8b4b72fed7d14067c583f6 Signed-off-by: aleksisch --- ets2panda/compiler/core/ASTVerifier.cpp | 108 ++++++++++++++++++-- ets2panda/compiler/core/ASTVerifier.h | 15 +++ ets2panda/test/public/ast_verifier_test.cpp | 48 +++++++++ 3 files changed, 165 insertions(+), 6 deletions(-) diff --git a/ets2panda/compiler/core/ASTVerifier.cpp b/ets2panda/compiler/core/ASTVerifier.cpp index dd3e309bd3..9ede53666b 100644 --- a/ets2panda/compiler/core/ASTVerifier.cpp +++ b/ets2panda/compiler/core/ASTVerifier.cpp @@ -91,6 +91,8 @@ ASTVerifier::ASTVerifier(ArenaAllocator *allocator, util::StringView source_code ADD_CHECK(HasType); ADD_CHECK(HasVariable); ADD_CHECK(HasScope); + ADD_CHECK(VerifyChildNode); + ADD_CHECK(VerifyScopeNode); } bool ASTVerifier::VerifyFull(const ir::AstNode *ast) @@ -397,14 +399,108 @@ bool ASTVerifier::HasScope(const ir::AstNode *ast) return true; // we will check only Identifier } // we will check only local variables of identifiers - if (HasVariable(ast) && ast->AsIdentifier()->Variable()->IsLocalVariable() && - ast->AsIdentifier()->Variable()->AsLocalVariable()->GetScope() == nullptr) { - const auto *id = ast->AsIdentifier(); - AddError("NULL_SCOPE_LOCAL_VAR: " + ToStringHelper(ast), id->Start()); - return false; + if (const auto maybe_var = GetLocalScopeVariable(ast)) { + const auto var = *maybe_var; + const auto scope = var->GetScope(); + if (scope == nullptr) { + AddError("NULL_SCOPE_LOCAL_VAR: " + ToStringHelper(ast), ast->Start()); + return false; + } + return ScopeEncloseVariable(var); } - // NOTE(tatiana): Add check that the scope enclose this identifier return true; } +std::optional ASTVerifier::GetLocalScopeVariable(const ir::AstNode *ast) +{ + if (!ast->IsIdentifier()) { + return std::nullopt; + } + + if (HasVariable(ast) && ast->AsIdentifier()->Variable()->IsLocalVariable()) { + const auto local_var = ast->AsIdentifier()->Variable()->AsLocalVariable(); + if (local_var->HasFlag(varbinder::VariableFlags::LOCAL)) { + return local_var; + } + } + return std::nullopt; +} + +bool ASTVerifier::VerifyChildNode(const ir::AstNode *ast) +{ + ASSERT(ast); + bool is_ok; + ast->Iterate([&](const auto node) { + if (ast != node->Parent()) { + AddError("INCORRECT_PARENT_REF: " + ToStringHelper(node), node->Start()); + is_ok = false; + } + }); + return is_ok; +} + +bool ASTVerifier::VerifyScopeNode(const ir::AstNode *ast) +{ + ASSERT(ast); + const auto maybe_var = GetLocalScopeVariable(ast); + if (!maybe_var) { + return true; + } + const auto var = *maybe_var; + const auto scope = var->GetScope(); + if (scope == nullptr) { + // already checked + return false; + } + const auto enclose_scope = scope->EnclosingVariableScope(); + if (enclose_scope == nullptr) { + AddError("NO_ENCLOSING_VAR_SCOPE: " + ToStringHelper(ast), ast->Start()); + return false; + } + const auto node = scope->Node(); + bool is_ok = true; + if (!IsInherited(ast, node)) { + is_ok = false; + AddError("VARIABLE_NOT_ENCLOSE_SCOPE: " + ToStringHelper(ast), ast->Start()); + } + if (!IsInherited(scope, enclose_scope)) { + is_ok = false; + AddError("VARIABLE_NOT_ENCLOSE_SCOPE: " + ToStringHelper(ast), ast->Start()); + } + return is_ok; +} + +bool ASTVerifier::ScopeEncloseVariable(const varbinder::LocalVariable *var) +{ + ASSERT(var); + + const auto scope = var->GetScope(); + if (scope == nullptr || var->Declaration() == nullptr) { + return true; + } + const auto node = var->Declaration()->Node(); + if (node == nullptr) { + return true; + } + const auto var_start = node->Start(); + bool is_ok = true; + if (scope->Bindings().count(var->Name()) == 0) { + AddError("SCOPE_DO_NOT_ENCLOSE_LOCAL_VAR: " + ToStringHelper(node), var_start); + is_ok = false; + } + const auto scope_node = scope->Node(); + auto var_node = node; + if (!IsInherited(var_node, scope_node) || scope_node == nullptr) { + AddError("SCOPE_NODE_DONT_DOMINATE_VAR_NODE: " + ToStringHelper(node), var_start); + is_ok = false; + } + const auto &decls = scope->Decls(); + const auto decl_dominate = std::count(decls.begin(), decls.end(), var->Declaration()); + if (decl_dominate == 0) { + AddError("SCOPE_DECL_DONT_DOMINATE_VAR_DECL: " + ToStringHelper(node), var_start); + is_ok = false; + } + return is_ok; +} + } // namespace panda::es2panda::compiler diff --git a/ets2panda/compiler/core/ASTVerifier.h b/ets2panda/compiler/core/ASTVerifier.h index 0fce8e4f36..801c12b153 100644 --- a/ets2panda/compiler/core/ASTVerifier.h +++ b/ets2panda/compiler/core/ASTVerifier.h @@ -77,6 +77,8 @@ private: bool HasType(const ir::AstNode *ast); bool HasVariable(const ir::AstNode *ast); bool HasScope(const ir::AstNode *ast); + bool VerifyChildNode(const ir::AstNode *ast); + bool VerifyScopeNode(const ir::AstNode *ast); void AddError(const std::string &message, const lexer::SourcePosition &from) { @@ -84,6 +86,19 @@ private: encountered_errors_.emplace_back(Error {message, loc}); } + bool ScopeEncloseVariable(const varbinder::LocalVariable *var); + std::optional GetLocalScopeVariable(const ir::AstNode *ast); + + template + bool IsInherited(const T *child, T *parent) + { + while (child != nullptr && child != parent) { + child = child->Parent(); + } + return child == parent; + } + +private: std::optional index_; ArenaAllocator *allocator_; diff --git a/ets2panda/test/public/ast_verifier_test.cpp b/ets2panda/test/public/ast_verifier_test.cpp index 14766be293..e2f9b2bfae 100644 --- a/ets2panda/test/public/ast_verifier_test.cpp +++ b/ets2panda/test/public/ast_verifier_test.cpp @@ -25,6 +25,7 @@ #include "checker/ETSchecker.h" #include "ir/astDump.h" #include "ir/expressions/literals/stringLiteral.h" +#include "ir/expressions/identifier.h" // NOLINTBEGIN(cppcoreguidelines-macro-usage) #define TREE(node) \ @@ -120,4 +121,51 @@ TEST_F(ASTVerifierTest, WithoutScope) ASSERT_EQ(errors.size(), 0); } +TEST_F(ASTVerifierTest, ScopeTest) +{ + panda::es2panda::compiler::ASTVerifier verifier {Allocator()}; + panda::es2panda::ir::Identifier ident(panda::es2panda::util::StringView("var_decl"), Allocator()); + panda::es2panda::varbinder::LetDecl decl("test", &ident); + panda::es2panda::varbinder::LocalVariable local(&decl, panda::es2panda::varbinder::VariableFlags::LOCAL); + ident.SetVariable(&local); + + panda::es2panda::varbinder::LocalScope scope(Allocator(), nullptr); + panda::es2panda::varbinder::FunctionScope parent_scope(Allocator(), nullptr); + scope.SetParent(&parent_scope); + scope.AddDecl(Allocator(), &decl, panda::es2panda::ScriptExtension::ETS); + scope.BindNode(&ident); + + local.SetScope(&scope); + + auto checks = compiler::ASTVerifier::CheckSet {Allocator()->Adapter()}; + checks.insert("HasScope"); + bool is_ok = verifier.Verify(&ident, checks); + + ASSERT_EQ(is_ok, true); +} + +TEST_F(ASTVerifierTest, ScopeNodeTest) +{ + panda::es2panda::compiler::ASTVerifier verifier {Allocator()}; + panda::es2panda::ir::Identifier ident(panda::es2panda::util::StringView("var_decl"), Allocator()); + panda::es2panda::varbinder::LetDecl decl("test", &ident); + panda::es2panda::varbinder::LocalVariable local(&decl, panda::es2panda::varbinder::VariableFlags::LOCAL); + ident.SetVariable(&local); + + panda::es2panda::varbinder::LocalScope scope(Allocator(), nullptr); + panda::es2panda::varbinder::FunctionScope parent_scope(Allocator(), nullptr); + scope.SetParent(&parent_scope); + scope.AddDecl(Allocator(), &decl, panda::es2panda::ScriptExtension::ETS); + scope.BindNode(&ident); + parent_scope.BindNode(&ident); + + local.SetScope(&scope); + + auto checks = compiler::ASTVerifier::CheckSet {Allocator()->Adapter()}; + checks.insert("VerifyScopeNode"); + bool is_ok = verifier.Verify(&ident, checks); + + ASSERT_EQ(is_ok, true); +} + } // namespace panda::es2panda -- Gitee