From f10ea6d3696c689fe86d8b4d19b22a41c5ffbbeb Mon Sep 17 00:00:00 2001 From: Orlovsky Maxim Date: Fri, 27 Oct 2023 12:27:23 +0300 Subject: [PATCH] Title: Verifier for AST representation Ast verifier for checking private, protected and export. Rename IsOptional in AstNode. Fixed internal issue #14181. Check that `forIn/forOf` has correct `left_` declaration and `forUpdate` has `test_` or `body_` with exit statements. Scope embedding check & scope enclose variable Add arithmetic expression checker Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/I8ILY3 Testing: C++ unit tests, ci existing tests Signed-off-by: Amosov Alexey --- ets2panda/CMakeLists.txt | 3 + ets2panda/checker/ETSAnalyzer.cpp | 5 + ets2panda/checker/TSAnalyzer.cpp | 5 + ets2panda/compiler/core/ASTVerifier.cpp | 603 ++++++++++++++++-- ets2panda/compiler/core/ASTVerifier.h | 89 ++- ets2panda/compiler/core/ETSCompiler.cpp | 5 + ets2panda/compiler/core/JSCompiler.cpp | 5 + ets2panda/compiler/lowering/phase.cpp | 29 +- ets2panda/ir/astNode.h | 2 +- ets2panda/ir/astNodeMapping.h | 1 + ets2panda/ir/base/classProperty.cpp | 2 +- ets2panda/ir/base/methodDefinition.cpp | 2 +- ets2panda/ir/expressions/binaryExpression.h | 17 + ets2panda/parser/ETSparser.h | 2 +- ets2panda/parser/parserImpl.cpp | 5 + ets2panda/parser/parserImpl.h | 4 +- ets2panda/test/CMakeLists.txt | 3 +- .../test-lists/parser/parser-js-ignored.txt | 21 + .../test/unit/public/ast_verifier_test.cpp | 241 ++++++- 19 files changed, 924 insertions(+), 120 deletions(-) diff --git a/ets2panda/CMakeLists.txt b/ets2panda/CMakeLists.txt index d63126df9f..7a1858ff0f 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/checker/ETSAnalyzer.cpp b/ets2panda/checker/ETSAnalyzer.cpp index 923f84a35d..967232d3e0 100644 --- a/ets2panda/checker/ETSAnalyzer.cpp +++ b/ets2panda/checker/ETSAnalyzer.cpp @@ -370,6 +370,11 @@ checker::Type *ETSAnalyzer::Check([[maybe_unused]] ir::TSSignatureDeclaration *n UNREACHABLE(); } // from ets folder +checker::Type *ETSAnalyzer::Check([[maybe_unused]] ir::ETSScript *node) const +{ + UNREACHABLE(); +} + checker::Type *ETSAnalyzer::Check(ir::ETSClassLiteral *expr) const { ETSChecker *checker = GetETSChecker(); diff --git a/ets2panda/checker/TSAnalyzer.cpp b/ets2panda/checker/TSAnalyzer.cpp index c48b539cfb..9c0dbd5e89 100644 --- a/ets2panda/checker/TSAnalyzer.cpp +++ b/ets2panda/checker/TSAnalyzer.cpp @@ -224,6 +224,11 @@ checker::Type *TSAnalyzer::Check(ir::TSSignatureDeclaration *node) const return placeholder_obj; } // from ets folder +checker::Type *TSAnalyzer::Check([[maybe_unused]] ir::ETSScript *expr) const +{ + UNREACHABLE(); +} + checker::Type *TSAnalyzer::Check([[maybe_unused]] ir::ETSClassLiteral *expr) const { UNREACHABLE(); diff --git a/ets2panda/compiler/core/ASTVerifier.cpp b/ets2panda/compiler/core/ASTVerifier.cpp index fce72cee39..60cca9a8de 100644 --- a/ets2panda/compiler/core/ASTVerifier.cpp +++ b/ets2panda/compiler/core/ASTVerifier.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 - 2023 Huawei Device Co., Ltd. + * Copyright (c) 2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -15,70 +15,205 @@ #include "ASTVerifier.h" -#include "es2panda.h" -#include "varbinder/variableFlags.h" -#include "varbinder/scope.h" +#include "checker/types/typeFlag.h" #include "ir/astNode.h" -#include "ir/base/catchClause.h" #include "ir/base/classDefinition.h" #include "ir/base/classStaticBlock.h" #include "ir/base/methodDefinition.h" #include "ir/base/scriptFunction.h" #include "ir/ets/etsFunctionType.h" #include "ir/ets/etsNewClassInstanceExpression.h" -#include "ir/ets/etsPackageDeclaration.h" #include "ir/ets/etsParameterExpression.h" #include "ir/ets/etsTypeReference.h" #include "ir/ets/etsTypeReferencePart.h" +#include "ir/ets/etsImportDeclaration.h" +#include "ir/module/importSpecifier.h" +#include "ir/module/importNamespaceSpecifier.h" +#include "ir/module/importDefaultSpecifier.h" #include "ir/expressions/callExpression.h" +#include "ir/expressions/binaryExpression.h" #include "ir/expressions/functionExpression.h" #include "ir/expressions/identifier.h" -#include "ir/expressions/memberExpression.h" #include "ir/expressions/literals/numberLiteral.h" #include "ir/expressions/literals/stringLiteral.h" +#include "ir/expressions/memberExpression.h" #include "ir/statements/blockStatement.h" +#include "ir/statements/forInStatement.h" +#include "ir/statements/forOfStatement.h" +#include "ir/statements/forUpdateStatement.h" +#include "ir/statements/variableDeclaration.h" +#include "ir/statements/variableDeclarator.h" #include "ir/statements/classDeclaration.h" #include "ir/statements/expressionStatement.h" #include "ir/statements/throwStatement.h" #include "ir/statements/tryStatement.h" -#include "ir/statements/variableDeclaration.h" -#include "ir/statements/variableDeclarator.h" #include "ir/ts/tsClassImplements.h" #include "ir/ts/tsTypeParameter.h" #include "ir/ts/tsTypeParameterDeclaration.h" #include "ir/ts/tsTypeParameterInstantiation.h" +#include "lexer/token/tokenType.h" +#include "util/ustring.h" +#include "utils/arena_containers.h" +#include "varbinder/scope.h" + +#include +#include 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; + }; +} + +static bool IsNumericType(const ir::AstNode *ast) +{ + if (ast == nullptr) { + return false; + } - for (auto *statement : program->Ast()->Statements()) { - is_correct &= HaveParents(statement); + if (!ast->IsTyped()) { + return false; } - is_correct &= HaveParents(program->GlobalClass()); - for (auto *statement : program->Ast()->Statements()) { - is_correct &= HaveTypes(statement); + auto typed_ast = static_cast(ast); + + if (typed_ast->TsType() == nullptr) { + return false; } - is_correct &= HaveTypes(program->GlobalClass()); - for (auto *statement : program->Ast()->Statements()) { - is_correct &= HaveVariables(statement); + return typed_ast->TsType()->HasTypeFlag(checker::TypeFlag::ETS_NUMERIC) || + typed_ast->TsType()->HasTypeFlag(checker::TypeFlag::NUMBER_LITERAL) || + typed_ast->TsType()->HasTypeFlag(checker::TypeFlag::BIGINT_LITERAL); +} + +static bool IsStringType(const ir::AstNode *ast) +{ + if (ast == nullptr) { + return false; } - is_correct &= HaveVariables(program->GlobalClass()); - for (auto *statement : program->Ast()->Statements()) { - is_correct &= HaveScopes(statement); + if (!ast->IsTyped()) { + return false; } - 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; + auto typed_ast = static_cast(ast); + + if (typed_ast->TsType() == nullptr) { + return false; + } + + return typed_ast->TsType()->HasTypeFlag(checker::TypeFlag::STRING_LIKE); +} + +template +bool IsContainedIn(const T *child, const T *parent) +{ + if (child == nullptr || parent == nullptr) { + return false; + } + + std::unordered_set saved_nodes; + while (child != nullptr && child != parent) { + if (saved_nodes.find(child) != saved_nodes.end()) { + return false; + } + child = child->Parent(); + saved_nodes.emplace(child); + } + return child == parent; +} + +bool ValidateVariableAccess(const varbinder::LocalVariable *prop_var, const ir::MemberExpression *ast) +{ + const auto *decl = prop_var->Declaration(); + if (decl == nullptr) { + return false; + } + const auto *node = decl->Node(); + if (node == nullptr) { + return false; + } + auto *obj_type = ast->ObjType(); + if (obj_type == nullptr) { + return false; + } + const auto *obj_type_decl_node = obj_type->GetDeclNode(); + if (obj_type_decl_node == nullptr) { + return false; + } + const auto *parent_node = node->Parent(); + if (parent_node != nullptr && parent_node->IsClassDefinition() && obj_type_decl_node->IsClassDefinition()) { + if (IsContainedIn(ast, obj_type_decl_node->AsClassDefinition())) { + return true; + } + if (node->IsPrivate() && parent_node == obj_type_decl_node) { + return true; + } + if (node->IsProtected()) { + auto ret = obj_type->IsPropertyInherited(prop_var); + return ret; + } + } + return false; +} + +bool ValidateMethodAccess(const ir::MemberExpression *member_expression, const ir::CallExpression *ast) +{ + auto *obj_type = member_expression->ObjType(); + if (obj_type == nullptr) { + return false; + } + + if (obj_type->HasObjectFlag(checker::ETSObjectFlags::RESOLVED_SUPER) && obj_type->SuperType() != nullptr && + obj_type->SuperType()->HasObjectFlag(checker::ETSObjectFlags::BUILTIN_TYPE | + checker::ETSObjectFlags::GLOBAL_CLASS)) { + return true; + } + const auto *decl_node = obj_type->GetDeclNode(); + if (decl_node == nullptr) { + return false; + } + auto *signature = ast->Signature(); + if (signature == nullptr) { + return false; + } + auto *owner_sign = signature->Owner(); + if (owner_sign == nullptr) { + return false; + } + auto *decl_node_sign = owner_sign->GetDeclNode(); + if (decl_node_sign != nullptr && decl_node_sign->IsClassDefinition() && decl_node->IsClassDefinition()) { + if (IsContainedIn(ast, decl_node->AsClassDefinition())) { + return true; + } + if (signature->HasSignatureFlag(checker::SignatureFlags::PRIVATE) && decl_node_sign == decl_node) { + return true; + } + if (signature->HasSignatureFlag(checker::SignatureFlags::PROTECTED)) { + auto ret = obj_type->IsSignatureInherited(signature); + return ret; + } + } + return false; +} + +bool ValidateExport(const varbinder::Variable *var) +{ + const auto *decl = var->Declaration(); + if (decl == nullptr) { + return false; + } + const auto *node = decl->Node(); + if (node == nullptr) { + return false; + } + return node->IsExported(); } std::string ToStringHelper(const varbinder::ScopeType type) @@ -302,108 +437,438 @@ std::string ToStringHelper(const ir::AstNode *ast) } } +// 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"); \ + } +// NOLINTEND(cppcoreguidelines-macro-usage) + bool ASTVerifier::HasParent(const ir::AstNode *ast) { - if (ast == nullptr) { + if (ast != nullptr && !ast->IsETSScript() && ast->Parent() == nullptr) { + AddError("NULL_PARENT: " + ToStringHelper(ast), ast->Start()); return false; } - if (ast->Parent() == nullptr) { - error_messages_.push_back("NULL_PARENT: " + ToStringHelper(ast)); + return true; +} + +bool ASTVerifier::HasType(const ir::AstNode *ast) +{ + if (ast->IsTyped() && static_cast(ast)->TsType() == nullptr) { + AddError("NULL_TS_TYPE: " + ToStringHelper(ast), ast->Start()); return false; } + return true; +} + +bool ASTVerifier::HasVariable(const ir::AstNode *ast) +{ + if (!ast->IsIdentifier() || ast->AsIdentifier()->Variable() != nullptr) { + return true; + } + const auto *id = ast->AsIdentifier(); + AddError("NULL_VARIABLE: " + ToStringHelper(id), id->Start()); + return false; +} + +bool ASTVerifier::HasScope(const ir::AstNode *ast) +{ + if (!ast->IsIdentifier()) { + return true; // we will check only Identifier + } + + // we will check only local variables of identifiers + 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); + } return true; } -bool ASTVerifier::HaveParents(const ir::AstNode *ast) +std::optional ASTVerifier::GetLocalScopeVariable(const ir::AstNode *ast) { - if (ast == nullptr) { - return false; + if (!ast->IsIdentifier()) { + return std::nullopt; } - bool has_parent = HasParent(ast); - ast->IterateRecursively([this, &has_parent](ir::AstNode *child) { has_parent &= HasParent(child); }); - return has_parent; + const auto variable = ast->AsIdentifier()->Variable(); + if (HasVariable(ast) && variable->IsLocalVariable()) { + const auto local_var = variable->AsLocalVariable(); + if (local_var->HasFlag(varbinder::VariableFlags::LOCAL)) { + return local_var; + } + } + return std::nullopt; } -bool ASTVerifier::HasType(const ir::AstNode *ast) +bool ASTVerifier::VerifyChildNode(const ir::AstNode *ast) { - if (ast == nullptr) { + 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; } - - if (ast->IsTyped() && static_cast(ast)->TsType() == nullptr) { - error_messages_.push_back("NULL_TS_TYPE: " + ToStringHelper(ast)); + const auto enclose_scope = scope->EnclosingVariableScope(); + if (enclose_scope == nullptr) { + AddError("NO_ENCLOSING_VAR_SCOPE: " + ToStringHelper(ast), ast->Start()); return false; } - return true; + const auto node = scope->Node(); + bool is_ok = true; + if (!IsContainedIn(ast, node)) { + is_ok = false; + AddError("VARIABLE_NOT_ENCLOSE_SCOPE: " + ToStringHelper(ast), ast->Start()); + } + if (!IsContainedIn(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 (!IsContainedIn(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; } -bool ASTVerifier::HaveTypes(const ir::AstNode *ast) +bool ASTVerifier::CheckArithmeticExpression(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; + if (ast->IsBinaryExpression() && ast->AsBinaryExpression()->IsArithmetic()) { + if (ast->AsBinaryExpression()->OperatorType() == lexer::TokenType::PUNCTUATOR_PLUS && + IsStringType(ast->AsBinaryExpression()->Left()) && IsStringType(ast->AsBinaryExpression()->Right())) { + return true; + } + bool is_correct = true; + ast->Iterate([&is_correct](ir::AstNode *child) { is_correct &= (IsNumericType(child)); }); + return is_correct; + } + + return true; } -bool ASTVerifier::HasVariable(const ir::AstNode *ast) +bool ASTVerifier::IsForLoopCorrectInitialized(const ir::AstNode *ast) { if (ast == nullptr) { return false; } - if (!ast->IsIdentifier() || ast->AsIdentifier()->Variable() != nullptr) { - return true; + if (ast->IsForInStatement()) { + auto const *left = ast->AsForInStatement()->Left(); + if (left == nullptr) { + AddError("NULL FOR-IN-LEFT: " + ToStringHelper(ast), ast->Start()); + return false; + } + + if (!left->IsIdentifier() && !left->IsVariableDeclaration()) { + AddError("INCORRECT FOR-IN-LEFT: " + ToStringHelper(ast), ast->Start()); + return false; + } } - error_messages_.push_back("NULL_VARIABLE: " + ToStringHelper(ast->AsIdentifier())); - return false; + if (ast->IsForOfStatement()) { + auto const *left = ast->AsForOfStatement()->Left(); + if (left == nullptr) { + AddError("NULL FOR-OF-LEFT: " + ToStringHelper(ast), ast->Start()); + return false; + } + + if (!left->IsIdentifier() && !left->IsVariableDeclaration()) { + AddError("INCORRECT FOR-OF-LEFT: " + ToStringHelper(ast), ast->Start()); + return false; + } + } + + if (ast->IsForUpdateStatement()) { + // The most important part of for-loop is the test. + // But it also can be null. Then there must be break;(return) in the body. + auto const *test = ast->AsForUpdateStatement()->Test(); + if (test == nullptr) { + auto const *body = ast->AsForUpdateStatement()->Body(); + if (body == nullptr) { + AddError("NULL FOR-TEST AND FOR-BODY: " + ToStringHelper(ast), ast->Start()); + return false; + } + bool has_exit = body->IsBreakStatement() || body->IsReturnStatement(); + body->IterateRecursively([&has_exit](ir::AstNode *child) { + has_exit |= child->IsBreakStatement() || child->IsReturnStatement(); + }); + if (!has_exit) { + // an infinite loop + AddError("WARNING: NULL FOR-TEST AND FOR-BODY doesn't exit: " + ToStringHelper(ast), ast->Start()); + } + return true; + } + + if (test->IsExpression()) { + AddError("NULL FOR VAR: " + ToStringHelper(ast), ast->Start()); + return false; + } + } + return true; } -bool ASTVerifier::HaveVariables(const ir::AstNode *ast) +bool ASTVerifier::AreForLoopsCorrectInitialized(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 is_for_initialized = IsForLoopCorrectInitialized(ast); + ast->IterateRecursively( + [this, &is_for_initialized](ir::AstNode *child) { is_for_initialized &= IsForLoopCorrectInitialized(child); }); + return is_for_initialized; } -bool ASTVerifier::HasScope(const ir::AstNode *ast) +bool ASTVerifier::VerifyModifierAccess(const ir::AstNode *ast) { if (ast == nullptr) { return false; } + if (ast->IsMemberExpression()) { + const auto *prop_var = ast->AsMemberExpression()->PropVar(); + if (prop_var != nullptr && prop_var->HasFlag(varbinder::VariableFlags::PROPERTY) && + !ValidateVariableAccess(prop_var, ast->AsMemberExpression())) { + AddError("PROPERTY_NOT_VISIBLE_HERE: " + ToStringHelper(ast), ast->Start()); + return false; + } + } + if (ast->IsCallExpression()) { + const auto *call_expr = ast->AsCallExpression(); + const auto *callee = call_expr->Callee(); + if (callee != nullptr && callee->IsMemberExpression()) { + const auto *callee_member = callee->AsMemberExpression(); + const auto *prop_var_callee = callee_member->PropVar(); + if (prop_var_callee != nullptr && prop_var_callee->HasFlag(varbinder::VariableFlags::METHOD) && + !ValidateMethodAccess(callee_member, ast->AsCallExpression())) { + AddError("PROPERTY_NOT_VISIBLE_HERE: " + ToStringHelper(callee), callee->Start()); + return false; + } + } + } + return true; +} - if (!ast->IsIdentifier()) { - return true; // we will check only Identifier +bool ASTVerifier::VerifyExportAccess(const ir::AstNode *ast) +{ + if (ast == nullptr) { + return false; } - // 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)); + if (ast->IsETSImportDeclaration()) { + const auto import_decl = ast->AsETSImportDeclaration()->Specifiers(); + const auto name = [](ir::AstNode *const specifier) { + if (specifier->IsImportNamespaceSpecifier()) { + return specifier->AsImportNamespaceSpecifier()->Local()->Name(); + } + if (specifier->IsImportSpecifier()) { + return specifier->AsImportSpecifier()->Local()->Name(); + } + return specifier->AsImportDefaultSpecifier()->Local()->Name(); + }; + for (const auto import : import_decl) { + imported_variables_.emplace(name(import)); + } + } + if (ast->IsCallExpression()) { + const auto *call_expr = ast->AsCallExpression(); + const auto *callee = call_expr->Callee(); + if (callee != nullptr && callee->IsIdentifier() && + !HandleImportExportIdentifier(callee->AsIdentifier(), call_expr)) { + AddError("PROPERTY_NOT_VISIBLE_HERE(NOT_EXPORTED): " + ToStringHelper(callee), callee->Start()); + return false; + } + } + if (ast->IsIdentifier() && !HandleImportExportIdentifier(ast->AsIdentifier())) { + AddError("PROPERTY_NOT_VISIBLE_HERE(NOT_EXPORTED): " + ToStringHelper(ast), ast->Start()); return false; } - // NOTE(tatiana): Add check that the scope enclose this identifier return true; } -bool ASTVerifier::HaveScopes(const ir::AstNode *ast) +bool ASTVerifier::CheckImportExportMethod(const varbinder::Variable *var_callee, const ir::AstNode *call_expr, + util::StringView name) { - if (ast == nullptr) { + auto *signature = call_expr->AsCallExpression()->Signature(); + if (signature != nullptr && var_callee->Declaration() != nullptr && var_callee->Declaration()->Node() != nullptr && + !IsContainedIn(var_callee->Declaration()->Node(), signature->Owner()->GetDeclNode()) && + var_callee->Declaration()->Node() != signature->Owner()->GetDeclNode()) { + if (imported_variables_.find(name) != imported_variables_.end() || + imported_variables_.find(util::StringView("")) != imported_variables_.end()) { + return ValidateExport(var_callee); + } return false; } + return true; +} + +bool ASTVerifier::CheckImportExportVariable(const varbinder::Variable *var, const ir::Identifier *ident, + util::StringView name) +{ + if (!var->HasFlag(varbinder::VariableFlags::LOCAL) && !var->HasFlag(varbinder::VariableFlags::VAR) && + var->HasFlag(varbinder::VariableFlags::INITIALIZED) && var->Declaration() != nullptr && + var->Declaration()->Node() != nullptr && !var->Declaration()->Node()->IsMethodDefinition() && + !var->Declaration()->Node()->IsClassProperty()) { + auto var_parent = var->Declaration()->Node()->Parent(); + if (var_parent != nullptr && !IsContainedIn(ident->Parent(), var_parent) && ident->Parent() != var_parent) { + if (var->GetScope() != nullptr && var->GetScope()->Parent() != nullptr && + var->GetScope()->Parent()->IsGlobalScope() && + ident->GetTopStatement() == var_parent->GetTopStatement()) { + return true; + } + if (imported_variables_.find(name) != imported_variables_.end() || + imported_variables_.find(util::StringView("")) != imported_variables_.end()) { + return ValidateExport(var); + } + return false; + } + } + return true; +} - bool has_scope = HasScope(ast); - ast->IterateRecursively([this, &has_scope](ir::AstNode *child) { has_scope &= HasScope(child); }); - return has_scope; +bool ASTVerifier::HandleImportExportIdentifier(const ir::Identifier *ident, const ir::AstNode *call_expr) +{ + if (ident->IsReference()) { + const auto *var = ident->Variable(); + if (var != nullptr) { + if (var->HasFlag(varbinder::VariableFlags::METHOD) && call_expr != nullptr) { + return CheckImportExportMethod(var, call_expr, ident->Name()); + } + return CheckImportExportVariable(var, ident, ident->Name()); + } + } + return true; +} + +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); + } + + ADD_CHECK(HasParent); + ADD_CHECK(HasType); + ADD_CHECK(HasVariable); + ADD_CHECK(HasScope); + ADD_CHECK(VerifyChildNode); + ADD_CHECK(VerifyScopeNode); + ADD_CHECK(IsForLoopCorrectInitialized); + ADD_CHECK(AreForLoopsCorrectInitialized); + ADD_CHECK(VerifyModifierAccess); + ADD_CHECK(VerifyExportAccess); + ADD_CHECK(CheckArithmeticExpression); +} + +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 {}}}); + } + } + + for (const auto &[name, check] : checks_) { + if (check_set.find(name) != check_set.end()) { + check_and_report(name, check, ast); + } + } + return is_correct; } } // namespace panda::es2panda::compiler diff --git a/ets2panda/compiler/core/ASTVerifier.h b/ets2panda/compiler/core/ASTVerifier.h index d5cbb7f2d5..6637ec6e22 100644 --- a/ets2panda/compiler/core/ASTVerifier.h +++ b/ets2panda/compiler/core/ASTVerifier.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 - 2023 Huawei Device Co., Ltd. + * Copyright (c) 2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -16,36 +16,99 @@ #ifndef ES2PANDA_COMPILER_CORE_ASTVERIFIER_H #define ES2PANDA_COMPILER_CORE_ASTVERIFIER_H -#include "parser/program/program.h" +#include "ir/astNode.h" +#include "lexer/token/sourceLocation.h" +#include "util/ustring.h" +#include "utils/arena_containers.h" +#include "varbinder/variable.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); + bool VerifyChildNode(const ir::AstNode *ast); + bool VerifyScopeNode(const ir::AstNode *ast); + bool CheckArithmeticExpression(const ir::AstNode *ast); + bool IsForLoopCorrectInitialized(const ir::AstNode *ast); + bool AreForLoopsCorrectInitialized(const ir::AstNode *ast); + bool VerifyModifierAccess(const ir::AstNode *ast); + bool VerifyExportAccess(const ir::AstNode *ast); - ErrorMessages GetErrorMessages() + bool HandleImportExportIdentifier(const ir::Identifier *ident, const ir::AstNode *call_expr = nullptr); + bool CheckImportExportVariable(const varbinder::Variable *var, const ir::Identifier *ident, util::StringView name); + bool CheckImportExportMethod(const varbinder::Variable *var_callee, const ir::AstNode *call_expr, + util::StringView name); + + 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}); } + bool ScopeEncloseVariable(const varbinder::LocalVariable *var); + std::optional GetLocalScopeVariable(const ir::AstNode *ast); + private: - ErrorMessages error_messages_; + std::optional index_; + + ArenaAllocator *allocator_; + Errors named_errors_; + ArenaVector encountered_errors_; + Checks checks_; + CheckSet all_checks_; + std::unordered_set imported_variables_; }; std::string ToStringHelper(const ir::AstNode *ast); diff --git a/ets2panda/compiler/core/ETSCompiler.cpp b/ets2panda/compiler/core/ETSCompiler.cpp index bb9125011a..f4209e0e1e 100644 --- a/ets2panda/compiler/core/ETSCompiler.cpp +++ b/ets2panda/compiler/core/ETSCompiler.cpp @@ -139,6 +139,11 @@ void ETSCompiler::Compile([[maybe_unused]] const ir::TSSignatureDeclaration *nod UNREACHABLE(); } // from ets folder +void ETSCompiler::Compile([[maybe_unused]] const ir::ETSScript *node) const +{ + UNREACHABLE(); +} + void ETSCompiler::Compile(const ir::ETSClassLiteral *expr) const { ETSGen *etsg = GetETSGen(); diff --git a/ets2panda/compiler/core/JSCompiler.cpp b/ets2panda/compiler/core/JSCompiler.cpp index a328ddf565..6f11b26be7 100644 --- a/ets2panda/compiler/core/JSCompiler.cpp +++ b/ets2panda/compiler/core/JSCompiler.cpp @@ -469,6 +469,11 @@ void JSCompiler::Compile([[maybe_unused]] const ir::TSSignatureDeclaration *node UNREACHABLE(); } // from ets folder +void JSCompiler::Compile([[maybe_unused]] const ir::ETSScript *expr) const +{ + UNREACHABLE(); +} + void JSCompiler::Compile([[maybe_unused]] const ir::ETSClassLiteral *expr) const { UNREACHABLE(); diff --git a/ets2panda/compiler/lowering/phase.cpp b/ets2panda/compiler/lowering/phase.cpp index d9d06b2190..6fb3e350c8 100644 --- a/ets2panda/compiler/lowering/phase.cpp +++ b/ets2panda/compiler/lowering/phase.cpp @@ -65,6 +65,25 @@ std::vector GetETSPhaseList() bool Phase::Apply(public_lib::Context *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->compiler_context->Options(); const auto name = std::string {Name()}; if (options->skip_phases.count(name) > 0) { @@ -77,10 +96,6 @@ bool Phase::Apply(public_lib::Context *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 {}); @@ -97,10 +112,8 @@ bool Phase::Apply(public_lib::Context *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/ir/astNode.h b/ets2panda/ir/astNode.h index 52ae35ba71..888028f17e 100644 --- a/ets2panda/ir/astNode.h +++ b/ets2panda/ir/astNode.h @@ -284,7 +284,7 @@ public: return (flags_ & ModifierFlags::READONLY) != 0; } - [[nodiscard]] bool IsOptional() const noexcept + [[nodiscard]] bool IsOptionalDeclaration() const noexcept { return (flags_ & ModifierFlags::OPTIONAL) != 0; } diff --git a/ets2panda/ir/astNodeMapping.h b/ets2panda/ir/astNodeMapping.h index 6a1dd0e6d3..94ad053988 100644 --- a/ets2panda/ir/astNodeMapping.h +++ b/ets2panda/ir/astNodeMapping.h @@ -91,6 +91,7 @@ _(ETS_IMPORT_DECLARATION, ETSImportDeclaration) \ _(ETS_PARAMETER_EXPRESSION, ETSParameterExpression) \ _(ETS_TUPLE, ETSTuple) \ + _(ETS_SCRIPT, ETSScript) \ _(SUPER_EXPRESSION, SuperExpression) \ _(STRUCT_DECLARATION, ETSStructDeclaration) \ _(SWITCH_CASE_STATEMENT, SwitchCaseStatement) \ diff --git a/ets2panda/ir/base/classProperty.cpp b/ets2panda/ir/base/classProperty.cpp index 081a992abf..26e1c96ed9 100644 --- a/ets2panda/ir/base/classProperty.cpp +++ b/ets2panda/ir/base/classProperty.cpp @@ -71,7 +71,7 @@ void ClassProperty::Dump(ir::AstDumper *dumper) const {"static", IsStatic()}, {"readonly", IsReadonly()}, {"declare", IsDeclare()}, - {"optional", IsOptional()}, + {"optional", IsOptionalDeclaration()}, {"computed", is_computed_}, {"typeAnnotation", AstDumper::Optional(type_annotation_)}, {"definite", IsDefinite()}, diff --git a/ets2panda/ir/base/methodDefinition.cpp b/ets2panda/ir/base/methodDefinition.cpp index a74af0796a..0f261bdc94 100644 --- a/ets2panda/ir/base/methodDefinition.cpp +++ b/ets2panda/ir/base/methodDefinition.cpp @@ -113,7 +113,7 @@ void MethodDefinition::Dump(ir::AstDumper *dumper) const {"kind", kind}, {"accessibility", AstDumper::Optional(AstDumper::ModifierToString(flags_))}, {"static", IsStatic()}, - {"optional", IsOptional()}, + {"optional", IsOptionalDeclaration()}, {"computed", is_computed_}, {"value", value_}, {"overloads", overloads_}, diff --git a/ets2panda/ir/expressions/binaryExpression.h b/ets2panda/ir/expressions/binaryExpression.h index 69bdfa92f8..81c361b4e6 100644 --- a/ets2panda/ir/expressions/binaryExpression.h +++ b/ets2panda/ir/expressions/binaryExpression.h @@ -86,6 +86,23 @@ public: operator_ == lexer::TokenType::PUNCTUATOR_LOGICAL_OR; } + [[nodiscard]] bool IsArithmetic() const noexcept + { + return operator_ == lexer::TokenType::PUNCTUATOR_PLUS || operator_ == lexer::TokenType::PUNCTUATOR_MINUS || + operator_ == lexer::TokenType::PUNCTUATOR_MULTIPLY || operator_ == lexer::TokenType::PUNCTUATOR_DIVIDE || + operator_ == lexer::TokenType::PUNCTUATOR_MOD || operator_ == lexer::TokenType::PUNCTUATOR_BITWISE_OR || + operator_ == lexer::TokenType::PUNCTUATOR_BITWISE_XOR || + operator_ == lexer::TokenType::PUNCTUATOR_BITWISE_AND || + operator_ == lexer::TokenType::PUNCTUATOR_PLUS_EQUAL || + operator_ == lexer::TokenType::PUNCTUATOR_MINUS_EQUAL || + operator_ == lexer::TokenType::PUNCTUATOR_MULTIPLY_EQUAL || + operator_ == lexer::TokenType::PUNCTUATOR_DIVIDE_EQUAL || + operator_ == lexer::TokenType::PUNCTUATOR_MOD_EQUAL || + operator_ == lexer::TokenType::PUNCTUATOR_BITWISE_AND_EQUAL || + operator_ == lexer::TokenType::PUNCTUATOR_BITWISE_OR_EQUAL || + operator_ == lexer::TokenType::PUNCTUATOR_BITWISE_XOR_EQUAL; + } + void SetLeft(Expression *expr) noexcept { left_ = expr; diff --git a/ets2panda/parser/ETSparser.h b/ets2panda/parser/ETSparser.h index 91146099b9..dcff82e498 100644 --- a/ets2panda/parser/ETSparser.h +++ b/ets2panda/parser/ETSparser.h @@ -338,7 +338,7 @@ private: std::string_view file_name = DEFAULT_SOURCE_FILE); ir::TypeNode *CreateTypeAnnotation(TypeAnnotationParsingOptions *options, std::string_view source_code, std::string_view file_name = DEFAULT_SOURCE_FILE); - + // NOLINTEND(google-default-arguments) friend class ExternalSourceParser; friend class InnerSourceParser; diff --git a/ets2panda/parser/parserImpl.cpp b/ets2panda/parser/parserImpl.cpp index 4a19f88427..1d631d932c 100644 --- a/ets2panda/parser/parserImpl.cpp +++ b/ets2panda/parser/parserImpl.cpp @@ -1206,6 +1206,11 @@ void ParserImpl::ThrowSyntaxError(std::string_view error_message, const lexer::S throw Error {ErrorType::SYNTAX, program_->SourceFile().Utf8(), error_message, loc.line, loc.col}; } +void ParserImpl::ThrowAllocationError(std::string_view message) const +{ + throw Error(ErrorType::GENERIC, program_->SourceFile().Utf8(), message); +} + ScriptExtension ParserImpl::Extension() const { return program_->Extension(); diff --git a/ets2panda/parser/parserImpl.h b/ets2panda/parser/parserImpl.h index 64a4b9b3f3..9f2ef98558 100644 --- a/ets2panda/parser/parserImpl.h +++ b/ets2panda/parser/parserImpl.h @@ -213,6 +213,8 @@ protected: ir::MethodDefinition *last_overload, bool impl_exists, bool is_abstract = false); + void ThrowAllocationError(std::string_view message) const; + void ValidateAccessor(ExpressionParseFlags flags, lexer::TokenFlags current_token_flags); void CheckPropertyKeyAsyncModifier(ParserStatus *method_status); ir::Property *ParseShorthandProperty(const lexer::LexerPosition *start_pos); @@ -281,7 +283,7 @@ protected: { auto *ret = program_->Allocator()->New(std::forward(args)...); if (ret == nullptr) { - throw Error(ErrorType::GENERIC, program_->SourceFile().Utf8(), "Unsuccessful allocation during parsing"); + ThrowAllocationError("Unsuccessful allocation during parsing"); } return ret; diff --git a/ets2panda/test/CMakeLists.txt b/ets2panda/test/CMakeLists.txt index f30dfa56a4..906befaf5e 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} ) @@ -114,6 +114,7 @@ if(PANDA_WITH_ETS) es2panda-lib INCLUDE_DIRS ${ES2PANDA_PATH} + ${ES2PANDA_BINARY_ROOT} SANITIZERS ${PANDA_SANITIZERS_LIST} ) diff --git a/ets2panda/test/test-lists/parser/parser-js-ignored.txt b/ets2panda/test/test-lists/parser/parser-js-ignored.txt index f8691f4193..f44c772da3 100644 --- a/ets2panda/test/test-lists/parser/parser-js-ignored.txt +++ b/ets2panda/test/test-lists/parser/parser-js-ignored.txt @@ -33,6 +33,27 @@ compiler/ets/override3.ets compiler/ets/override4.ets compiler/ets/override5.ets +# panda#14517 +compiler/ets/dynamic_call.ets +compiler/ets/dynamic-equality.ets +compiler/ets/dynamic-equality-error.ets +compiler/ets/dynamic_instanceof.ets +compiler/ets/dynamic_instanceof_error.ets +compiler/ets/dynamicJsImport.ets +compiler/ets/dynamicLambda.ets +compiler/ets/dynamicLambda_error.ets +compiler/ets/dynamicLambdaJSValue.ets +compiler/ets/dynamicLambdaJSValueCast.ets +compiler/ets/dynamicObjectLiteral.ets +parser/ets/dynamic_import_tests/dynamic_class_ctor_decl_import_bad.ets +parser/ets/dynamic_import_tests/dynamic_class_field_decl_import_bad_1.ets +parser/ets/dynamic_import_tests/dynamic_class_field_decl_import_bad_2.ets +parser/ets/dynamic_import_tests/dynamic_class_method_decl_import_bad_1.ets +parser/ets/dynamic_import_tests/dynamic_class_method_decl_import_bad_2.ets +parser/ets/dynamic_import_tests/dynamic_decl_import.ets +parser/ets/dynamic_import_tests/dynamic_func_decl_import_bad.ets +parser/ets/dynamic_import_tests/dynamic_iface_decl_bad.ets + # Disabled temporarily #11468 parser/ets/null-coalesc-negative.ets diff --git a/ets2panda/test/unit/public/ast_verifier_test.cpp b/ets2panda/test/unit/public/ast_verifier_test.cpp index dd36a19379..77acdc5129 100644 --- a/ets2panda/test/unit/public/ast_verifier_test.cpp +++ b/ets2panda/test/unit/public/ast_verifier_test.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2023 Huawei Device Co., Ltd. + * Copyright (c) 2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,59 +13,252 @@ * limitations under the License. */ -#include -#include -#include "macros.h" - +#include "checker/ETSchecker.h" #include "compiler/core/ASTVerifier.h" -#include "ir/astDump.h" #include "ir/expressions/literals/stringLiteral.h" +#include "ir/expressions/identifier.h" +#include "ir/expressions/literals/numberLiteral.h" +#include "ir/expressions/literals/booleanLiteral.h" +#include "macros.h" +#include "parser/ETSparser.h" +#include "varbinder/ETSBinder.h" + +#include +#include + +// 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 = operator""_MB(256ULL); + 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_EQ(errors.size(), 1); + 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); +} + +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); } + +TEST_F(ASTVerifierTest, ArithmeticExpressionCorrect1) +{ + panda::es2panda::checker::ETSChecker etschecker {}; + panda::es2panda::compiler::ASTVerifier verifier {Allocator()}; + auto program = panda::es2panda::parser::Program::NewProgram(Allocator()); + auto parser = panda::es2panda::parser::ETSParser(&program, panda::es2panda::CompilerOptions {}); + + auto left = panda::es2panda::ir::NumberLiteral(panda::es2panda::lexer::Number {1}); + auto right = panda::es2panda::ir::NumberLiteral(panda::es2panda::lexer::Number {6}); + auto arithmetic_expression = + panda::es2panda::ir::BinaryExpression(&left, &right, panda::es2panda::lexer::TokenType::PUNCTUATOR_PLUS); + + left.SetTsType(etschecker.GlobalIntType()); + right.SetTsType(etschecker.GlobalIntType()); + + auto checks = compiler::ASTVerifier::CheckSet {Allocator()->Adapter()}; + checks.insert("CheckArithmeticExpression"); + bool is_correct = verifier.Verify(arithmetic_expression.AsBinaryExpression(), checks); + ASSERT_EQ(is_correct, true); +} + +TEST_F(ASTVerifierTest, ArithmeticExpressionCorrect2) +{ + panda::es2panda::checker::ETSChecker etschecker {}; + panda::es2panda::compiler::ASTVerifier verifier {Allocator()}; + auto program = panda::es2panda::parser::Program::NewProgram(Allocator()); + auto parser = panda::es2panda::parser::ETSParser(&program, panda::es2panda::CompilerOptions {}); + + constexpr uint32_t LEFT1_PARAM = 1; + constexpr uint32_t LEFT2_PARAM = 12; + constexpr uint32_t RIGHT2_PARAM = 6; + auto left1 = panda::es2panda::ir::NumberLiteral(panda::es2panda::lexer::Number {LEFT1_PARAM}); + auto left2 = panda::es2panda::ir::NumberLiteral(panda::es2panda::lexer::Number {LEFT2_PARAM}); + auto right2 = panda::es2panda::ir::NumberLiteral(panda::es2panda::lexer::Number {RIGHT2_PARAM}); + auto right1 = + panda::es2panda::ir::BinaryExpression(&left2, &right2, panda::es2panda::lexer::TokenType::PUNCTUATOR_MULTIPLY); + auto arithmetic_expression = + panda::es2panda::ir::BinaryExpression(&left1, &right1, panda::es2panda::lexer::TokenType::PUNCTUATOR_PLUS); + + left1.SetTsType(etschecker.GlobalIntType()); + right1.SetTsType(etschecker.GlobalIntType()); + left2.SetTsType(etschecker.GlobalIntType()); + right2.SetTsType(etschecker.GlobalIntType()); + + auto checks = compiler::ASTVerifier::CheckSet {Allocator()->Adapter()}; + checks.insert("CheckArithmeticExpression"); + bool is_correct = verifier.Verify(arithmetic_expression.AsBinaryExpression(), checks); + ASSERT_EQ(is_correct, true); +} + +TEST_F(ASTVerifierTest, ArithmeticExpressionNegative1) +{ + panda::es2panda::checker::ETSChecker etschecker {}; + panda::es2panda::compiler::ASTVerifier verifier {Allocator()}; + auto program = panda::es2panda::parser::Program::NewProgram(Allocator()); + auto parser = panda::es2panda::parser::ETSParser(&program, panda::es2panda::CompilerOptions {}); + + const util::StringView left_param("1"); + constexpr uint32_t RIGHT_PARAM = 1; + auto left = panda::es2panda::ir::StringLiteral(left_param); + auto right = panda::es2panda::ir::NumberLiteral(panda::es2panda::lexer::Number {RIGHT_PARAM}); + auto arithmetic_expression = + panda::es2panda::ir::BinaryExpression(&left, &right, panda::es2panda::lexer::TokenType::PUNCTUATOR_DIVIDE); + + left.SetTsType(etschecker.GlobalETSStringLiteralType()); + right.SetTsType(etschecker.GlobalIntType()); + + auto checks = compiler::ASTVerifier::CheckSet {Allocator()->Adapter()}; + checks.insert("CheckArithmeticExpression"); + bool is_correct = verifier.Verify(arithmetic_expression.AsBinaryExpression(), checks); + + ASSERT_EQ(is_correct, false); +} + +TEST_F(ASTVerifierTest, ArithmeticExpressionNegative2) +{ + panda::es2panda::checker::ETSChecker etschecker {}; + panda::es2panda::compiler::ASTVerifier verifier {Allocator()}; + auto program = panda::es2panda::parser::Program::NewProgram(Allocator()); + auto parser = panda::es2panda::parser::ETSParser(&program, panda::es2panda::CompilerOptions {}); + auto left = panda::es2panda::ir::BooleanLiteral(true); + auto right = panda::es2panda::ir::NumberLiteral(panda::es2panda::lexer::Number {1}); + auto arithmetic_expression = + panda::es2panda::ir::BinaryExpression(&left, &right, panda::es2panda::lexer::TokenType::PUNCTUATOR_DIVIDE); + + left.SetTsType(etschecker.GlobalETSStringLiteralType()); + right.SetTsType(etschecker.GlobalIntType()); + + auto checks = compiler::ASTVerifier::CheckSet {Allocator()->Adapter()}; + checks.insert("CheckArithmeticExpression"); + bool is_correct = verifier.Verify(arithmetic_expression.AsBinaryExpression(), checks); + + ASSERT_EQ(is_correct, false); +} + +} // namespace panda::es2panda -- Gitee