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