xref: /llvm-project/clang/lib/Analysis/FlowSensitive/DebugSupport.cpp (revision 3c849d0aefa1101cf38586449c2105a97797c414)
1 //===- DebugSupport.cpp -----------------------------------------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 //  This file defines functions which generate more readable forms of data
10 //  structures used in the dataflow analyses, for debugging purposes.
11 //
12 //===----------------------------------------------------------------------===//
13 
14 #include <utility>
15 
16 #include "clang/Analysis/FlowSensitive/DebugSupport.h"
17 #include "clang/Analysis/FlowSensitive/Solver.h"
18 #include "clang/Analysis/FlowSensitive/Value.h"
19 #include "llvm/ADT/DenseMap.h"
20 #include "llvm/ADT/StringSet.h"
21 #include "llvm/Support/ErrorHandling.h"
22 #include "llvm/Support/FormatAdapters.h"
23 #include "llvm/Support/FormatCommon.h"
24 #include "llvm/Support/FormatVariadic.h"
25 
26 namespace clang {
27 namespace dataflow {
28 
29 using llvm::AlignStyle;
30 using llvm::fmt_pad;
31 using llvm::formatv;
32 
33 namespace {
34 
35 class DebugStringGenerator {
36 public:
37   explicit DebugStringGenerator(
38       llvm::DenseMap<const AtomicBoolValue *, std::string> AtomNamesArg)
39       : Counter(0), AtomNames(std::move(AtomNamesArg)) {
40 #ifndef NDEBUG
41     llvm::StringSet<> Names;
42     for (auto &N : AtomNames) {
43       assert(Names.insert(N.second).second &&
44              "The same name must not assigned to different atoms");
45     }
46 #endif
47   }
48 
49   /// Returns a string representation of a boolean value `B`.
50   std::string debugString(const BoolValue &B, size_t Depth = 0) {
51     std::string S;
52     switch (B.getKind()) {
53     case Value::Kind::AtomicBool: {
54       S = getAtomName(&cast<AtomicBoolValue>(B));
55       break;
56     }
57     case Value::Kind::Conjunction: {
58       auto &C = cast<ConjunctionValue>(B);
59       auto L = debugString(C.getLeftSubValue(), Depth + 1);
60       auto R = debugString(C.getRightSubValue(), Depth + 1);
61       S = formatv("(and\n{0}\n{1})", L, R);
62       break;
63     }
64     case Value::Kind::Disjunction: {
65       auto &D = cast<DisjunctionValue>(B);
66       auto L = debugString(D.getLeftSubValue(), Depth + 1);
67       auto R = debugString(D.getRightSubValue(), Depth + 1);
68       S = formatv("(or\n{0}\n{1})", L, R);
69       break;
70     }
71     case Value::Kind::Negation: {
72       auto &N = cast<NegationValue>(B);
73       S = formatv("(not\n{0})", debugString(N.getSubVal(), Depth + 1));
74       break;
75     }
76     default:
77       llvm_unreachable("Unhandled value kind");
78     }
79     auto Indent = Depth * 4;
80     return formatv("{0}", fmt_pad(S, Indent, 0));
81   }
82 
83   /// Returns a string representation of a set of boolean `Constraints` and the
84   /// `Result` of satisfiability checking on the `Constraints`.
85   std::string debugString(const std::vector<BoolValue *> &Constraints,
86                           const Solver::Result &Result) {
87     auto Template = R"(
88 Constraints
89 ------------
90 {0:$[
91 
92 ]}
93 ------------
94 {1}.
95 {2}
96 )";
97 
98     std::vector<std::string> ConstraintsStrings;
99     ConstraintsStrings.reserve(Constraints.size());
100     for (auto &Constraint : Constraints) {
101       ConstraintsStrings.push_back(debugString(*Constraint));
102     }
103 
104     auto StatusString = debugString(Result.getStatus());
105     auto Solution = Result.getSolution();
106     auto SolutionString = Solution ? "\n" + debugString(Solution.value()) : "";
107 
108     return formatv(
109         Template,
110         llvm::make_range(ConstraintsStrings.begin(), ConstraintsStrings.end()),
111         StatusString, SolutionString);
112   }
113 
114 private:
115   /// Returns a string representation of a truth assignment to atom booleans.
116   std::string debugString(
117       const llvm::DenseMap<AtomicBoolValue *, Solver::Result::Assignment>
118           &AtomAssignments) {
119     size_t MaxNameLength = 0;
120     for (auto &AtomName : AtomNames) {
121       MaxNameLength = std::max(MaxNameLength, AtomName.second.size());
122     }
123 
124     std::vector<std::string> Lines;
125     for (auto &AtomAssignment : AtomAssignments) {
126       auto Line = formatv("{0} = {1}",
127                           fmt_align(getAtomName(AtomAssignment.first),
128                                     AlignStyle::Left, MaxNameLength),
129                           debugString(AtomAssignment.second));
130       Lines.push_back(Line);
131     }
132     llvm::sort(Lines.begin(), Lines.end());
133 
134     return formatv("{0:$[\n]}", llvm::make_range(Lines.begin(), Lines.end()));
135   }
136 
137   /// Returns a string representation of a boolean assignment to true or false.
138   std::string debugString(Solver::Result::Assignment Assignment) {
139     switch (Assignment) {
140     case Solver::Result::Assignment::AssignedFalse:
141       return "False";
142     case Solver::Result::Assignment::AssignedTrue:
143       return "True";
144     }
145     llvm_unreachable("Booleans can only be assigned true/false");
146   }
147 
148   /// Returns a string representation of the result status of a SAT check.
149   std::string debugString(Solver::Result::Status Status) {
150     switch (Status) {
151     case Solver::Result::Status::Satisfiable:
152       return "Satisfiable";
153     case Solver::Result::Status::Unsatisfiable:
154       return "Unsatisfiable";
155     case Solver::Result::Status::TimedOut:
156       return "TimedOut";
157     }
158     llvm_unreachable("Unhandled SAT check result status");
159   }
160 
161   /// Returns the name assigned to `Atom`, either user-specified or created by
162   /// default rules (B0, B1, ...).
163   std::string getAtomName(const AtomicBoolValue *Atom) {
164     auto Entry = AtomNames.try_emplace(Atom, formatv("B{0}", Counter));
165     if (Entry.second) {
166       Counter++;
167     }
168     return Entry.first->second;
169   }
170 
171   // Keep track of number of atoms without a user-specified name, used to assign
172   // non-repeating default names to such atoms.
173   size_t Counter;
174 
175   // Keep track of names assigned to atoms.
176   llvm::DenseMap<const AtomicBoolValue *, std::string> AtomNames;
177 };
178 
179 } // namespace
180 
181 std::string
182 debugString(const BoolValue &B,
183             llvm::DenseMap<const AtomicBoolValue *, std::string> AtomNames) {
184   return DebugStringGenerator(std::move(AtomNames)).debugString(B);
185 }
186 
187 std::string
188 debugString(const std::vector<BoolValue *> &Constraints,
189             const Solver::Result &Result,
190             llvm::DenseMap<const AtomicBoolValue *, std::string> AtomNames) {
191   return DebugStringGenerator(std::move(AtomNames))
192       .debugString(Constraints, Result);
193 }
194 
195 } // namespace dataflow
196 } // namespace clang
197