1//===-- Arena.cpp ---------------------------------------------------------===//
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#include "clang/Analysis/FlowSensitive/Arena.h"
10#include "clang/Analysis/FlowSensitive/Formula.h"
11#include "clang/Analysis/FlowSensitive/Value.h"
12#include "llvm/Support/Error.h"
13#include <string>
14
15namespace clang::dataflow {
16
17static std::pair<const Formula *, const Formula *>
18canonicalFormulaPair(const Formula &LHS, const Formula &RHS) {
19 auto Res = std::make_pair(x: &LHS, y: &RHS);
20 if (&RHS < &LHS) // FIXME: use a deterministic order instead
21 std::swap(a&: Res.first, b&: Res.second);
22 return Res;
23}
24
25template <class Key, class ComputeFunc>
26const Formula &cached(llvm::DenseMap<Key, const Formula *> &Cache, Key K,
27 ComputeFunc &&Compute) {
28 auto [It, Inserted] = Cache.try_emplace(std::forward<Key>(K));
29 if (Inserted)
30 It->second = Compute();
31 return *It->second;
32}
33
34const Formula &Arena::makeAtomRef(Atom A) {
35 return cached(Cache&: AtomRefs, K: A, Compute: [&] {
36 return &Formula::create(Alloc, K: Formula::AtomRef, Operands: {},
37 Value: static_cast<unsigned>(A));
38 });
39}
40
41const Formula &Arena::makeAnd(const Formula &LHS, const Formula &RHS) {
42 return cached(Cache&: Ands, K: canonicalFormulaPair(LHS, RHS), Compute: [&] {
43 if (&LHS == &RHS)
44 return &LHS;
45 if (LHS.kind() == Formula::Literal)
46 return LHS.literal() ? &RHS : &LHS;
47 if (RHS.kind() == Formula::Literal)
48 return RHS.literal() ? &LHS : &RHS;
49
50 return &Formula::create(Alloc, K: Formula::And, Operands: {&LHS, &RHS});
51 });
52}
53
54const Formula &Arena::makeOr(const Formula &LHS, const Formula &RHS) {
55 return cached(Cache&: Ors, K: canonicalFormulaPair(LHS, RHS), Compute: [&] {
56 if (&LHS == &RHS)
57 return &LHS;
58 if (LHS.kind() == Formula::Literal)
59 return LHS.literal() ? &LHS : &RHS;
60 if (RHS.kind() == Formula::Literal)
61 return RHS.literal() ? &RHS : &LHS;
62
63 return &Formula::create(Alloc, K: Formula::Or, Operands: {&LHS, &RHS});
64 });
65}
66
67const Formula &Arena::makeNot(const Formula &Val) {
68 return cached(Cache&: Nots, K: &Val, Compute: [&] {
69 if (Val.kind() == Formula::Not)
70 return Val.operands()[0];
71 if (Val.kind() == Formula::Literal)
72 return &makeLiteral(Value: !Val.literal());
73
74 return &Formula::create(Alloc, K: Formula::Not, Operands: {&Val});
75 });
76}
77
78const Formula &Arena::makeImplies(const Formula &LHS, const Formula &RHS) {
79 return cached(Cache&: Implies, K: std::make_pair(x: &LHS, y: &RHS), Compute: [&] {
80 if (&LHS == &RHS)
81 return &makeLiteral(Value: true);
82 if (LHS.kind() == Formula::Literal)
83 return LHS.literal() ? &RHS : &makeLiteral(Value: true);
84 if (RHS.kind() == Formula::Literal)
85 return RHS.literal() ? &RHS : &makeNot(Val: LHS);
86
87 return &Formula::create(Alloc, K: Formula::Implies, Operands: {&LHS, &RHS});
88 });
89}
90
91const Formula &Arena::makeEquals(const Formula &LHS, const Formula &RHS) {
92 return cached(Cache&: Equals, K: canonicalFormulaPair(LHS, RHS), Compute: [&] {
93 if (&LHS == &RHS)
94 return &makeLiteral(Value: true);
95 if (LHS.kind() == Formula::Literal)
96 return LHS.literal() ? &RHS : &makeNot(Val: RHS);
97 if (RHS.kind() == Formula::Literal)
98 return RHS.literal() ? &LHS : &makeNot(Val: LHS);
99
100 return &Formula::create(Alloc, K: Formula::Equal, Operands: {&LHS, &RHS});
101 });
102}
103
104IntegerValue &Arena::makeIntLiteral(llvm::APInt Value) {
105 auto [It, Inserted] = IntegerLiterals.try_emplace(Key: Value, Args: nullptr);
106
107 if (Inserted)
108 It->second = &create<IntegerValue>();
109 return *It->second;
110}
111
112BoolValue &Arena::makeBoolValue(const Formula &F) {
113 auto [It, Inserted] = FormulaValues.try_emplace(Key: &F);
114 if (Inserted)
115 It->second = (F.kind() == Formula::AtomRef)
116 ? (BoolValue *)&create<AtomicBoolValue>(args: F)
117 : &create<FormulaBoolValue>(args: F);
118 return *It->second;
119}
120
121namespace {
122const Formula *parse(Arena &A, llvm::StringRef &In) {
123 auto EatSpaces = [&] { In = In.ltrim(Char: ' '); };
124 EatSpaces();
125
126 if (In.consume_front(Prefix: "!")) {
127 if (auto *Arg = parse(A, In))
128 return &A.makeNot(Val: *Arg);
129 return nullptr;
130 }
131
132 if (In.consume_front(Prefix: "(")) {
133 auto *Arg1 = parse(A, In);
134 if (!Arg1)
135 return nullptr;
136
137 EatSpaces();
138 decltype(&Arena::makeOr) Op;
139 if (In.consume_front(Prefix: "|"))
140 Op = &Arena::makeOr;
141 else if (In.consume_front(Prefix: "&"))
142 Op = &Arena::makeAnd;
143 else if (In.consume_front(Prefix: "=>"))
144 Op = &Arena::makeImplies;
145 else if (In.consume_front(Prefix: "="))
146 Op = &Arena::makeEquals;
147 else
148 return nullptr;
149
150 auto *Arg2 = parse(A, In);
151 if (!Arg2)
152 return nullptr;
153
154 EatSpaces();
155 if (!In.consume_front(Prefix: ")"))
156 return nullptr;
157
158 return &(A.*Op)(*Arg1, *Arg2);
159 }
160
161 // For now, only support unnamed variables V0, V1 etc.
162 // FIXME: parse e.g. "X" by allocating an atom and storing a name somewhere.
163 if (In.consume_front(Prefix: "V")) {
164 std::underlying_type_t<Atom> At;
165 if (In.consumeInteger(Radix: 10, Result&: At))
166 return nullptr;
167 return &A.makeAtomRef(A: static_cast<Atom>(At));
168 }
169
170 if (In.consume_front(Prefix: "true"))
171 return &A.makeLiteral(Value: true);
172 if (In.consume_front(Prefix: "false"))
173 return &A.makeLiteral(Value: false);
174
175 return nullptr;
176}
177
178class FormulaParseError : public llvm::ErrorInfo<FormulaParseError> {
179 std::string Formula;
180 unsigned Offset;
181
182public:
183 static char ID;
184 FormulaParseError(llvm::StringRef Formula, unsigned Offset)
185 : Formula(Formula), Offset(Offset) {}
186
187 void log(raw_ostream &OS) const override {
188 OS << "bad formula at offset " << Offset << "\n";
189 OS << Formula << "\n";
190 OS.indent(NumSpaces: Offset) << "^";
191 }
192
193 std::error_code convertToErrorCode() const override {
194 return std::make_error_code(e: std::errc::invalid_argument);
195 }
196};
197
198char FormulaParseError::ID = 0;
199
200} // namespace
201
202llvm::Expected<const Formula &> Arena::parseFormula(llvm::StringRef In) {
203 llvm::StringRef Rest = In;
204 auto *Result = parse(A&: *this, In&: Rest);
205 if (!Result) // parse() hit something unparseable
206 return llvm::make_error<FormulaParseError>(Args&: In, Args: In.size() - Rest.size());
207 Rest = Rest.ltrim();
208 if (!Rest.empty()) // parse didn't consume all the input
209 return llvm::make_error<FormulaParseError>(Args&: In, Args: In.size() - Rest.size());
210 return *Result;
211}
212
213} // namespace clang::dataflow
214