• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "ir/astNode.h"
26 #include "ir/statements/blockStatement.h"
27 #include "lexer/token/sourceLocation.h"
28 #include "parser/program/program.h"
29 #include "util/ustring.h"
30 #include "utils/arena_containers.h"
31 #include "utils/json_builder.h"
32 #include "varbinder/variable.h"
33 
34 namespace panda::es2panda::compiler {
35 
36 /*
37  * ASTVerifier used for checking various invariants that should hold during AST transformation in lowerings
38  * For all available checks lookup the constructor
39  */
40 class ASTVerifier final {
41 public:
42     struct InvariantError {
43         std::string cause;
44         std::string message;
45         size_t line;
46     };
47     struct CheckError {
CheckErrorCheckError48         explicit CheckError(std::string name, InvariantError error)
49             : invariantName_ {std::move(name)}, error_ {std::move(error)}
50         {
51         }
DumpJSONCheckError52         std::function<void(JsonObjectBuilder &)> DumpJSON() const
53         {
54             return [&](JsonObjectBuilder &body) {
55                 body.AddProperty("invariant", invariantName_);
56                 body.AddProperty("cause", error_.cause);
57                 body.AddProperty("message", error_.message);
58                 body.AddProperty("line", error_.line + 1);
59             };
60         }
GetNameCheckError61         const std::string &GetName() const
62         {
63             return invariantName_;
64         }
65 
66     private:
67         std::string invariantName_;
68         InvariantError error_;
69     };
70     using Errors = std::vector<CheckError>;
71 
72     enum class CheckResult { FAILED, SUCCESS, SKIP_SUBTREE };
73     class ErrorContext {
74     public:
75         explicit ErrorContext() = default;
76 
AddError(const std::string & message)77         void AddError(const std::string &message)
78         {
79             errors_.emplace_back(CheckError {"Unnamed", ASTVerifier::InvariantError {message, "", 0}});
80         }
81 
AddInvariantError(const std::string & name,const std::string & cause,const ir::AstNode & node)82         virtual void AddInvariantError(const std::string &name, const std::string &cause, const ir::AstNode &node)
83         {
84             errors_.emplace_back(
85                 CheckError {name, ASTVerifier::InvariantError {cause, node.DumpJSON(), node.Start().line}});
86         }
87 
GetErrors()88         ASTVerifier::Errors GetErrors()
89         {
90             return errors_;
91         }
92 
93     private:
94         Errors errors_;
95     };
96 
97     class AssertsContext : public ErrorContext {
98     public:
AddInvariantError(const std::string & name,const std::string & cause,const ir::AstNode & node)99         void AddInvariantError(const std::string &name, const std::string &cause, const ir::AstNode &node) override
100         {
101             ASTVerifier::ErrorContext::AddInvariantError(name, cause, node);
102             // NOTE(tatiana): add ASSERT here
103         }
104     };
105 
106     class NoneContext : public ErrorContext {
107     public:
AddInvariantError(const std::string & name,const std::string & cause,const ir::AstNode & node)108         void AddInvariantError([[maybe_unused]] const std::string &name, [[maybe_unused]] const std::string &cause,
109                                [[maybe_unused]] const ir::AstNode &node) override
110         {
111         }
112     };
113     using InvariantCheck = std::function<CheckResult(ErrorContext &ctx, const ir::AstNode *)>;
114     struct Invariant {
115         util::StringView invariantName;
116         InvariantCheck invariant;
117     };
118     using Invariants = std::map<std::string, InvariantCheck>;
119 
120     NO_COPY_SEMANTIC(ASTVerifier);
121     NO_MOVE_SEMANTIC(ASTVerifier);
122 
123     explicit ASTVerifier(ArenaAllocator *allocator);
124     ~ASTVerifier() = default;
125 
126     using InvariantSet = std::unordered_set<std::string>;
127 
128     /**
129      * @brief Run all existing invariants on some ast node (and consequently it's children)
130      * @param ast AstNode which will be analyzed
131      * @return Errors report of analysis
132      */
133     std::tuple<ASTVerifier::Errors, ASTVerifier::Errors> VerifyFull(const std::unordered_set<std::string> &warnings,
134                                                                     const std::unordered_set<std::string> &asserts,
135                                                                     const ir::AstNode *ast);
136 
137     /**
138      * @brief Run some particular invariants on some ast node
139      * @note invariants must be supplied as strings to invariant_set, additionally invariant
140      * name can be suffixed by `ForAll` string to include recursive analysis of provided node
141      * I.e. 'HasParent' invariant can be named 'HasParentRecursive' to traverse all child nodes as well
142      * @param ast AstNode which will be analyzed
143      * @param invariant_set Set of strings which will be used as invariant names
144      * @return Errors report of analysis
145      */
146     std::tuple<ASTVerifier::Errors, ASTVerifier::Errors> Verify(const std::unordered_set<std::string> &warnings,
147                                                                 const std::unordered_set<std::string> &asserts,
148                                                                 const ir::AstNode *ast,
149                                                                 const InvariantSet &invariantSet);
150 
151 private:
152     void AddInvariant(const std::string &name, const InvariantCheck &invariant);
153 
154     Invariants invariantsChecks_;
155     InvariantSet invariantsNames_;
156 };
157 
158 class ASTVerifierContext final {
159 public:
ASTVerifierContext(ASTVerifier & verifier)160     explicit ASTVerifierContext(ASTVerifier &verifier) : verifier_ {verifier} {}
161 
IntroduceNewInvariants(util::StringView phaseName)162     void IntroduceNewInvariants(util::StringView phaseName)
163     {
164         auto invariantSet = [phaseName]() -> std::optional<ASTVerifier::InvariantSet> {
165             (void)phaseName;
166             if (phaseName == "ScopesInitPhase") {
167                 return {{
168                     "NodeHasParentForAll",
169                     "EveryChildHasValidParentForAll",
170                     "VariableHasScopeForAll",
171                 }};
172             }
173             if (phaseName == "CheckerPhase") {
174                 return {{
175                     "NodeHasTypeForAll",
176                     "IdentifierHasVariableForAll",
177                     "ArithmeticOperationValidForAll",
178                     "SequenceExpressionHasLastTypeForAll",
179                     "ForLoopCorrectlyInitializedForAll",
180                     "VariableHasEnclosingScopeForAll",
181                     "ModifierAccessValidForAll",
182                     "ImportExportAccessValid",
183                 }};
184             }
185             const std::set<std::string> withoutAdditionalChecks = {"PromiseVoidInferencePhase",
186                                                                    "StructLowering",
187                                                                    "GenerateTsDeclarationsPhase",
188                                                                    "InterfacePropertyDeclarationsPhase",
189                                                                    "LambdaConstructionPhase",
190                                                                    "ObjectIndexLowering",
191                                                                    "OpAssignmentLowering",
192                                                                    "PromiseVoidInferencePhase",
193                                                                    "TupleLowering",
194                                                                    "UnionLowering",
195                                                                    "ExpandBracketsPhase"};
196             if (withoutAdditionalChecks.count(phaseName.Mutf8()) > 0) {
197                 return {{}};
198             }
199             if (phaseName.Utf8().find("plugins") != std::string_view::npos) {
200                 return {{}};
201             }
202             return std::nullopt;
203         }();
204 
205         ASSERT_PRINT(invariantSet.has_value(),
206                      std::string {"Invariant set does not contain value for "} + phaseName.Mutf8());
207         const auto &s = *invariantSet;
208         accumulatedChecks_.insert(s.begin(), s.end());
209     }
210 
Verify(const std::unordered_set<std::string> & warnings,const std::unordered_set<std::string> & errors,const ir::AstNode * ast,util::StringView phaseName,util::StringView sourceName)211     bool Verify(const std::unordered_set<std::string> &warnings, const std::unordered_set<std::string> &errors,
212                 const ir::AstNode *ast, util::StringView phaseName, util::StringView sourceName)
213     {
214         auto [warns, asserts] = verifier_.Verify(warnings, errors, ast, accumulatedChecks_);
215         std::for_each(warns.begin(), warns.end(), [this, &sourceName, &phaseName](ASTVerifier::CheckError &e) {
216             warnings_.Add([e, sourceName, phaseName](JsonObjectBuilder &err) {
217                 err.AddProperty("from", sourceName.Utf8());
218                 err.AddProperty("phase", phaseName.Utf8());
219                 err.AddProperty("error", e.DumpJSON());
220             });
221         });
222         std::for_each(asserts.begin(), asserts.end(), [this, &sourceName, &phaseName](ASTVerifier::CheckError &e) {
223             asserts_.Add([e, sourceName, phaseName](JsonObjectBuilder &err) {
224                 err.AddProperty("from", sourceName.Utf8());
225                 err.AddProperty("phase", phaseName.Utf8());
226                 err.AddProperty("error", e.DumpJSON());
227             });
228         });
229         return warns.empty() && asserts.empty();
230     }
231 
DumpWarningsJSON()232     std::string DumpWarningsJSON()
233     {
234         return std::move(warnings_).Build();
235     }
DumpAssertsJSON()236     std::string DumpAssertsJSON()
237     {
238         return std::move(asserts_).Build();
239     }
240 
241 private:
242     ASTVerifier &verifier_;
243     JsonArrayBuilder warnings_;
244     JsonArrayBuilder asserts_;
245     ASTVerifier::InvariantSet accumulatedChecks_ {};
246 };
247 
248 }  // namespace panda::es2panda::compiler
249 
250 #endif  // ES2PANDA_COMPILER_CORE_ASTVERIFIER_H
251