xref: /aosp_15_r20/external/stg/reporting.cc (revision 9e3b08ae94a55201065475453d799e8b1378bea6)
1 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2 // -*- mode: C++ -*-
3 //
4 // Copyright 2020-2022 Google LLC
5 //
6 // Licensed under the Apache License v2.0 with LLVM Exceptions (the
7 // "License"); you may not use this file except in compliance with the
8 // License.  You may obtain a copy of the License at
9 //
10 //     https://llvm.org/LICENSE.txt
11 //
12 // Unless required by applicable law or agreed to in writing, software
13 // distributed under the License is distributed on an "AS IS" BASIS,
14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 // See the License for the specific language governing permissions and
16 // limitations under the License.
17 //
18 // Author: Giuliano Procida
19 // Author: Siddharth Nayyar
20 
21 #include "reporting.h"
22 
23 #include <array>
24 #include <cstddef>
25 #include <deque>
26 #include <optional>
27 #include <ostream>
28 #include <sstream>
29 #include <string>
30 #include <string_view>
31 #include <type_traits>
32 #include <unordered_map>
33 #include <unordered_set>
34 #include <utility>
35 #include <vector>
36 
37 #include "comparison.h"
38 #include "error.h"
39 #include "fidelity.h"
40 #include "graph.h"
41 #include "naming.h"
42 #include "post_processing.h"
43 
44 namespace stg {
45 namespace reporting {
46 
47 namespace {
48 
49 struct FormatDescriptor {
50   std::string_view name;
51   OutputFormat value;
52 };
53 
54 constexpr std::array<FormatDescriptor, 5> kFormats{{
55   {"plain", OutputFormat::PLAIN},
56   {"flat",  OutputFormat::FLAT },
57   {"small", OutputFormat::SMALL},
58   {"short", OutputFormat::SHORT},
59   {"viz",   OutputFormat::VIZ  },
60 }};
61 
62 }  // namespace
63 
ParseOutputFormat(std::string_view format)64 std::optional<OutputFormat> ParseOutputFormat(std::string_view format) {
65   for (const auto& [name, value] : kFormats) {
66     if (name == format) {
67       return {value};
68     }
69   }
70   return {};
71 }
72 
operator <<(std::ostream & os,OutputFormatUsage)73 std::ostream& operator<<(std::ostream& os, OutputFormatUsage) {
74   os << "output formats:";
75   for (const auto& [name, _] : kFormats) {
76     os << ' ' << name;
77   }
78   return os << '\n';
79 }
80 
81 namespace {
82 
GetResolvedDescription(const Graph & graph,NameCache & names,Id id)83 std::string GetResolvedDescription(
84     const Graph& graph, NameCache& names, Id id) {
85   std::ostringstream os;
86   const auto [resolved, typedefs] = diff::ResolveTypedefs(graph, id);
87   for (const auto& td : typedefs) {
88     os << '\'' << td << "' = ";
89   }
90   os << '\'' << Describe(graph, names)(resolved) << '\''
91      << DescribeExtra(graph)(resolved);
92   return os.str();
93 }
94 
95 // Prints a comparison to the given output stream. The comparison is printed
96 // with the given indentation and prefixed with the given prefix if it is not
97 // empty.
98 //
99 // It returns true if the comparison denotes addition or removal of a node.
PrintComparison(const Reporting & reporting,const diff::Comparison & comparison,std::ostream & os,size_t indent,const std::string & prefix)100 bool PrintComparison(const Reporting& reporting,
101                      const diff::Comparison& comparison, std::ostream& os,
102                      size_t indent, const std::string& prefix) {
103   os << std::string(indent, ' ');
104   if (!prefix.empty()) {
105     os << prefix << ' ';
106   }
107   const auto id1 = comparison.first;
108   const auto id2 = comparison.second;
109 
110   Check(id1.has_value() || id2.has_value())
111       << "internal error: Attempt to print comparison with nothing to compare.";
112 
113   if (!id2) {
114     os << DescribeKind(reporting.graph)(*id1) << " '"
115        << Describe(reporting.graph, reporting.names)(*id1)
116        << "'"
117        << DescribeExtra(reporting.graph)(*id1)
118        << " was removed\n";
119     return true;
120   }
121   if (!id1) {
122     os << DescribeKind(reporting.graph)(*id2) << " '"
123        << Describe(reporting.graph, reporting.names)(*id2)
124        << "'"
125        << DescribeExtra(reporting.graph)(*id2)
126        << " was added\n";
127     return true;
128   }
129 
130   const auto description1 =
131       GetResolvedDescription(reporting.graph, reporting.names, *id1);
132   const auto description2 =
133       GetResolvedDescription(reporting.graph, reporting.names, *id2);
134   os << DescribeKind(reporting.graph)(*id1) << ' ';
135   if (description1 == description2) {
136     os << description1 << " changed\n";
137   } else {
138     os << "changed from " << description1 << " to " << description2 << '\n';
139   }
140   return false;
141 }
142 
143 constexpr size_t INDENT_INCREMENT = 2;
144 
145 class Plain {
146   // unvisited (absent) -> started (false) -> finished (true)
147   using Seen = std::unordered_map<diff::Comparison, bool, diff::HashComparison>;
148 
149  public:
Plain(const Reporting & reporting,std::ostream & output)150   Plain(const Reporting& reporting, std::ostream& output)
151       : reporting_(reporting), output_(output) {}
152 
153   void Report(const diff::Comparison&);
154 
155  private:
156   const Reporting& reporting_;
157   std::ostream& output_;
158   Seen seen_;
159 
160   void Print(const diff::Comparison&, size_t, const std::string&);
161 };
162 
Print(const diff::Comparison & comparison,size_t indent,const std::string & prefix)163 void Plain::Print(const diff::Comparison& comparison, size_t indent,
164                   const std::string& prefix) {
165   if (PrintComparison(reporting_, comparison, output_, indent, prefix)) {
166     return;
167   }
168 
169   indent += INDENT_INCREMENT;
170   const auto it = reporting_.outcomes.find(comparison);
171   Check(it != reporting_.outcomes.end())
172       << "internal error: missing comparison";
173   const auto& diff = it->second;
174 
175   const bool holds_changes = diff.holds_changes;
176   std::pair<Seen::iterator, bool> insertion;
177 
178   if (holds_changes) {
179     insertion = seen_.insert({comparison, false});
180   }
181 
182   if (holds_changes && !insertion.second) {
183     if (!insertion.first->second) {
184       output_ << std::string(indent, ' ') << "(being reported)\n";
185     } else if (!diff.details.empty()) {
186       output_ << std::string(indent, ' ') << "(already reported)\n";
187     }
188     return;
189   }
190 
191   for (const auto& detail : diff.details) {
192     if (detail.edge == diff::Comparison{}) {
193       output_ << std::string(indent, ' ') << detail.text << '\n';
194     } else {
195       Print(detail.edge, indent, detail.text);
196     }
197   }
198 
199   if (holds_changes) {
200     insertion.first->second = true;
201   }
202 }
203 
Report(const diff::Comparison & comparison)204 void Plain::Report(const diff::Comparison& comparison) {
205   // unpack then print - want symbol diff forest rather than symbols diff tree
206   const auto& diff = reporting_.outcomes.at(comparison);
207   for (const auto& detail : diff.details) {
208     Print(detail.edge, 0, {});
209     // paragraph spacing
210     output_ << '\n';
211   }
212 }
213 
214 // Print the subtree of a diff graph starting at a given node and stopping at
215 // nodes that can themselves hold diffs, queuing such nodes for subsequent
216 // printing. Optionally, avoid printing "uninteresting" nodes - those that have
217 // no diff and no path to a diff that does not pass through a node that can hold
218 // diffs. Return whether the diff node's tree was intrinisically interesting.
219 class Flat {
220  public:
Flat(const Reporting & reporting,bool full,std::ostream & output)221   Flat(const Reporting& reporting, bool full, std::ostream& output)
222       : reporting_(reporting), full_(full), output_(output) {}
223 
224   void Report(const diff::Comparison&);
225 
226  private:
227   const Reporting& reporting_;
228   const bool full_;
229   std::ostream& output_;
230   std::unordered_set<diff::Comparison, diff::HashComparison> seen_;
231   std::deque<diff::Comparison> todo_;
232 
233   bool Print(const diff::Comparison&, bool, std::ostream&, size_t,
234              const std::string&);
235 };
236 
Print(const diff::Comparison & comparison,bool stop,std::ostream & os,size_t indent,const std::string & prefix)237 bool Flat::Print(const diff::Comparison& comparison, bool stop,
238                  std::ostream& os, size_t indent, const std::string& prefix) {
239   // Nodes that represent additions or removal are always interesting and no
240   // recursion is possible.
241   if (PrintComparison(reporting_, comparison, os, indent, prefix)) {
242     return true;
243   }
244 
245   // Look up the diff (including node and edge changes).
246   const auto it = reporting_.outcomes.find(comparison);
247   Check(it != reporting_.outcomes.end())
248       << "internal error: missing comparison";
249   const auto& diff = it->second;
250 
251   // Check the stopping condition.
252   if (diff.holds_changes && stop) {
253     // If it's a new diff-holding node, queue it.
254     if (seen_.insert(comparison).second) {
255       todo_.push_back(comparison);
256     }
257     return false;
258   }
259   // The stop flag can only be false on a non-recursive call which should be for
260   // a diff-holding node.
261   if (!diff.holds_changes && !stop) {
262     Die() << "internal error: FlatPrint called on inappropriate node";
263   }
264 
265   // Indent before describing diff details.
266   indent += INDENT_INCREMENT;
267   bool interesting = diff.has_changes;
268   for (const auto& detail : diff.details) {
269     if (detail.edge == diff::Comparison{}) {
270       os << std::string(indent, ' ') << detail.text << '\n';
271       // Node changes may not be interesting, if we allow non-change diff
272       // details at some point. Just trust the has_changes flag.
273     } else {
274       // Edge changes are interesting if the target diff node is.
275       std::ostringstream sub_os;
276       // Set the stop flag to prevent recursion past diff-holding nodes.
277       const bool sub_interesting =
278           Print(detail.edge, true, sub_os, indent, detail.text);
279       // If the sub-tree was interesting, add it.
280       if (sub_interesting || full_) {
281         os << sub_os.str();
282       }
283       interesting |= sub_interesting;
284     }
285   }
286   return interesting;
287 }
288 
Report(const diff::Comparison & comparison)289 void Flat::Report(const diff::Comparison& comparison) {
290   // We want a symbol diff forest rather than a symbol table diff tree, so
291   // unpack the symbol table and then print the symbols specially.
292   const auto& diff = reporting_.outcomes.at(comparison);
293   for (const auto& detail : diff.details) {
294     std::ostringstream os;
295     const bool interesting = Print(detail.edge, true, os, 0, {});
296     if (interesting || full_) {
297       output_ << os.str() << '\n';
298     }
299   }
300   while (!todo_.empty()) {
301     auto comp = todo_.front();
302     todo_.pop_front();
303     std::ostringstream os;
304     const bool interesting = Print(comp, false, os, 0, {});
305     if (interesting || full_) {
306       output_ << os.str() << '\n';
307     }
308   }
309 }
310 
VizId(std::unordered_map<diff::Comparison,size_t,diff::HashComparison> & ids,const diff::Comparison & comparison)311 size_t VizId(
312     std::unordered_map<diff::Comparison, size_t, diff::HashComparison>& ids,
313     const diff::Comparison& comparison) {
314   return ids.insert({comparison, ids.size()}).first->second;
315 }
316 
VizPrint(const Reporting & reporting,const diff::Comparison & comparison,std::unordered_set<diff::Comparison,diff::HashComparison> & seen,std::unordered_map<diff::Comparison,size_t,diff::HashComparison> & ids,std::ostream & os)317 void VizPrint(
318     const Reporting& reporting, const diff::Comparison& comparison,
319     std::unordered_set<diff::Comparison, diff::HashComparison>& seen,
320     std::unordered_map<diff::Comparison, size_t, diff::HashComparison>& ids,
321     std::ostream& os) {
322   if (!seen.insert(comparison).second) {
323     return;
324   }
325 
326   const auto node = VizId(ids, comparison);
327 
328   const auto id1 = comparison.first;
329   const auto id2 = comparison.second;
330 
331   Check(id1.has_value() || id2.has_value())
332       << "internal error: Attempt to print comparison with nothing to compare.";
333 
334   if (!id2) {
335     os << "  \"" << node << "\" [color=red, label=\"" << "removed("
336        << Describe(reporting.graph, reporting.names)(*id1)
337        << DescribeExtra(reporting.graph)(*id1)
338        << ")\"]\n";
339     return;
340   }
341   if (!id1) {
342     os << "  \"" << node << "\" [color=red, label=\"" << "added("
343        << Describe(reporting.graph, reporting.names)(*id2)
344        << DescribeExtra(reporting.graph)(*id2)
345        << ")\"]\n";
346     return;
347   }
348 
349   const auto it = reporting.outcomes.find(comparison);
350   Check(it != reporting.outcomes.end()) << "internal error: missing comparison";
351   const auto& diff = it->second;
352   const char* colour = diff.has_changes ? "color=red, " : "";
353   const char* shape = diff.holds_changes ? "shape=rectangle, " : "";
354   const auto description1 =
355       GetResolvedDescription(reporting.graph, reporting.names, *id1);
356   const auto description2 =
357       GetResolvedDescription(reporting.graph, reporting.names, *id2);
358   if (description1 == description2) {
359     os << "  \"" << node << "\" [" << colour << shape << "label=\""
360        << description1 << "\"]\n";
361   } else {
362     os << "  \"" << node << "\" [" << colour << shape << "label=\""
363        << description1 << " → " << description2 << "\"]\n";
364   }
365 
366   size_t index = 0;
367   for (const auto& detail : diff.details) {
368     if (detail.edge == diff::Comparison{}) {
369       // attribute change, create an implicit edge and node
370       os << "  \"" << node << "\" -> \"" << node << ':' << index << "\"\n"
371          << "  \"" << node << ':' << index << "\" [color=red, label=\""
372          << detail.text << "\"]\n";
373       ++index;
374     } else {
375       const auto& to = detail.edge;
376       VizPrint(reporting, to, seen, ids, os);
377       os << "  \"" << node << "\" -> \"" << VizId(ids, to) << "\" [label=\""
378          << detail.text << "\"]\n";
379     }
380   }
381 }
382 
ReportViz(const Reporting & reporting,const diff::Comparison & comparison,std::ostream & output)383 void ReportViz(const Reporting& reporting, const diff::Comparison& comparison,
384                std::ostream& output) {
385   output << "digraph \"ABI diff\" {\n";
386   std::unordered_set<diff::Comparison, diff::HashComparison> seen;
387   std::unordered_map<diff::Comparison, size_t, diff::HashComparison> ids;
388   VizPrint(reporting, comparison, seen, ids, output);
389   output << "}\n";
390 }
391 
392 template <typename T>
PrintFidelityReportBucket(T transition,const std::vector<std::string> & symbols_or_types,std::ostream & output)393 void PrintFidelityReportBucket(T transition,
394                                const std::vector<std::string>& symbols_or_types,
395                                std::ostream& output) {
396   output << symbols_or_types.size() << ' ' << transition << ":\n";
397   for (const auto& symbol_or_type : symbols_or_types) {
398     output << "  " << symbol_or_type << '\n';
399   }
400   output << '\n';
401 }
402 
403 }  // namespace
404 
Report(const Reporting & reporting,const diff::Comparison & comparison,std::ostream & output)405 void Report(const Reporting& reporting, const diff::Comparison& comparison,
406             std::ostream& output) {
407   switch (reporting.options.format) {
408     case OutputFormat::PLAIN: {
409       Plain(reporting, output).Report(comparison);
410       break;
411     }
412     case OutputFormat::FLAT:
413     case OutputFormat::SMALL: {
414       const bool full = reporting.options.format == OutputFormat::FLAT;
415       Flat(reporting, full, output).Report(comparison);
416       break;
417     }
418     case OutputFormat::SHORT: {
419       std::stringstream report;
420       Flat(reporting, false, report).Report(comparison);
421       std::vector<std::string> report_lines;
422       std::string line;
423       while (std::getline(report, line)) {
424         report_lines.push_back(line);
425       }
426       report_lines = stg::PostProcess(report_lines);
427       for (const auto& line : report_lines) {
428         output << line << '\n';
429       }
430       break;
431     }
432     case OutputFormat::VIZ: {
433       ReportViz(reporting, comparison, output);
434       break;
435     }
436   }
437 }
438 
FidelityDiff(const stg::FidelityDiff & diff,std::ostream & output)439 bool FidelityDiff(const stg::FidelityDiff& diff, std::ostream& output) {
440   bool diffs_reported = false;
441   auto print_bucket = [&diff, &output, &diffs_reported](auto&& from,
442                                                         auto&& to) {
443     auto transition = std::make_pair(from, to);
444     if constexpr (std::is_same_v<decltype(from), SymbolFidelity&&>) {
445       auto it = diff.symbol_transitions.find(transition);
446       if (it != diff.symbol_transitions.end()) {
447         PrintFidelityReportBucket(transition, it->second, output);
448         diffs_reported = true;
449       }
450     } else if constexpr (std::is_same_v<decltype(from), TypeFidelity&&>) {
451       auto it = diff.type_transitions.find(transition);
452       if (it != diff.type_transitions.end()) {
453         PrintFidelityReportBucket(transition, it->second, output);
454         diffs_reported = true;
455       }
456     }
457   };
458 
459   print_bucket(TypeFidelity::FULLY_DEFINED, TypeFidelity::ABSENT);
460   print_bucket(TypeFidelity::DECLARATION_ONLY, TypeFidelity::ABSENT);
461   print_bucket(TypeFidelity::FULLY_DEFINED, TypeFidelity::DECLARATION_ONLY);
462   print_bucket(SymbolFidelity::TYPED, SymbolFidelity::UNTYPED);
463   print_bucket(TypeFidelity::ABSENT, TypeFidelity::DECLARATION_ONLY);
464   print_bucket(TypeFidelity::DECLARATION_ONLY, TypeFidelity::FULLY_DEFINED);
465   print_bucket(SymbolFidelity::UNTYPED, SymbolFidelity::TYPED);
466   print_bucket(TypeFidelity::ABSENT, TypeFidelity::FULLY_DEFINED);
467   return diffs_reported;
468 }
469 
470 }  // namespace reporting
471 }  // namespace stg
472