• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2023-2025 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 #include "ASTVerifier.h"
17 
18 namespace ark::es2panda::compiler::ast_verifier {
19 
20 using AstToCheck = ArenaMap<ASTVerifier::SourcePath, const ir::AstNode *>;
21 
22 template <typename Inv>
VerifyNode(Inv * inv,const ir::AstNode * node)23 auto VerifyNode(Inv *inv, const ir::AstNode *node)
24 {
25     auto [res, action] = (*inv)(node);
26     if (action == CheckAction::SKIP_SUBTREE) {
27         LOG_ASTV(DEBUG, Inv::NAME << ": SKIP_SUBTREE");
28     }
29     return CheckResult {res, action};
30 }
31 
32 struct ASTVerifier::SinglePassVerifier {
33     // NOLINTBEGIN(misc-non-private-member-variables-in-classes)
34     ASTVerifier *verifier {nullptr};
35     bool *astCorrect;
36     // NOLINTEND(misc-non-private-member-variables-in-classes)
37 
operator ()ark::es2panda::compiler::ast_verifier::ASTVerifier::SinglePassVerifier38     void operator()(ir::AstNode *ncnode) const
39     {
40         const auto *node = ncnode;
41         auto enabledSave = verifier->enabled_;
42         LOG_ASTV(DEBUG, "Verify: " << ir::ToString(node->Type()));
43 
44         verifier->Apply([this, node](auto &...inv) {
45             InvArray<CheckDecision> decisions {};
46             InvArray<CheckAction> actions {};
47             ((std::tie(decisions[inv.ID], actions[inv.ID]) =
48                   verifier->NeedCheckInvariant(inv) ? VerifyNode(&inv, node)
49                                                     : CheckResult {CheckDecision::CORRECT, CheckAction::SKIP_SUBTREE}),
50              ...);
51             // Temporaly disable invariant, the value should be restored after node and its childs are visited:
52             ((verifier->enabled_[inv.ID] &= (actions[inv.ID] == CheckAction::CONTINUE)), ...);
53 
54             for (size_t i = 0; i < VerifierInvariants::COUNT; i++) {
55                 LOG_ASTV(DEBUG, (actions[i] == CheckAction::CONTINUE ? "Enabled " : "Disabled ")
56                                     << util::gen::ast_verifier::ToString(VerifierInvariants {i}));
57             }
58 
59             (*astCorrect) &= ((decisions[inv.ID] == CheckDecision::CORRECT) && ...);
60         });
61 
62         node->Iterate(*this);
63         verifier->enabled_ = enabledSave;
64     }
65 };
66 
ExtractAst(const parser::Program & program,bool checkFullProgram)67 static auto ExtractAst(const parser::Program &program, bool checkFullProgram)
68 {
69     auto *allocator = program.Allocator();
70     auto astToCheck = AstToCheck {allocator->Adapter()};
71     astToCheck.insert(std::make_pair(program.SourceFilePath().Utf8(), program.Ast()));
72     if (checkFullProgram) {
73         for (const auto &externalSource : program.ExternalSources()) {
74             for (auto *external : externalSource.second) {
75                 ES2PANDA_ASSERT(external->Ast() != nullptr);
76                 astToCheck.insert(std::make_pair(external->SourceFilePath().Utf8(), external->Ast()));
77             }
78         }
79     }
80     return astToCheck;
81 }
82 
Verify(std::string_view phaseName)83 void ASTVerifier::Verify(std::string_view phaseName) noexcept
84 {
85     if (context_.diagnosticEngine->IsAnyError()) {
86         // NOTE(dkofanov): As for now, the policy is that ASTVerifier doesn't interrupt pipeline if there were errors
87         // reported. Should be revisited.
88         Suppress();
89     }
90     if (suppressed_) {
91         return;
92     }
93     auto astToCheck = ExtractAst(program_, Options().IsAstVerifierFullProgram());
94     for (const auto &p : astToCheck) {
95         const auto sourceName = p.first;
96         const auto *ast = p.second;
97         Apply([](auto &&...inv) { ((inv.Init()), ...); });
98 
99         LOG_ASTV(INFO, "Begin traversal (" << sourceName << ')');
100 
101         bool astCorrect = true;
102         // `const_cast` due to `ir::NodeTraverser` signature:
103         SinglePassVerifier {this, &astCorrect}(const_cast<ir::AstNode *>(ast));
104 
105         auto reporter = [this, sourceName](auto &&inv) {
106             if (inv.HasMessages()) {
107                 report_.back().second[sourceName][TreatAsError(inv.ID) ? "errors" : "warnings"][inv.ID] =
108                     std::forward<InvariantMessages>(inv).MoveMessages();
109                 (TreatAsError(inv.ID) ? hasErrors_ : hasWarnings_) = true;
110             }
111         };
112         ES2PANDA_ASSERT(astCorrect == Apply([](const auto &...inv) { return ((!inv.HasMessages()) && ...); }));
113         if (!astCorrect) {
114             if (report_.empty() || report_.back().first != phaseName) {
115                 report_.emplace_back();
116                 report_.back().first = phaseName;
117             }
118             Apply([&reporter](auto &...inv) { (reporter(std::move(inv)), ...); });
119         }
120     }
121 }
122 
123 template <typename K, typename V>
JsonAddProperty(JsonObjectBuilder & outer,K k,const V & v)124 void JsonAddProperty(JsonObjectBuilder &outer, K k, const V &v)
125 {
126     outer.AddProperty(k, [&v](JsonObjectBuilder &inner) {
127         for (const auto &[kInner, vInner] : v) {
128             JsonAddProperty(inner, kInner, vInner);
129         }
130     });
131 }
132 
133 template <>
JsonAddProperty(JsonObjectBuilder & outer,VerifierInvariants k,const Messages & v)134 void JsonAddProperty<VerifierInvariants, Messages>(JsonObjectBuilder &outer, VerifierInvariants k, const Messages &v)
135 {
136     outer.AddProperty(util::gen::ast_verifier::ToString(k), [&v](JsonArrayBuilder &msgsBuilder) {
137         for (const auto &msg : v) {
138             msgsBuilder.Add(msg.DumpJSON());
139         }
140     });
141 }
142 
DumpJson(const ASTVerifier::GroupedMessages & report,const std::string & filePath)143 void DumpJson(const ASTVerifier::GroupedMessages &report, const std::string &filePath)
144 {
145     JsonObjectBuilder reportJson {};
146     for (const auto &[phase, sourceMessages] : report) {
147         JsonAddProperty(reportJson, phase, sourceMessages);
148     }
149     auto str = std::move(reportJson).Build();
150     std::ofstream(filePath, std::ios::trunc | std::ios::out).write(str.data(), str.size());
151 }
152 
153 template <bool IS_WARNING>
DumpLogMessage(VerifierInvariants id,const Messages & msgs)154 void DumpLogMessage(VerifierInvariants id, const Messages &msgs)
155 {
156     auto invaraintName = util::gen::ast_verifier::ToString(id);
157     if constexpr (IS_WARNING) {
158         LOG(WARNING, ES2PANDA) << "    " << invaraintName << ':';
159     } else {
160         LOG(ERROR, ES2PANDA) << "    " << invaraintName << ':';
161     }
162 
163     for (const auto &msg : msgs) {
164         if constexpr (IS_WARNING) {
165             LOG(WARNING, ES2PANDA) << "      " << msg.ToString();
166         } else {
167             LOG(ERROR, ES2PANDA) << "      " << msg.ToString();
168         }
169     }
170 }
171 
172 template <Logger::Level LEVEL>
DumpLogAstMessages(std::string_view path,const ASTVerifier::WarningsErrors & astMessages)173 void DumpLogAstMessages(std::string_view path, const ASTVerifier::WarningsErrors &astMessages)
174 {
175     static_assert((LEVEL == Logger::Level::WARNING) || (LEVEL == Logger::Level::ERROR));
176     constexpr bool IS_WARNING = LEVEL == Logger::Level::WARNING;
177 
178     auto severityStr = IS_WARNING ? "warnings" : "errors";
179     if (astMessages.find(severityStr) != astMessages.end()) {
180         if constexpr (IS_WARNING) {
181             LOG(WARNING, ES2PANDA) << "  In " << path << ':';
182         } else {
183             LOG(ERROR, ES2PANDA) << "  In " << path << ':';
184         }
185         for (const auto &[invaraintId, messages] : astMessages.at(severityStr)) {
186             DumpLogMessage<IS_WARNING>(invaraintId, messages);
187         }
188     }
189 }
190 
DumpLog(const ASTVerifier::GroupedMessages & report)191 void DumpLog(const ASTVerifier::GroupedMessages &report)
192 {
193     for (const auto &[phase, sourceMessages] : report) {
194         LOG(WARNING, ES2PANDA) << "After " << phase << ':';
195         for (const auto &[path, astMessages] : sourceMessages) {
196             DumpLogAstMessages<Logger::Level::WARNING>(path, astMessages);
197             DumpLogAstMessages<Logger::Level::ERROR>(path, astMessages);
198         }
199     }
200 }
201 
DumpMessages() const202 void ASTVerifier::DumpMessages() const
203 {
204     std::string errMsg = "ASTVerifier found broken invariants.";
205     if (Options().IsAstVerifierJson()) {
206         DumpJson(report_, Options().GetAstVerifierJsonPath());
207         errMsg += " Dumped to '" + std::string(Options().GetAstVerifierJsonPath()) + "'.";
208     } else {
209         DumpLog(report_);
210         errMsg += " You may want to pass '--ast-verifier:json' option for more verbose output.";
211     }
212 
213     if (hasErrors_) {
214         LOG(FATAL, ES2PANDA) << errMsg;
215     } else if (hasWarnings_) {
216         LOG(WARNING, ES2PANDA) << errMsg;
217     }
218 }
219 
AddCheckMessage(const std::string & cause,const ir::AstNode & node)220 void InvariantMessages::AddCheckMessage(const std::string &cause, const ir::AstNode &node)
221 {
222     messages_.emplace_back(cause.data(), &node);
223 }
224 
225 }  // namespace ark::es2panda::compiler::ast_verifier
226