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