1/*
2 * Copyright (c) 2023-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#ifndef ES2PANDA_COMPILER_CORE_ASTVERIFIER_H
17#define ES2PANDA_COMPILER_CORE_ASTVERIFIER_H
18
19#include <algorithm>
20#include <iterator>
21#include <regex>
22#include <string>
23#include <unordered_set>
24
25#include "ast_verifier/checkContext.h"
26
27#include "ir/astNode.h"
28#include "ir/statements/blockStatement.h"
29#include "lexer/token/sourceLocation.h"
30#include "parser/program/program.h"
31#include "util/ustring.h"
32#include "utils/arena_containers.h"
33#include "varbinder/variable.h"
34
35namespace ark::es2panda::compiler::ast_verifier {
36
37using InvariantCheck = std::function<CheckResult(CheckContext &ctx, const ir::AstNode *)>;
38using Invariants = std::map<std::string, InvariantCheck>;
39
40using InvariantNameSet = std::set<std::string>;
41
42class VerificationContext final {
43public:
44    void IntroduceNewInvariants(util::StringView phaseName)
45    {
46        if (phaseName == "ScopesInitPhase") {
47            accumulatedChecks_.insert("NodeHasParentForAll");
48            accumulatedChecks_.insert("NodeHasSourceRangeForAll");
49            accumulatedChecks_.insert("EveryChildHasValidParentForAll");
50            accumulatedChecks_.insert("EveryChildInParentRangeForAll");
51            accumulatedChecks_.insert("VariableHasScopeForAll");
52        }
53        if (phaseName == "CheckerPhase") {
54            accumulatedChecks_.insert("NodeHasTypeForAll");
55            accumulatedChecks_.insert("IdentifierHasVariableForAll");
56            accumulatedChecks_.insert("ReferenceTypeAnnotationIsNullForAll");
57            accumulatedChecks_.insert("ArithmeticOperationValidForAll");
58            accumulatedChecks_.insert("SequenceExpressionHasLastTypeForAll");
59            accumulatedChecks_.insert("CheckInfiniteLoopForAll");
60            accumulatedChecks_.insert("ForLoopCorrectlyInitializedForAll");
61            accumulatedChecks_.insert("VariableHasEnclosingScopeForAll");
62            accumulatedChecks_.insert("ModifierAccessValidForAll");
63            accumulatedChecks_.insert("ImportExportAccessValid");
64            accumulatedChecks_.insert("VariableNameIdentifierNameSameForAll");
65            accumulatedChecks_.insert("CheckAbstractMethodForAll");
66            accumulatedChecks_.insert("GetterSetterValidationForAll");
67            accumulatedChecks_.insert("CheckScopeDeclarationForAll");
68        }
69    }
70
71    const InvariantNameSet &AccumulatedChecks() const
72    {
73        return accumulatedChecks_;
74    }
75
76private:
77    InvariantNameSet accumulatedChecks_ {};
78};
79
80/*
81 * ASTVerifier used for checking various invariants that should hold during AST transformation in lowerings
82 * For all available checks lookup the constructor
83 */
84class ASTVerifier final {
85public:
86    NO_COPY_SEMANTIC(ASTVerifier);
87    NO_MOVE_SEMANTIC(ASTVerifier);
88
89    explicit ASTVerifier(ArenaAllocator *allocator);
90    ~ASTVerifier() = default;
91
92    /**
93     * @brief Run all existing invariants on some ast node (and consequently it's children)
94     * @param ast AstNode which will be analyzed
95     * @return Messages report of analysis
96     */
97    Messages VerifyFull(const ir::AstNode *ast);
98
99    /**
100     * @brief Run some particular invariants on some ast node
101     * @note invariants must be supplied as strings to invariant_set, additionally invariant
102     * name can be suffixed by `ForAll` string to include recursive analysis of provided node
103     * I.e. 'HasParent' invariant can be named 'HasParentRecursive' to traverse all child nodes as well
104     * @param ast AstNode which will be analyzed
105     * @param invariantSet Set of invariants to check
106     * @return Messages report of analysis
107     */
108    Messages Verify(const ir::AstNode *ast, const InvariantNameSet &invariantSet);
109
110private:
111    static constexpr const char *RECURSIVE_SUFFIX = "ForAll";
112
113    static InvariantCheck RecursiveInvariant(const InvariantCheck &func)
114    {
115        return [func](CheckContext &ctx, const ir::AstNode *ast) -> CheckResult {
116            std::function<void(const ir::AstNode *)> aux;
117            auto finalDecision = CheckDecision::CORRECT;
118            aux = [&ctx, func, &aux, &finalDecision](const ir::AstNode *child) -> void {
119                const auto [decision, action] = func(ctx, child);
120                if (decision == CheckDecision::INCORRECT) {
121                    finalDecision = CheckDecision::INCORRECT;
122                }
123                if (action == CheckAction::SKIP_SUBTREE) {
124                    return;
125                }
126                child->Iterate(aux);
127            };
128            aux(ast);
129            return {finalDecision, CheckAction::CONTINUE};
130        };
131    }
132
133    template <typename T>
134    void AddInvariant(ArenaAllocator *allocator, const std::string &name)
135    {
136        auto check = *allocator->New<T>(*allocator);
137        invariantsChecks_[name] = check;
138        invariantsNames_.insert(name);
139        invariantsChecks_[name + RECURSIVE_SUFFIX] = RecursiveInvariant(check);
140        invariantsNames_.insert(name + RECURSIVE_SUFFIX);
141    }
142
143    Invariants invariantsChecks_;
144    InvariantNameSet invariantsNames_;
145};
146
147}  // namespace ark::es2panda::compiler::ast_verifier
148
149#endif  // ES2PANDA_COMPILER_CORE_ASTVERIFIER_H
150