Skip to content

Commit

Permalink
[dataflow] Parse formulas from text (#66424)
Browse files Browse the repository at this point in the history
My immediate use for this is not in checked-in code, but rather the
ability to plug printed flow conditions (from analysis logs) back into
sat solver unittests to reproduce slowness.

It does allow simplifying some of the existing solver tests, though.
  • Loading branch information
sam-mccall committed Sep 22, 2023
1 parent b2bbf69 commit 3f78d6a
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 90 deletions.
5 changes: 5 additions & 0 deletions clang/include/clang/Analysis/FlowSensitive/Arena.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "clang/Analysis/FlowSensitive/Formula.h"
#include "clang/Analysis/FlowSensitive/StorageLocation.h"
#include "clang/Analysis/FlowSensitive/Value.h"
#include "llvm/ADT/StringRef.h"
#include <vector>

namespace clang::dataflow {
Expand Down Expand Up @@ -109,6 +110,10 @@ class Arena {
return makeAtomRef(Value ? True : False);
}

// Parses a formula from its textual representation.
// This may refer to atoms that were not produced by makeAtom() yet!
llvm::Expected<const Formula &> parseFormula(llvm::StringRef);

/// Returns a new atomic boolean variable, distinct from any other.
Atom makeAtom() { return static_cast<Atom>(NextAtom++); };

Expand Down
1 change: 0 additions & 1 deletion clang/include/clang/Analysis/FlowSensitive/Formula.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
#include "llvm/Support/raw_ostream.h"
#include <cassert>
#include <string>
#include <type_traits>

namespace clang::dataflow {

Expand Down
95 changes: 95 additions & 0 deletions clang/lib/Analysis/FlowSensitive/Arena.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
//===----------------------------------------------------------------------===//

#include "clang/Analysis/FlowSensitive/Arena.h"
#include "clang/Analysis/FlowSensitive/Formula.h"
#include "clang/Analysis/FlowSensitive/Value.h"
#include "llvm/Support/Error.h"
#include <string>

namespace clang::dataflow {

Expand Down Expand Up @@ -95,4 +98,96 @@ BoolValue &Arena::makeBoolValue(const Formula &F) {
return *It->second;
}

namespace {
const Formula *parse(Arena &A, llvm::StringRef &In) {
auto EatSpaces = [&] { In = In.ltrim(' '); };
EatSpaces();

if (In.consume_front("!")) {
if (auto *Arg = parse(A, In))
return &A.makeNot(*Arg);
return nullptr;
}

if (In.consume_front("(")) {
auto *Arg1 = parse(A, In);
if (!Arg1)
return nullptr;

EatSpaces();
decltype(&Arena::makeOr) Op;
if (In.consume_front("|"))
Op = &Arena::makeOr;
else if (In.consume_front("&"))
Op = &Arena::makeAnd;
else if (In.consume_front("=>"))
Op = &Arena::makeImplies;
else if (In.consume_front("="))
Op = &Arena::makeEquals;
else
return nullptr;

auto *Arg2 = parse(A, In);
if (!Arg2)
return nullptr;

EatSpaces();
if (!In.consume_front(")"))
return nullptr;

return &(A.*Op)(*Arg1, *Arg2);
}

// For now, only support unnamed variables V0, V1 etc.
// FIXME: parse e.g. "X" by allocating an atom and storing a name somewhere.
if (In.consume_front("V")) {
std::underlying_type_t<Atom> At;
if (In.consumeInteger(10, At))
return nullptr;
return &A.makeAtomRef(static_cast<Atom>(At));
}

if (In.consume_front("true"))
return &A.makeLiteral(true);
if (In.consume_front("false"))
return &A.makeLiteral(false);

return nullptr;
}

class FormulaParseError : public llvm::ErrorInfo<FormulaParseError> {
std::string Formula;
unsigned Offset;

public:
static char ID;
FormulaParseError(llvm::StringRef Formula, unsigned Offset)
: Formula(Formula), Offset(Offset) {}

void log(raw_ostream &OS) const override {
OS << "bad formula at offset " << Offset << "\n";
OS << Formula << "\n";
OS.indent(Offset) << "^";
}

std::error_code convertToErrorCode() const override {
return std::make_error_code(std::errc::invalid_argument);
}
};

char FormulaParseError::ID = 0;

} // namespace

llvm::Expected<const Formula &> Arena::parseFormula(llvm::StringRef In) {
llvm::StringRef Rest = In;
auto *Result = parse(*this, Rest);
if (!Result) // parse() hit something unparseable
return llvm::make_error<FormulaParseError>(In, In.size() - Rest.size());
Rest = Rest.ltrim();
if (!Rest.empty()) // parse didn't consume all the input
return llvm::make_error<FormulaParseError>(In, In.size() - Rest.size());
return *Result;
}

} // namespace clang::dataflow
1 change: 1 addition & 0 deletions clang/lib/Analysis/FlowSensitive/Formula.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "llvm/Support/Allocator.h"
#include "llvm/Support/ErrorHandling.h"
#include <cassert>
#include <type_traits>

namespace clang::dataflow {

Expand Down
44 changes: 44 additions & 0 deletions clang/unittests/Analysis/FlowSensitive/ArenaTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@

#include "clang/Analysis/FlowSensitive/Arena.h"
#include "llvm/Support/ScopedPrinter.h"
#include "llvm/Testing/Support/Error.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"

namespace clang::dataflow {
namespace {
using llvm::HasValue;
using testing::Ref;

class ArenaTest : public ::testing::Test {
protected:
Expand Down Expand Up @@ -137,5 +140,46 @@ TEST_F(ArenaTest, Interning) {
EXPECT_EQ(&B1.formula(), &F1);
}

TEST_F(ArenaTest, ParseFormula) {
Atom V5{5};
Atom V6{6};
EXPECT_THAT_EXPECTED(A.parseFormula("V5"), HasValue(Ref(A.makeAtomRef(V5))));
EXPECT_THAT_EXPECTED(A.parseFormula("true"),
HasValue(Ref(A.makeLiteral(true))));
EXPECT_THAT_EXPECTED(A.parseFormula("!V5"),
HasValue(Ref(A.makeNot(A.makeAtomRef(V5)))));

EXPECT_THAT_EXPECTED(
A.parseFormula("(V5 = V6)"),
HasValue(Ref(A.makeEquals(A.makeAtomRef(V5), A.makeAtomRef(V6)))));
EXPECT_THAT_EXPECTED(
A.parseFormula("(V5 => V6)"),
HasValue(Ref(A.makeImplies(A.makeAtomRef(V5), A.makeAtomRef(V6)))));
EXPECT_THAT_EXPECTED(
A.parseFormula("(V5 & V6)"),
HasValue(Ref(A.makeAnd(A.makeAtomRef(V5), A.makeAtomRef(V6)))));
EXPECT_THAT_EXPECTED(
A.parseFormula("(V5 | V6)"),
HasValue(Ref(A.makeOr(A.makeAtomRef(V5), A.makeAtomRef(V6)))));

EXPECT_THAT_EXPECTED(
A.parseFormula("((V5 & (V6 & !false)) => ((V5 | V6) | false))"),
HasValue(Ref(
A.makeImplies(A.makeAnd(A.makeAtomRef(V5),
A.makeAnd(A.makeAtomRef(V6),
A.makeNot(A.makeLiteral(false)))),
A.makeOr(A.makeOr(A.makeAtomRef(V5), A.makeAtomRef(V6)),
A.makeLiteral(false))))));

EXPECT_THAT_EXPECTED(
A.parseFormula("(V0 => error)"), llvm::FailedWithMessage(R"(bad formula at offset 7
(V0 => error)
^)"));
EXPECT_THAT_EXPECTED(
A.parseFormula("V1 V2"), llvm::FailedWithMessage(R"(bad formula at offset 3
V1 V2
^)"));
}

} // namespace
} // namespace clang::dataflow

0 comments on commit 3f78d6a

Please sign in to comment.