diff --git a/clang/docs/tools/clang-formatted-files.txt b/clang/docs/tools/clang-formatted-files.txt --- a/clang/docs/tools/clang-formatted-files.txt +++ b/clang/docs/tools/clang-formatted-files.txt @@ -129,6 +129,7 @@ clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h clang/include/clang/Analysis/FlowSensitive/DataflowLattice.h clang/include/clang/Analysis/FlowSensitive/DataflowWorklist.h +clang/include/clang/Analysis/FlowSensitive/DebugSupport.h clang/include/clang/Analysis/FlowSensitive/MapLattice.h clang/include/clang/Analysis/FlowSensitive/MatchSwitch.h clang/include/clang/Analysis/FlowSensitive/NoopLattice.h @@ -309,6 +310,7 @@ clang/lib/Analysis/FlowSensitive/ControlFlowContext.cpp clang/lib/Analysis/FlowSensitive/DataflowAnalysisContext.cpp clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp +clang/lib/Analysis/FlowSensitive/DebugSupport.cpp clang/lib/Analysis/FlowSensitive/Transfer.cpp clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp clang/lib/Analysis/FlowSensitive/WatchedLiteralsSolver.cpp diff --git a/clang/include/clang/Analysis/FlowSensitive/DebugSupport.h b/clang/include/clang/Analysis/FlowSensitive/DebugSupport.h new file mode 100644 --- /dev/null +++ b/clang/include/clang/Analysis/FlowSensitive/DebugSupport.h @@ -0,0 +1,36 @@ +//===-- DebugSupport.h ------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines functions which generate more readable forms of data +// structures used in the dataflow analyses, for debugging purposes. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_DEBUGSUPPORT_H_ +#define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_DEBUGSUPPORT_H_ + +#include + +#include "clang/Analysis/FlowSensitive/Value.h" +#include "llvm/ADT/DenseMap.h" + +namespace clang { +namespace dataflow { +/// Returns a string representation for a boolean value `B`. +/// +/// Atomic booleans appearing in the boolean value `B` are assigned to labels +/// either specified in `AtomNames` or created by default rules as B0, B1, ... +std::string +debugString(BoolValue &B, + llvm::DenseMap AtomNames); +inline std::string debugString(BoolValue &B) { return debugString(B, {{}}); } + +} // namespace dataflow +} // namespace clang + +#endif // LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_DEBUGSUPPORT_H_ diff --git a/clang/lib/Analysis/FlowSensitive/CMakeLists.txt b/clang/lib/Analysis/FlowSensitive/CMakeLists.txt --- a/clang/lib/Analysis/FlowSensitive/CMakeLists.txt +++ b/clang/lib/Analysis/FlowSensitive/CMakeLists.txt @@ -5,6 +5,7 @@ Transfer.cpp TypeErasedDataflowAnalysis.cpp WatchedLiteralsSolver.cpp + DebugSupport.cpp LINK_LIBS clangAnalysis diff --git a/clang/lib/Analysis/FlowSensitive/DebugSupport.cpp b/clang/lib/Analysis/FlowSensitive/DebugSupport.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/Analysis/FlowSensitive/DebugSupport.cpp @@ -0,0 +1,98 @@ +//===- DebugSupport.cpp -----------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines functions which generate more readable forms of data +// structures used in the dataflow analyses, for debugging purposes. +// +//===----------------------------------------------------------------------===// + +#include "clang/Analysis/FlowSensitive/DebugSupport.h" +#include "clang/Analysis/FlowSensitive/Value.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/StringSet.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/FormatAdapters.h" +#include "llvm/Support/FormatVariadic.h" + +namespace clang { +namespace dataflow { + +using llvm::fmt_pad; +using llvm::formatv; + +class DebugStringGenerator { +public: + explicit DebugStringGenerator( + llvm::DenseMap AtomNames) + : Counter(0), AtomNames(AtomNames) { + llvm::StringSet<> Names; + for (auto &N : AtomNames) { + assert(Names.insert(N.second).second && + "The same name must not assigned to different atoms"); + } + } + + /// Returns a string representation of a boolean value `B`. + std::string debugString(BoolValue &B, size_t Depth = 0) { + std::string S; + switch (B.getKind()) { + case Value::Kind::AtomicBool: { + S = formatv("({0})", getAtomName(&cast(B))); + break; + } + case Value::Kind::Conjunction: { + auto &C = cast(B); + S = formatv("(and\n{0}\n{1})", + debugString(C.getLeftSubValue(), Depth + 1), + debugString(C.getRightSubValue(), Depth + 1)); + break; + } + case Value::Kind::Disjunction: { + auto &D = cast(B); + S = formatv("(or\n{0}\n{1})", debugString(D.getLeftSubValue(), Depth + 1), + debugString(D.getRightSubValue(), Depth + 1)); + break; + } + case Value::Kind::Negation: { + auto &N = cast(B); + S = formatv("(not\n{0})", debugString(N.getSubVal(), Depth + 1)); + break; + } + default: + llvm_unreachable("Unhandled value kind"); + } + auto Indent = Depth * 4; + return formatv("{0}", fmt_pad(S, Indent, 0)); + } + +private: + /// Returns the name assigned to `Atom`, either user-specified or created by + /// default rules (B0, B1, ...). + std::string getAtomName(AtomicBoolValue *Atom) { + auto Entry = AtomNames.try_emplace(Atom, formatv("B{0}", Counter)); + if (Entry.second) { + Counter++; + } + return Entry.first->second; + } + + // Keep track of number of atoms without a user-specified name, used to assign + // non-repeating default names to such atoms. + size_t Counter; + + // Keep track of names assigned to atoms. + llvm::DenseMap AtomNames; +}; + +std::string debugString(BoolValue &B, + llvm::DenseMap Names) { + return DebugStringGenerator(Names).debugString(B); +} + +} // namespace dataflow +} // namespace clang diff --git a/clang/unittests/Analysis/FlowSensitive/DebugSupportTest.cpp b/clang/unittests/Analysis/FlowSensitive/DebugSupportTest.cpp new file mode 100644 --- /dev/null +++ b/clang/unittests/Analysis/FlowSensitive/DebugSupportTest.cpp @@ -0,0 +1,188 @@ +//===- unittests/Analysis/FlowSensitive/DebugSupportTest.cpp --------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/Analysis/FlowSensitive/DebugSupport.h" +#include "TestingSupport.h" +#include "clang/Analysis/FlowSensitive/Value.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace { + +using namespace clang; +using namespace dataflow; +using testing::StrEq; + +class BoolValueDebugStringTest : public ::testing::Test { +protected: + test::BoolValueManager Bools; +}; + +TEST_F(BoolValueDebugStringTest, AtomicBoolean) { + // B0 + auto B = Bools.atom(); + + auto Expected = R"((B0))"; + EXPECT_THAT(debugString(*B), StrEq(Expected)); +} + +TEST_F(BoolValueDebugStringTest, Negation) { + // !B0 + auto B = Bools.neg(Bools.atom()); + + auto Expected = R"((not + (B0)))"; + EXPECT_THAT(debugString(*B), StrEq(Expected)); +} + +TEST_F(BoolValueDebugStringTest, Conjunction) { + // B0 ^ B1 + auto B = Bools.conj(Bools.atom(), Bools.atom()); + + auto Expected = R"((and + (B0) + (B1)))"; + EXPECT_THAT(debugString(*B), StrEq(Expected)); +} + +TEST_F(BoolValueDebugStringTest, Disjunction) { + // B0 v B1 + auto B = Bools.disj(Bools.atom(), Bools.atom()); + + auto Expected = R"((or + (B0) + (B1)))"; + EXPECT_THAT(debugString(*B), StrEq(Expected)); +} + +TEST_F(BoolValueDebugStringTest, Implication) { + // B0 => B1, implemented as !B0 v B1 + auto B = Bools.disj(Bools.neg(Bools.atom()), Bools.atom()); + + auto Expected = R"((or + (not + (B0)) + (B1)))"; + EXPECT_THAT(debugString(*B), StrEq(Expected)); +} + +TEST_F(BoolValueDebugStringTest, Iff) { + // B0 <=> B1, implemented as (!B0 v B1) ^ (B0 v !B1) + auto B0 = Bools.atom(); + auto B1 = Bools.atom(); + auto B = + Bools.conj(Bools.disj(Bools.neg(B0), B1), Bools.disj(B0, Bools.neg(B1))); + + auto Expected = R"((and + (or + (not + (B0)) + (B1)) + (or + (B0) + (not + (B1)))))"; + EXPECT_THAT(debugString(*B), StrEq(Expected)); +} + +TEST_F(BoolValueDebugStringTest, Xor) { + // (B0 ^ !B1) V (!B0 ^ B1) + auto B0 = Bools.atom(); + auto B1 = Bools.atom(); + auto B = + Bools.disj(Bools.conj(B0, Bools.neg(B1)), Bools.conj(Bools.neg(B0), B1)); + + auto Expected = R"((or + (and + (B0) + (not + (B1))) + (and + (not + (B0)) + (B1))))"; + EXPECT_THAT(debugString(*B), StrEq(Expected)); +} + +TEST_F(BoolValueDebugStringTest, NestedBoolean) { + // B0 ^ (B1 v (B2 ^ (B3 v B4))) + auto B = Bools.conj( + Bools.atom(), + Bools.disj( + Bools.atom(), + Bools.conj(Bools.atom(), Bools.disj(Bools.atom(), Bools.atom())))); + + auto Expected = R"((and + (B0) + (or + (B1) + (and + (B2) + (or + (B3) + (B4))))))"; + EXPECT_THAT(debugString(*B), StrEq(Expected)); +} + +TEST_F(BoolValueDebugStringTest, AtomicBooleanWithName) { + // True + llvm::StringMap NamedBools; + auto True = cast(Bools.atom()); + auto B = True; + + auto Expected = R"((True))"; + EXPECT_THAT(debugString(*B, {{True, "True"}}), StrEq(Expected)); +} + +TEST_F(BoolValueDebugStringTest, ComplexBooleanWithNames) { + // (Cond ^ Then ^ !Else) v (!Cond ^ !Then ^ Else) + auto Cond = cast(Bools.atom()); + auto Then = cast(Bools.atom()); + auto Else = cast(Bools.atom()); + auto B = Bools.disj( + Bools.conj(Cond, Bools.conj(Then, Bools.neg(Else))), + Bools.conj(Bools.neg(Cond), Bools.conj(Bools.neg(Then), Else))); + + auto Expected = R"((or + (and + (Cond) + (and + (Then) + (not + (Else)))) + (and + (not + (Cond)) + (and + (not + (Then)) + (Else)))))"; + EXPECT_THAT(debugString(*B, {{Cond, "Cond"}, {Then, "Then"}, {Else, "Else"}}), + StrEq(Expected)); +} + +TEST_F(BoolValueDebugStringTest, ComplexBooleanWithSomeNames) { + // (False && B0) v (True v B1) + llvm::StringMap NamedBools; + auto True = cast(Bools.atom()); + auto False = cast(Bools.atom()); + auto B = Bools.disj(Bools.conj(False, Bools.atom()), + Bools.disj(True, Bools.atom())); + + auto Expected = R"((or + (and + (False) + (B0)) + (or + (True) + (B1))))"; + EXPECT_THAT(debugString(*B, {{True, "True"}, {False, "False"}}), + StrEq(Expected)); +} + +} // namespace diff --git a/llvm/utils/gn/secondary/clang/lib/Analysis/FlowSensitive/BUILD.gn b/llvm/utils/gn/secondary/clang/lib/Analysis/FlowSensitive/BUILD.gn --- a/llvm/utils/gn/secondary/clang/lib/Analysis/FlowSensitive/BUILD.gn +++ b/llvm/utils/gn/secondary/clang/lib/Analysis/FlowSensitive/BUILD.gn @@ -12,5 +12,6 @@ "Transfer.cpp", "TypeErasedDataflowAnalysis.cpp", "WatchedLiteralsSolver.cpp", + "DebugSupport.cpp", ] }