diff --git a/ets2panda/CMakeLists.txt b/ets2panda/CMakeLists.txt index 463ac9740cc65699399329dc2787691508ead700..7b9b2ef09d72911843176db30d085ec5196eed47 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 fce72cee39540ca260c2bfe99921bce78fc74bef..dd3e309bd3b00e3518475e2d839a66335085caae 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 d5cbb7f2d5923998392a6a02d4cc482d1424d013..0fce8e4f36942a8f3473242834ce7ac19b7b523b 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 f3b2ff83a6ee0aba42f56ab2082eb0483dc834ac..389d4d7d9ef7ffa7e58b217bd5aef7084f1ed588 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 a602e480e52b0af635d8bcf31cb82414f9adcf08..21f50ed3dd9b79bdfbdb8ff205ad5e4693c59786 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/unit/public/ast_verifier_test.cpp b/ets2panda/test/unit/public/ast_verifier_test.cpp index dd36a1937988cfe8ea1ae3622f6bfae857a57f81..14766be29385ba07b865072e2c51c050714a0097 100644 --- a/ets2panda/test/unit/public/ast_verifier_test.cpp +++ b/ets2panda/test/unit/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