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