[clang] [clang][dataflow] Add a simple boolean model for clang dataflow analyses (PR #231061)
Jan Voung via cfe-commits
cfe-commits at lists.llvm.org
Mon Oct 12 18:51:13 PDT 2026
https://github.com/jvoung updated https://github.com/llvm/llvm-project/pull/231061
>From 081cbed91e769a8e506bc40fb5c1aed5c00ade66 Mon Sep 17 00:00:00 2001
From: Jan Voung <jvoung at gmail.com>
Date: Mon, 12 Oct 2026 03:57:33 +0000
Subject: [PATCH 1/3] [clang][dataflow] Add a simple boolean model for clang
dataflow analyses
For https://github.com/llvm/llvm-project/issues/224638
Based on an earlier attempt https://github.com/llvm/llvm-project/pull/82950
but with cleanups and fixes:
- fix the FIXME regarding unreachable blocks / contradictions
- fill out more forced assignment cases during assumeFormula (e.g., for Equal and Implies)
- track assumed formulas which aren't resolved to a literal initially, and try later (to a fixed point). A later assume() call can make an earlier assumed formula suddenly have literals (this can happen when we branch to the then vs else). For example, helps when the model uses makeImplies e.g., for InequalityCheckRightNullopt.
- expose joinBoolValues to reuse in Crubit's mergeFormulas instead of exposing the FlowConditionToken and always doing a join with a makeOr https://github.com/google/crubit/blob/2e91a0a3d93ae631d2e04333fd7fa34c6d5e7f8a/nullability/pointer_nullability_analysis.cc#L140
- fill out popCall, equivalentTo etc.
Currently only hooks up to some parameterized tests.
The next patch will hook it into some checker options.
===
Example benchmarks with optional checker (checker time only):
(1) llvm/tools/obj2yaml/elf2yaml.cpp
- Watched Literals Solver: 42 s
- Z3: 2.4 s
- Simple: 0.2 s
(2) llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
- WLS: 23 s
- Z3: 0.9 s
- Simple: 0.2 s
(3) llvm/tools/llvm-readobj/ELFDumper.cpp
- WLS: 83 s
- Z3: 4.9 s
- Simple: 0.3 s
---
.../FlowSensitive/DataflowAnalysisContext.h | 23 +-
.../FlowSensitive/DataflowEnvironment.h | 101 ++-
.../FlowSensitive/DataflowEnvironment.cpp | 548 ++++++++++++-
.../Analysis/FlowSensitive/TransferTest.cpp | 720 +++++++++++-------
.../UncheckedOptionalAccessModelTest.cpp | 48 +-
5 files changed, 1097 insertions(+), 343 deletions(-)
diff --git a/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h b/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h
index 36d735db46b19..2320d071b3e2f 100644
--- a/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h
+++ b/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h
@@ -55,6 +55,20 @@ struct SimpleLogicalContext {
llvm::DenseMap<Atom, llvm::DenseSet<Atom>> TokenDeps;
};
+/// Selects the boolean model used to represent and reason about path
+/// conditions and boolean values in `Environment`.
+enum class BoolModelKind {
+ /// Full boolean formula tracking backed by a SAT solver.
+ SAT,
+ /// Lightweight model that tracks known truth assignments for atoms (e.g.,
+ /// for each `assume()`) and unresolved assumed formulas, propagating forced
+ /// assignments and evaluating queries directly without invoking a SAT
+ /// solver.
+ /// Join operations are more conservative than in the SAT model (intersecting
+ /// known facts instead of encoding a disjunctive case split).
+ Simple,
+};
+
/// Owns objects that encompass the state of a program and stores context that
/// is used during dataflow analysis.
class DataflowAnalysisContext {
@@ -70,6 +84,9 @@ class DataflowAnalysisContext {
/// (This is always non-null within an AnalysisContext, the framework
/// provides a fallback no-op logger).
Logger *Log = nullptr;
+
+ /// Which boolean model to use in `Environment`.
+ BoolModelKind BoolModel = BoolModelKind::SAT;
};
/// Constructs a dataflow analysis context.
@@ -80,7 +97,8 @@ class DataflowAnalysisContext {
DataflowAnalysisContext(std::unique_ptr<Solver> S,
Options Opts = Options{
/*ContextSensitiveOpts=*/std::nullopt,
- /*Logger=*/nullptr})
+ /*Logger=*/nullptr,
+ /*BoolModel=*/BoolModelKind::SAT})
: DataflowAnalysisContext(*S, std::move(S), Opts) {}
/// Constructs a dataflow analysis context.
@@ -90,7 +108,8 @@ class DataflowAnalysisContext {
/// `S` must outlive the `DataflowAnalysisContext`.
DataflowAnalysisContext(Solver &S, Options Opts = Options{
/*ContextSensitiveOpts=*/std::nullopt,
- /*Logger=*/nullptr})
+ /*Logger=*/nullptr,
+ /*BoolModel=*/BoolModelKind::SAT})
: DataflowAnalysisContext(S, nullptr, Opts) {}
~DataflowAnalysisContext();
diff --git a/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h b/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
index 47ac06137bb5f..d55e50002ff3f 100644
--- a/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
+++ b/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
@@ -34,6 +34,7 @@
#include "llvm/Support/ErrorHandling.h"
#include <cassert>
#include <memory>
+#include <optional>
#include <type_traits>
#include <utility>
#include <vector>
@@ -157,19 +158,82 @@ class Environment {
}
};
+ /// Abstract interface for modeling boolean formulas and path conditions in
+ /// an `Environment`.
+ class BoolModel {
+ public:
+ explicit BoolModel(BoolModelKind K) : K(K) {}
+ virtual ~BoolModel() = default;
+
+ BoolModelKind getKind() const { return K; }
+
+ /// Returns an exact copy of this model state.
+ virtual std::unique_ptr<BoolModel> clone() const = 0;
+
+ /// If this model state was cloned, updates this model state to be a fork
+ /// so that mutations to either model state will not affect the other.
+ virtual void onFork() = 0;
+
+ /// Records a fact that must be true if this point in the program is
+ /// reached.
+ virtual void assume(const Formula &F) = 0;
+
+ /// Returns true if `F` is always true when this point is reached.
+ /// Returns false if the formula may be false (or the flow condition isn't
+ /// sufficiently precise to prove that it is true).
+ /// See `Environment::proves` for more details.
+ virtual bool proves(const Formula &F) const = 0;
+
+ /// Returns true if `F` may be true when this point is reached.
+ /// Returns false if the formula is always false when this point is reached
+ /// (or the flow condition is overly constraining).
+ /// See `Environment::allows` for more details.
+ virtual bool allows(const Formula &F) const = 0;
+
+ /// Joins two distinct boolean values `Val1` (from `Model1`) and `Val2`
+ /// (from `Model2`) into `JoinedEnv`.
+ /// Requires `Model1` and `Model2` to have the same kind as `this`.
+ virtual BoolValue &joinBoolValues(BoolValue &Val1, const BoolModel &Model1,
+ BoolValue &Val2, const BoolModel &Model2,
+ Environment &JoinedEnv) = 0;
+
+ /// Populates `this` with the join of `ModelA` and `ModelB`.
+ ///
+ /// Requirements: `ModelA` and `ModelB` must have the same kind as `this`.
+ virtual void join(const BoolModel &ModelA, const BoolModel &ModelB) = 0;
+
+ /// Widens `this` with respect to `PrevModel` and returns whether the state
+ /// changed relative to `PrevModel`.
+ ///
+ /// Requirements: `PrevModel` must have the same kind as `this`.
+ virtual LatticeEffect widen(const BoolModel &PrevModel) = 0;
+
+ /// Returns whether this boolean model state is equivalent to `Other` for
+ /// convergence purposes.
+ /// Requirements: `Other` must have the same kind as `this`.
+ virtual bool isEquivalentTo(const BoolModel &Other) const = 0;
+
+ /// Returns the flow condition token (only meaningful for SAT-based models).
+ virtual Atom getFlowConditionToken() const = 0;
+
+ virtual void dump(raw_ostream &OS) const = 0;
+
+ private:
+ BoolModelKind K;
+ };
+
/// Creates an environment that uses `DACtx` to store objects that encompass
/// the state of a program. `FlowConditionToken` sets the flow condition
- /// associated with the environment. Generally, new environments should be
- /// initialized with a fresh token, by using one of the other
- /// constructors. This constructor is for specialized use, including
- /// deserialization and delegation from other constructors.
- Environment(DataflowAnalysisContext &DACtx, Atom FlowConditionToken)
- : DACtx(&DACtx), FlowConditionToken(FlowConditionToken) {}
+ /// associated with the environment (requires `DACtx.getOptions().BoolModel ==
+ /// BoolModelKind::SAT`). Generally, new environments should be initialized
+ /// using one of the other constructors. This constructor is for specialized
+ /// use, such as deserialization.
+ Environment(DataflowAnalysisContext &DACtx, Atom FlowConditionToken);
/// Creates an environment that uses `DACtx` to store objects that encompass
- /// the state of a program. Populates a fresh atom as flow condition token.
- explicit Environment(DataflowAnalysisContext &DACtx)
- : Environment(DACtx, DACtx.arena().makeFlowConditionToken()) {}
+ /// the state of a program. Initializes the boolean model configured in
+ /// `DACtx.getOptions().BoolModel`.
+ explicit Environment(DataflowAnalysisContext &DACtx);
/// Creates an environment that uses `DACtx` to store objects that encompass
/// the state of a program, with `S` as the statement to analyze.
@@ -279,6 +343,14 @@ class Environment {
Environment &JoinedEnv,
Environment::ValueModel &Model);
+ /// Joins two distinct boolean values `Val1` (from `Env1`) and `Val2` (from
+ /// `Env2`) into this environment.
+ BoolValue &joinBoolValues(BoolValue &Val1, const Environment &Env1,
+ BoolValue &Val2, const Environment &Env2) {
+ return BoolState->joinBoolValues(Val1, *Env1.BoolState, Val2,
+ *Env2.BoolState, *this);
+ }
+
/// Widens the environment point-wise, using `PrevEnv` as needed to inform the
/// approximation.
///
@@ -669,7 +741,9 @@ class Environment {
/// In particular, where the FC token appears in flow conditions of successor
/// environments, it means "point X may have been reached", not
/// "point X was reached".
- Atom getFlowConditionToken() const { return FlowConditionToken; }
+ Atom getFlowConditionToken() const {
+ return BoolState->getFlowConditionToken();
+ }
/// Record a fact that must be true if this point in the program is reached.
void assume(const Formula &);
@@ -720,8 +794,8 @@ class Environment {
using ThisExprOverridesMap =
llvm::DenseMap<const CXXThisExpr *, RecordStorageLocation *>;
- // The copy-constructor is for use in fork() only.
- Environment(const Environment &) = default;
+ // The copy-constructor is for use in fork() and pushCall() only.
+ Environment(const Environment &);
/// Creates a value appropriate for `Type`, if `Type` is supported, otherwise
/// return null.
@@ -855,7 +929,8 @@ class Environment {
llvm::MapVector<const Expr *, Value *> ExprToVal;
llvm::MapVector<const StorageLocation *, Value *> LocToVal;
- Atom FlowConditionToken;
+ // The flow condition abstracted in a boolean model.
+ std::unique_ptr<BoolModel> BoolState;
};
/// Returns the storage location for the implicit object of a
diff --git a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
index ab0b76037837f..268ee8af63796 100644
--- a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
+++ b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
@@ -22,16 +22,20 @@
#include "clang/Analysis/FlowSensitive/ASTOps.h"
#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
#include "clang/Analysis/FlowSensitive/DataflowLattice.h"
+#include "clang/Analysis/FlowSensitive/Formula.h"
#include "clang/Analysis/FlowSensitive/StorageLocation.h"
#include "clang/Analysis/FlowSensitive/Value.h"
+#include "clang/Basic/LLVM.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/MapVector.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/ScopeExit.h"
+#include "llvm/ADT/SetVector.h"
#include "llvm/Support/ErrorHandling.h"
#include <cassert>
#include <memory>
+#include <optional>
#include <stack>
#include <utility>
@@ -40,6 +44,481 @@
namespace clang {
namespace dataflow {
+namespace {
+
+class SimpleBoolModel : public Environment::BoolModel {
+public:
+ SimpleBoolModel() : BoolModel(BoolModelKind::Simple) {}
+
+ static bool classof(const BoolModel *M) {
+ return M->getKind() == BoolModelKind::Simple;
+ }
+
+ std::unique_ptr<BoolModel> clone() const override {
+ return std::make_unique<SimpleBoolModel>(*this);
+ }
+
+ void onFork() override {}
+
+ void assume(const Formula &F) override {
+ if (HasContradiction)
+ return;
+ unsigned PrevSize = AtomToVal.size();
+ // Given that F is assumed true, see if we can force the assignment
+ // of any atoms and ultimately reduce F to a literal (if possible).
+ // For example, if F is `A && B`, we will force `A` and `B` to be true.
+ // If F is `A || B`, we will only force an assignment if one of A or B is
+ // already known to be false.
+ assumeFormula(/*AssumedVal=*/true, F);
+ if (HasContradiction)
+ return;
+
+ // Retain `F` in `AssumedFormulas` only if it has not yet been reduced to a
+ // known truth value in `AtomToVal` (e.g., `A || B` or `A == B` where
+ // neither side is known yet).
+ //
+ // - Why save unresolved formulas: a compound formula (like
+ // `EqVal => (LHS == RHS)` from a comparison expression) is often assumed
+ // while its atoms are still unknown; when we later follow a branch that
+ // fixes one of those atoms, the propagation loop below can reduce the
+ // saved formula to concrete `AtomToVal` entries.
+ //
+ // - Skipping formulas that already evaluate to `true` avoids bloating
+ // `AssumedFormulas` with single-atom and conjunctive assumptions that are
+ // already fully represented by `AtomToVal` (though it also drops
+ // disjunctive formulas that were reduced using a branch-local atom, such
+ // as on the RHS of a short-circuit `&&` or `||`).
+ // TODO(jvoung): We may want to retain some disjunctive formulas, but
+ // would need to profile and see how much it helps vs how much it costs.
+ if (std::optional<bool> Val = getLiteralValue(F)) {
+ if (!*Val) {
+ HasContradiction = true;
+ return;
+ }
+ } else {
+ AssumedFormulas.insert(&F);
+ }
+
+ if (AtomToVal.size() == PrevSize)
+ return;
+
+ // Propagate newly learned atom values through any pending assumed formulas.
+ // Note: Do not remove formulas from `AssumedFormulas` when they evaluate to
+ // `true` in a branch, because the atoms that satisfied them may be
+ // branch-local and discarded at a subsequent `join`, where the formula
+ // itself still holds on both branches.
+ bool Changed = true;
+ while (Changed && !HasContradiction) {
+ Changed = false;
+ for (const Formula *Pending : AssumedFormulas) {
+ unsigned Before = AtomToVal.size();
+ assumeFormula(/*AssumedVal=*/true, *Pending);
+ if (HasContradiction)
+ return;
+ if (getLiteralValue(*Pending) == false) {
+ HasContradiction = true;
+ return;
+ }
+ if (AtomToVal.size() > Before)
+ Changed = true;
+ }
+ }
+ }
+
+ bool proves(const Formula &F) const override {
+ if (HasContradiction)
+ return true;
+ auto V = getLiteralValue(F);
+ return V.value_or(false);
+ }
+
+ bool allows(const Formula &F) const override {
+ if (HasContradiction)
+ return false;
+ auto V = getLiteralValue(F);
+ // We are conservative in the negative direction: if we can't determine the
+ // value, assume it is allowed.
+ return V.value_or(true);
+ }
+
+ BoolValue &joinBoolValues(BoolValue &Val1, const BoolModel &Model1,
+ BoolValue &Val2, const BoolModel &Model2,
+ Environment &JoinedEnv) override {
+ const auto &S1 = cast<SimpleBoolModel>(Model1);
+ const auto &S2 = cast<SimpleBoolModel>(Model2);
+ if (S1.HasContradiction && !S2.HasContradiction)
+ return Val2;
+ if (S2.HasContradiction && !S1.HasContradiction)
+ return Val1;
+ if (auto V1 = S1.getLiteralValue(Val1.formula());
+ V1.has_value() && V1 == S2.getLiteralValue(Val2.formula()))
+ return JoinedEnv.getBoolLiteralValue(*V1);
+ return JoinedEnv.makeAtomicBoolValue();
+ }
+
+ void join(const BoolModel &ModelA, const BoolModel &ModelB) override {
+ const auto &SA = cast<SimpleBoolModel>(ModelA);
+ const auto &SB = cast<SimpleBoolModel>(ModelB);
+ AtomToVal.clear();
+ AssumedFormulas.clear();
+ HasContradiction = false;
+
+ if (SA.HasContradiction && SB.HasContradiction) {
+ HasContradiction = true;
+ } else if (SA.HasContradiction) {
+ AtomToVal = SB.AtomToVal;
+ AssumedFormulas = SB.AssumedFormulas;
+ } else if (SB.HasContradiction) {
+ AtomToVal = SA.AtomToVal;
+ AssumedFormulas = SA.AssumedFormulas;
+ } else {
+ // Intersect the AtomToVal maps.
+ for (const auto &Entry : SA.AtomToVal) {
+ auto It = SB.AtomToVal.find(Entry.first);
+ if (It != SB.AtomToVal.end() && Entry.second == It->second)
+ AtomToVal.insert({Entry.first, Entry.second});
+ }
+
+ // Keep assumed formulas that hold on both branches: either because both
+ // environments still carry `F` as an unresolved formula, or because one
+ // branch carries `F` and the other branch's `AtomToVal` assignments
+ // already prove `F`. Any branch-local formulas not proved by the other
+ // branch are dropped here, which shrinks `AssumedFormulas` at joins.
+ for (const Formula *F : SA.AssumedFormulas) {
+ if (SB.AssumedFormulas.contains(F) || SB.proves(*F))
+ AssumedFormulas.insert(F);
+ }
+ for (const Formula *F : SB.AssumedFormulas) {
+ if (SA.proves(*F))
+ AssumedFormulas.insert(F);
+ }
+ }
+ }
+
+ LatticeEffect widen(const BoolModel &PrevModel) override {
+ const auto &PrevSimple = cast<SimpleBoolModel>(PrevModel);
+ SimpleBoolModel Widened;
+ Widened.join(*this, PrevSimple);
+ LatticeEffect Effect = Widened.isEquivalentTo(PrevSimple)
+ ? LatticeEffect::Unchanged
+ : LatticeEffect::Changed;
+ *this = std::move(Widened);
+ return Effect;
+ }
+
+ bool isEquivalentTo(const BoolModel &Other) const override {
+ const auto &OtherSimple = cast<SimpleBoolModel>(Other);
+ if (HasContradiction != OtherSimple.HasContradiction)
+ return false;
+ if (HasContradiction)
+ return true;
+ if (AtomToVal.size() != OtherSimple.AtomToVal.size() ||
+ AssumedFormulas != OtherSimple.AssumedFormulas)
+ return false;
+ return llvm::all_of(AtomToVal, [&](const auto &Entry) {
+ return OtherSimple.getAtomValue(Entry.first) == Entry.second;
+ });
+ }
+
+ Atom getFlowConditionToken() const override {
+ llvm_unreachable(
+ "getFlowConditionToken() is only supported in SatBoolModel");
+ }
+
+ void dump(raw_ostream &OS) const override {
+ OS << "SimpleBoolModel:\n";
+ OS << " HasContradiction: " << HasContradiction << "\n";
+ OS << " AtomToVal:\n";
+ for (auto [A, V] : AtomToVal)
+ OS << " " << A << " = " << (V ? "true" : "false") << "\n";
+ OS << " AssumedFormulas:\n";
+ for (const Formula *F : AssumedFormulas)
+ OS << " " << *F << "\n";
+ }
+
+private:
+ std::optional<bool> getAtomValue(Atom A) const {
+ auto It = AtomToVal.find(A);
+ if (It == AtomToVal.end())
+ return std::nullopt;
+ return It->second;
+ }
+
+ void setAtomValue(Atom A, bool B) { AtomToVal[A] = B; }
+
+ std::optional<bool> getLiteralValue(const Formula &F) const {
+ switch (F.kind()) {
+ case Formula::AtomRef:
+ return getAtomValue(F.getAtom());
+ case Formula::Literal:
+ return F.literal();
+ case Formula::Not: {
+ ArrayRef<const Formula *> Operands = F.operands();
+ assert(Operands.size() == 1);
+ if (std::optional<bool> Maybe = getLiteralValue(*Operands[0]))
+ return !*Maybe;
+ return std::nullopt;
+ }
+ case Formula::And: {
+ ArrayRef<const Formula *> Operands = F.operands();
+ assert(Operands.size() == 2);
+ auto &LHS = *Operands[0];
+ auto &RHS = *Operands[1];
+ if (std::optional<bool> Left = getLiteralValue(LHS))
+ return !*Left ? Left : getLiteralValue(RHS);
+ if (std::optional<bool> Right = getLiteralValue(RHS); Right == false)
+ return Right;
+ return std::nullopt;
+ }
+ case Formula::Or: {
+ ArrayRef<const Formula *> Operands = F.operands();
+ assert(Operands.size() == 2);
+ auto &LHS = *Operands[0];
+ auto &RHS = *Operands[1];
+ if (std::optional<bool> Left = getLiteralValue(LHS))
+ return *Left ? Left : getLiteralValue(RHS);
+ if (std::optional<bool> Right = getLiteralValue(RHS); Right == true)
+ return Right;
+ return std::nullopt;
+ }
+ case Formula::Implies: {
+ ArrayRef<const Formula *> Operands = F.operands();
+ assert(Operands.size() == 2);
+ auto &LHS = *Operands[0];
+ auto &RHS = *Operands[1];
+ if (std::optional<bool> Left = getLiteralValue(LHS))
+ return !*Left ? true : getLiteralValue(RHS);
+ if (std::optional<bool> Right = getLiteralValue(RHS); Right == true)
+ return true;
+ return std::nullopt;
+ }
+ case Formula::Equal: {
+ ArrayRef<const Formula *> Operands = F.operands();
+ assert(Operands.size() == 2);
+ auto &LHS = *Operands[0];
+ auto &RHS = *Operands[1];
+ if (&LHS == &RHS)
+ return true;
+ if ((LHS.kind() == Formula::Not && LHS.operands()[0] == &RHS) ||
+ (RHS.kind() == Formula::Not && RHS.operands()[0] == &LHS))
+ return false;
+ auto V_L = getLiteralValue(LHS);
+ if (!V_L.has_value())
+ return std::nullopt;
+ auto V_R = getLiteralValue(RHS);
+ if (!V_R.has_value())
+ return std::nullopt;
+ return *V_L == *V_R;
+ }
+ }
+ }
+
+ // Updates atom assignments under the assumption that subformula `F`
+ // evaluates to `AssumedVal` (i.e., `F == AssumedVal`).
+ //
+ // Top-level `assume(F)` starts with `AssumedVal = true`, and recursive
+ // calls flip `AssumedVal` when traversing negations (`Formula::Not`) or
+ // comparisons (`Formula::Equal`) against known boolean values.
+ //
+ // For example:
+ // - for `assume(!A)`, then `A` must be `false`
+ // - for `assume(A && B)`, then both `A` and `B` must be `true`.
+ void assumeFormula(bool AssumedVal, const Formula &F) {
+ if (HasContradiction)
+ return;
+ switch (F.kind()) {
+ case Formula::AtomRef: {
+ // Base case: `F` is a single atom, so assuming `F == AssumedVal` directly
+ // binds this atom to `AssumedVal`, or marks the environment as having a
+ // contradiction if the atom was already bound to the opposite value.
+ Atom A = F.getAtom();
+ if (std::optional<bool> Existing = getAtomValue(A)) {
+ if (*Existing != AssumedVal)
+ HasContradiction = true;
+ break;
+ }
+ setAtomValue(A, AssumedVal);
+ break;
+ }
+ case Formula::Literal:
+ if (F.literal() != AssumedVal)
+ HasContradiction = true;
+ break;
+ case Formula::Not: {
+ // Assuming `(!Operand) == AssumedVal` is equivalent to assuming
+ // `Operand == !AssumedVal`.
+ ArrayRef<const Formula *> Operands = F.operands();
+ assert(Operands.size() == 1);
+ assumeFormula(!AssumedVal, *Operands[0]);
+ break;
+ }
+ case Formula::And: {
+ // - If `AssumedVal == true` (`LHS && RHS`), both operands must be `true`.
+ // - If `AssumedVal == false` (`!(LHS && RHS)` <=> `!LHS || !RHS`), at
+ // least one operand is `false`; if one operand is already known `true`,
+ // the other must be `false`.
+ ArrayRef<const Formula *> Operands = F.operands();
+ assert(Operands.size() == 2);
+ auto &LHS = *Operands[0];
+ auto &RHS = *Operands[1];
+ if (AssumedVal == true) {
+ assumeFormula(true, LHS);
+ assumeFormula(true, RHS);
+ } else {
+ if (getLiteralValue(LHS) == true)
+ assumeFormula(false, RHS);
+ else if (getLiteralValue(RHS) == true)
+ assumeFormula(false, LHS);
+ }
+ break;
+ }
+ case Formula::Or: {
+ // - If `AssumedVal == false` (`!(LHS || RHS)` <=> `!LHS && !RHS`), both
+ // operands must be `false`.
+ // - If `AssumedVal == true` (`LHS || RHS`), at least one operand is
+ // `true`; if one operand is already known `false`, the other must be
+ // `true`.
+ ArrayRef<const Formula *> Operands = F.operands();
+ assert(Operands.size() == 2);
+ auto &LHS = *Operands[0];
+ auto &RHS = *Operands[1];
+ if (AssumedVal == false) {
+ assumeFormula(false, LHS);
+ assumeFormula(false, RHS);
+ } else {
+ if (getLiteralValue(LHS) == false)
+ assumeFormula(true, RHS);
+ else if (getLiteralValue(RHS) == false)
+ assumeFormula(true, LHS);
+ }
+ break;
+ }
+ case Formula::Implies: {
+ // `LHS => RHS` is equivalent to `!LHS || RHS`.
+ // - If `AssumedVal == false` (`!(LHS => RHS)`), then `LHS` must be `true`
+ // and `RHS` must be `false`.
+ // - If `AssumedVal == true` (`LHS => RHS`):
+ // - Modus ponens: if `LHS` is known `true`, then `RHS` must be `true`.
+ // - Modus tollens: if `RHS` is known `false`, then `LHS` must be
+ // `false`.
+ ArrayRef<const Formula *> Operands = F.operands();
+ assert(Operands.size() == 2);
+ auto &LHS = *Operands[0];
+ auto &RHS = *Operands[1];
+ if (AssumedVal == false) {
+ assumeFormula(true, LHS);
+ assumeFormula(false, RHS);
+ } else {
+ if (getLiteralValue(LHS) == true)
+ assumeFormula(true, RHS);
+ else if (getLiteralValue(RHS) == false)
+ assumeFormula(false, LHS);
+ }
+ break;
+ }
+ case Formula::Equal: {
+ // Assuming `(LHS == RHS) == AssumedVal`: if one side already has a known
+ // truth value `*V`, then the other side must evaluate to
+ // `(AssumedVal == *V)`.
+ ArrayRef<const Formula *> Operands = F.operands();
+ assert(Operands.size() == 2);
+ auto &LHS = *Operands[0];
+ auto &RHS = *Operands[1];
+ if (auto V = getLiteralValue(LHS)) {
+ assumeFormula(AssumedVal == *V, RHS);
+ } else if (auto V = getLiteralValue(RHS)) {
+ assumeFormula(AssumedVal == *V, LHS);
+ }
+ break;
+ }
+ }
+ }
+
+ llvm::MapVector<Atom, bool> AtomToVal;
+ llvm::SmallSetVector<const Formula *, 4> AssumedFormulas;
+ bool HasContradiction = false;
+};
+
+class SatBoolModel : public Environment::BoolModel {
+public:
+ SatBoolModel(DataflowAnalysisContext &DACtx, Atom FlowConditionToken)
+ : BoolModel(BoolModelKind::SAT), DACtx(DACtx),
+ FlowConditionToken(FlowConditionToken) {}
+
+ explicit SatBoolModel(DataflowAnalysisContext &DACtx)
+ : SatBoolModel(DACtx, DACtx.arena().makeFlowConditionToken()) {}
+
+ static bool classof(const BoolModel *M) {
+ return M->getKind() == BoolModelKind::SAT;
+ }
+
+ std::unique_ptr<BoolModel> clone() const override {
+ return std::make_unique<SatBoolModel>(*this);
+ }
+
+ void onFork() override {
+ FlowConditionToken = DACtx.forkFlowCondition(FlowConditionToken);
+ }
+
+ void assume(const Formula &F) override {
+ DACtx.addFlowConditionConstraint(FlowConditionToken, F);
+ }
+
+ bool proves(const Formula &F) const override {
+ return DACtx.flowConditionImplies(FlowConditionToken, F);
+ }
+
+ bool allows(const Formula &F) const override {
+ return DACtx.flowConditionAllows(FlowConditionToken, F);
+ }
+
+ BoolValue &joinBoolValues(BoolValue &Val1, const BoolModel &Model1,
+ BoolValue &Val2, const BoolModel &Model2,
+ Environment &JoinedEnv) override {
+ const auto &Sat1 = cast<SatBoolModel>(Model1);
+ const auto &Sat2 = cast<SatBoolModel>(Model2);
+ auto &A = JoinedEnv.arena();
+ auto &JoinedVal = A.makeAtomRef(A.makeAtom());
+ JoinedEnv.assume(
+ A.makeOr(A.makeAnd(A.makeAtomRef(Sat1.getFlowConditionToken()),
+ A.makeEquals(JoinedVal, Val1.formula())),
+ A.makeAnd(A.makeAtomRef(Sat2.getFlowConditionToken()),
+ A.makeEquals(JoinedVal, Val2.formula()))));
+ return A.makeBoolValue(JoinedVal);
+ }
+
+ void join(const BoolModel &ModelA, const BoolModel &ModelB) override {
+ const auto &SatA = cast<SatBoolModel>(ModelA);
+ const auto &SatB = cast<SatBoolModel>(ModelB);
+ // FIXME: update join to detect backedges and simplify the flow condition
+ // accordingly.
+ FlowConditionToken = DACtx.joinFlowConditions(SatA.FlowConditionToken,
+ SatB.FlowConditionToken);
+ }
+
+ LatticeEffect widen(const BoolModel &PrevModel) override {
+ (void)cast<SatBoolModel>(PrevModel);
+ return LatticeEffect::Unchanged;
+ }
+
+ bool isEquivalentTo(const BoolModel &Other) const override {
+ (void)cast<SatBoolModel>(Other);
+ return true;
+ }
+
+ Atom getFlowConditionToken() const override { return FlowConditionToken; }
+
+ void dump(raw_ostream &OS) const override {
+ DACtx.dumpFlowCondition(FlowConditionToken, OS);
+ }
+
+private:
+ DataflowAnalysisContext &DACtx;
+ Atom FlowConditionToken;
+};
+
+} // namespace
+
// FIXME: convert these to parameters of the analysis or environment. Current
// settings have been experimentaly validated, but only for a particular
// analysis.
@@ -140,16 +619,8 @@ static Value *joinDistinctValues(QualType Type, Value &Val1,
// if (o.has_value())
// x = o.value();
// ```
- auto &Expr1 = cast<BoolValue>(Val1).formula();
- auto &Expr2 = cast<BoolValue>(Val2).formula();
- auto &A = JoinedEnv.arena();
- auto &JoinedVal = A.makeAtomRef(A.makeAtom());
- JoinedEnv.assume(
- A.makeOr(A.makeAnd(A.makeAtomRef(Env1.getFlowConditionToken()),
- A.makeEquals(JoinedVal, Expr1)),
- A.makeAnd(A.makeAtomRef(Env2.getFlowConditionToken()),
- A.makeEquals(JoinedVal, Expr2))));
- return &A.makeBoolValue(JoinedVal);
+ return &JoinedEnv.joinBoolValues(cast<BoolValue>(Val1), Env1,
+ cast<BoolValue>(Val2), Env2);
}
Value *JoinedVal = JoinedEnv.createValue(Type);
@@ -587,6 +1058,37 @@ class ThisExprOverridesVisitor : public AnalysisASTVisitor {
} // namespace
+Environment::Environment(DataflowAnalysisContext &DACtx,
+ Atom FlowConditionToken)
+ : DACtx(&DACtx),
+ BoolState(std::make_unique<SatBoolModel>(DACtx, FlowConditionToken)) {
+ assert(DACtx.getOptions().BoolModel == BoolModelKind::SAT &&
+ "FlowConditionToken constructor requires BoolModelKind::SAT");
+}
+
+Environment::Environment(DataflowAnalysisContext &DACtx) : DACtx(&DACtx) {
+ switch (DACtx.getOptions().BoolModel) {
+ case BoolModelKind::SAT:
+ BoolState = std::make_unique<SatBoolModel>(DACtx);
+ break;
+ case BoolModelKind::Simple:
+ BoolState = std::make_unique<SimpleBoolModel>();
+ break;
+ }
+}
+
+Environment::Environment(const Environment &Other)
+ : DACtx(Other.DACtx), CallStack(Other.CallStack),
+ InitialTargetFunc(Other.InitialTargetFunc),
+ InitialTargetStmt(Other.InitialTargetStmt),
+ ResultObjectMap(Other.ResultObjectMap), ReturnVal(Other.ReturnVal),
+ ReturnLoc(Other.ReturnLoc),
+ LocForRecordReturnVal(Other.LocForRecordReturnVal),
+ ThisPointeeLoc(Other.ThisPointeeLoc),
+ ThisExprOverrides(Other.ThisExprOverrides), DeclToLoc(Other.DeclToLoc),
+ ExprToLoc(Other.ExprToLoc), ExprToVal(Other.ExprToVal),
+ LocToVal(Other.LocToVal), BoolState(Other.BoolState->clone()) {}
+
void Environment::initialize() {
if (InitialTargetStmt == nullptr)
return;
@@ -702,7 +1204,7 @@ void Environment::initFieldsGlobalsAndFuncs(const ReferencedDecls &Referenced) {
Environment Environment::fork() const {
Environment Copy(*this);
- Copy.FlowConditionToken = DACtx->forkFlowCondition(FlowConditionToken);
+ Copy.BoolState->onFork();
return Copy;
}
@@ -784,7 +1286,7 @@ void Environment::popCall(const CallExpr *Call, const Environment &CalleeEnv) {
// callee's local scope, so when popping that scope, we do not propagate
// the maps.
this->LocToVal = std::move(CalleeEnv.LocToVal);
- this->FlowConditionToken = std::move(CalleeEnv.FlowConditionToken);
+ this->BoolState = CalleeEnv.BoolState->clone();
if (Call->isGLValue()) {
if (CalleeEnv.ReturnLoc != nullptr)
@@ -799,7 +1301,7 @@ void Environment::popCall(const CXXConstructExpr *Call,
const Environment &CalleeEnv) {
// See also comment in `popCall(const CallExpr *, const Environment &)` above.
this->LocToVal = std::move(CalleeEnv.LocToVal);
- this->FlowConditionToken = std::move(CalleeEnv.FlowConditionToken);
+ this->BoolState = CalleeEnv.BoolState->clone();
}
bool Environment::equivalentTo(const Environment &Other,
@@ -829,6 +1331,8 @@ bool Environment::equivalentTo(const Environment &Other,
if (!compareKeyToValueMaps(LocToVal, Other.LocToVal, *this, Other, Model))
return false;
+ if (!BoolState->isEquivalentTo(*Other.BoolState))
+ return false;
return true;
}
@@ -866,7 +1370,8 @@ LatticeEffect Environment::widen(const Environment &PrevEnv,
if (DeclToLoc.size() != PrevEnv.DeclToLoc.size() ||
ExprToLoc.size() != PrevEnv.ExprToLoc.size() ||
ExprToVal.size() != PrevEnv.ExprToVal.size() ||
- LocToVal.size() != PrevEnv.LocToVal.size())
+ LocToVal.size() != PrevEnv.LocToVal.size() ||
+ BoolState->widen(*PrevEnv.BoolState) == LatticeEffect::Changed)
Effect = LatticeEffect::Changed;
return Effect;
@@ -910,10 +1415,7 @@ Environment Environment::join(const Environment &EnvA, const Environment &EnvB,
JoinedEnv.DeclToLoc = intersectDeclToLoc(EnvA.DeclToLoc, EnvB.DeclToLoc);
- // FIXME: update join to detect backedges and simplify the flow condition
- // accordingly.
- JoinedEnv.FlowConditionToken = EnvA.DACtx->joinFlowConditions(
- EnvA.FlowConditionToken, EnvB.FlowConditionToken);
+ JoinedEnv.BoolState->join(*EnvA.BoolState, *EnvB.BoolState);
JoinedEnv.LocToVal =
joinLocToVal(EnvA.LocToVal, EnvB.LocToVal, EnvA, EnvB, JoinedEnv, Model);
@@ -1249,16 +1751,14 @@ StorageLocation &Environment::createObjectInternal(const ValueDecl *D,
return Loc;
}
-void Environment::assume(const Formula &F) {
- DACtx->addFlowConditionConstraint(FlowConditionToken, F);
-}
+void Environment::assume(const Formula &F) { BoolState->assume(F); }
bool Environment::proves(const Formula &F) const {
- return DACtx->flowConditionImplies(FlowConditionToken, F);
+ return BoolState->proves(F);
}
bool Environment::allows(const Formula &F) const {
- return DACtx->flowConditionAllows(FlowConditionToken, F);
+ return BoolState->allows(F);
}
void Environment::dump(raw_ostream &OS) const {
@@ -1311,7 +1811,7 @@ void Environment::dump(raw_ostream &OS) const {
}
OS << "\n";
- DACtx->dumpFlowCondition(FlowConditionToken, OS);
+ BoolState->dump(OS);
}
void Environment::dump() const { dump(llvm::dbgs()); }
diff --git a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
index 60d1d038540ff..6810f578be5b4 100644
--- a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
@@ -91,64 +91,108 @@ struct promise {
};
)cc";
-void runDataflow(
- llvm::StringRef Code,
- std::function<
- void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &,
- ASTContext &)>
- VerifyResults,
- DataflowAnalysisOptions Options,
- LangStandard::Kind Std = LangStandard::lang_cxx17,
- llvm::StringRef TargetFun = "target") {
- ASSERT_THAT_ERROR(checkDataflowWithNoopAnalysis(Code, VerifyResults, Options,
- Std, TargetFun),
- llvm::Succeeded());
-}
+class TransferTest : public ::testing::TestWithParam<BoolModelKind> {
+protected:
+ llvm::Error checkDataflowWithNoopAnalysis(
+ llvm::StringRef Code,
+ std::function<
+ void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &,
+ ASTContext &)>
+ VerifyResults = [](const auto &, auto &) {},
+ DataflowAnalysisOptions Options = {BuiltinOptions()},
+ LangStandard::Kind Std = LangStandard::lang_cxx17,
+ llvm::StringRef TargetFun = "target") {
+ if (Options.BuiltinOpts)
+ Options.BuiltinOpts->BoolModel = GetParam();
+ return test::checkDataflowWithNoopAnalysis(Code, std::move(VerifyResults),
+ Options, Std, TargetFun);
+ }
-void runDataflow(
- llvm::StringRef Code,
- std::function<
- void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &,
- ASTContext &)>
- VerifyResults,
- LangStandard::Kind Std = LangStandard::lang_cxx17,
- bool ApplyBuiltinTransfer = true, llvm::StringRef TargetFun = "target") {
- runDataflow(Code, std::move(VerifyResults),
- {ApplyBuiltinTransfer ? BuiltinOptions{}
- : std::optional<BuiltinOptions>()},
- Std, TargetFun);
-}
-
-void runDataflowOnLambda(
- llvm::StringRef Code,
- std::function<
- void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &,
- ASTContext &)>
- VerifyResults,
- DataflowAnalysisOptions Options,
- LangStandard::Kind Std = LangStandard::lang_cxx17) {
- ASSERT_THAT_ERROR(
- checkDataflowWithNoopAnalysis(
- Code,
- ast_matchers::hasDeclContext(
- ast_matchers::cxxRecordDecl(ast_matchers::isLambda())),
- VerifyResults, Options, Std),
- llvm::Succeeded());
-}
+ llvm::Error checkDataflowWithNoopAnalysis(
+ llvm::StringRef Code,
+ ast_matchers::internal::Matcher<FunctionDecl> TargetFuncMatcher,
+ std::function<
+ void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &,
+ ASTContext &)>
+ VerifyResults = [](const auto &, auto &) {},
+ DataflowAnalysisOptions Options = {BuiltinOptions()},
+ LangStandard::Kind Std = LangStandard::lang_cxx17,
+ std::function<llvm::StringMap<QualType>(QualType)>
+ SyntheticFieldCallback = {}) {
+ if (Options.BuiltinOpts)
+ Options.BuiltinOpts->BoolModel = GetParam();
+ return test::checkDataflowWithNoopAnalysis(
+ Code, std::move(TargetFuncMatcher), std::move(VerifyResults), Options,
+ Std, std::move(SyntheticFieldCallback));
+ }
-void runDataflowOnLambda(
- llvm::StringRef Code,
- std::function<
- void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &,
- ASTContext &)>
- VerifyResults,
- LangStandard::Kind Std = LangStandard::lang_cxx17,
- bool ApplyBuiltinTransfer = true) {
- runDataflowOnLambda(Code, std::move(VerifyResults),
- {ApplyBuiltinTransfer ? BuiltinOptions{}
- : std::optional<BuiltinOptions>()},
- Std);
-}
+ void
+ runDataflow(llvm::StringRef Code,
+ std::function<void(
+ const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &,
+ ASTContext &)>
+ VerifyResults,
+ DataflowAnalysisOptions Options,
+ LangStandard::Kind Std = LangStandard::lang_cxx17,
+ llvm::StringRef TargetFun = "target") {
+ ASSERT_THAT_ERROR(checkDataflowWithNoopAnalysis(Code, VerifyResults,
+ Options, Std, TargetFun),
+ llvm::Succeeded());
+ }
+
+ void runDataflow(
+ llvm::StringRef Code,
+ std::function<
+ void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &,
+ ASTContext &)>
+ VerifyResults,
+ LangStandard::Kind Std = LangStandard::lang_cxx17,
+ bool ApplyBuiltinTransfer = true, llvm::StringRef TargetFun = "target") {
+ runDataflow(Code, std::move(VerifyResults),
+ {ApplyBuiltinTransfer ? BuiltinOptions{}
+ : std::optional<BuiltinOptions>()},
+ Std, TargetFun);
+ }
+
+ void runDataflowOnLambda(
+ llvm::StringRef Code,
+ std::function<
+ void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &,
+ ASTContext &)>
+ VerifyResults,
+ DataflowAnalysisOptions Options,
+ LangStandard::Kind Std = LangStandard::lang_cxx17) {
+ ASSERT_THAT_ERROR(
+ checkDataflowWithNoopAnalysis(
+ Code,
+ ast_matchers::hasDeclContext(
+ ast_matchers::cxxRecordDecl(ast_matchers::isLambda())),
+ VerifyResults, Options, Std),
+ llvm::Succeeded());
+ }
+
+ void runDataflowOnLambda(
+ llvm::StringRef Code,
+ std::function<
+ void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &,
+ ASTContext &)>
+ VerifyResults,
+ LangStandard::Kind Std = LangStandard::lang_cxx17,
+ bool ApplyBuiltinTransfer = true) {
+ runDataflowOnLambda(Code, std::move(VerifyResults),
+ {ApplyBuiltinTransfer
+ ? BuiltinOptions{}
+ : std::optional<BuiltinOptions>()},
+ Std);
+ }
+};
+
+INSTANTIATE_TEST_SUITE_P(
+ TransferTestInst, TransferTest,
+ ::testing::Values(BoolModelKind::SAT, BoolModelKind::Simple),
+ [](const ::testing::TestParamInfo<BoolModelKind> &Info) {
+ return Info.param == BoolModelKind::SAT ? "SAT" : "Simple";
+ });
const Formula &getFormula(const ValueDecl &D, const Environment &Env) {
return cast<BoolValue>(Env.getValue(D))->formula();
@@ -163,7 +207,7 @@ const BindingDecl *findBindingDecl(ASTContext &ASTCtx, std::string_view Name) {
return ast_matchers::selectFirst<BindingDecl>("v", TargetNodes);
}
-TEST(TransferTest, CNotSupported) {
+TEST_P(TransferTest, CNotSupported) {
TestInputs Inputs("void target() {}");
Inputs.Language = TestLanguage::Lang_C89;
clang::TestAST AST(Inputs);
@@ -173,7 +217,7 @@ TEST(TransferTest, CNotSupported) {
llvm::FailedWithMessage("Can only analyze C++"));
}
-TEST(TransferTest, ObjectiveCNotSupported) {
+TEST_P(TransferTest, ObjectiveCNotSupported) {
TestInputs Inputs("void target() {}");
Inputs.Language = TestLanguage::Lang_OBJC;
clang::TestAST AST(Inputs);
@@ -183,7 +227,7 @@ TEST(TransferTest, ObjectiveCNotSupported) {
llvm::FailedWithMessage("Can only analyze C++"));
}
-TEST(TransferTest, ObjectiveCXXNotSupported) {
+TEST_P(TransferTest, ObjectiveCXXNotSupported) {
TestInputs Inputs("void target() {}");
Inputs.Language = TestLanguage::Lang_OBJCXX;
clang::TestAST AST(Inputs);
@@ -193,7 +237,7 @@ TEST(TransferTest, ObjectiveCXXNotSupported) {
llvm::FailedWithMessage("Can only analyze C++"));
}
-TEST(TransferTest, IntVarDeclNotTrackedWhenTransferDisabled) {
+TEST_P(TransferTest, IntVarDeclNotTrackedWhenTransferDisabled) {
std::string Code = R"(
void target() {
int Foo;
@@ -216,7 +260,7 @@ TEST(TransferTest, IntVarDeclNotTrackedWhenTransferDisabled) {
/*ApplyBuiltinTransfer=*/false);
}
-TEST(TransferTest, BoolVarDecl) {
+TEST_P(TransferTest, BoolVarDecl) {
std::string Code = R"(
void target() {
bool Foo;
@@ -241,7 +285,7 @@ TEST(TransferTest, BoolVarDecl) {
});
}
-TEST(TransferTest, IntVarDecl) {
+TEST_P(TransferTest, IntVarDecl) {
std::string Code = R"(
void target() {
int Foo;
@@ -266,7 +310,7 @@ TEST(TransferTest, IntVarDecl) {
});
}
-TEST(TransferTest, StructIncomplete) {
+TEST_P(TransferTest, StructIncomplete) {
std::string Code = R"(
struct A;
@@ -295,7 +339,7 @@ TEST(TransferTest, StructIncomplete) {
// level (currently, depth 3). This test verifies this lack of modeling. We also
// include a regression test for the case that the unmodeled field is a
// reference to a struct; previously, we crashed when accessing such a field.
-TEST(TransferTest, StructFieldUnmodeled) {
+TEST_P(TransferTest, StructFieldUnmodeled) {
std::string Code = R"(
struct S { int X; };
S GlobalS;
@@ -347,7 +391,7 @@ TEST(TransferTest, StructFieldUnmodeled) {
});
}
-TEST(TransferTest, StructVarDecl) {
+TEST_P(TransferTest, StructVarDecl) {
std::string Code = R"(
struct A {
int Bar;
@@ -388,7 +432,7 @@ TEST(TransferTest, StructVarDecl) {
});
}
-TEST(TransferTest, StructVarDeclWithInit) {
+TEST_P(TransferTest, StructVarDeclWithInit) {
std::string Code = R"(
struct A {
int Bar;
@@ -431,7 +475,7 @@ TEST(TransferTest, StructVarDeclWithInit) {
});
}
-TEST(TransferTest, StructArrayVarDecl) {
+TEST_P(TransferTest, StructArrayVarDecl) {
std::string Code = R"(
struct A {};
@@ -453,7 +497,7 @@ TEST(TransferTest, StructArrayVarDecl) {
});
}
-TEST(TransferTest, ClassVarDecl) {
+TEST_P(TransferTest, ClassVarDecl) {
std::string Code = R"(
class A {
public:
@@ -495,7 +539,7 @@ TEST(TransferTest, ClassVarDecl) {
});
}
-TEST(TransferTest, ReferenceVarDecl) {
+TEST_P(TransferTest, ReferenceVarDecl) {
std::string Code = R"(
struct A {};
@@ -521,7 +565,7 @@ TEST(TransferTest, ReferenceVarDecl) {
});
}
-TEST(TransferTest, SelfReferentialReferenceVarDecl) {
+TEST_P(TransferTest, SelfReferentialReferenceVarDecl) {
std::string Code = R"(
struct A;
@@ -626,7 +670,7 @@ TEST(TransferTest, SelfReferentialReferenceVarDecl) {
});
}
-TEST(TransferTest, PointerVarDecl) {
+TEST_P(TransferTest, PointerVarDecl) {
std::string Code = R"(
struct A {};
@@ -656,7 +700,7 @@ TEST(TransferTest, PointerVarDecl) {
});
}
-TEST(TransferTest, SelfReferentialPointerVarDecl) {
+TEST_P(TransferTest, SelfReferentialPointerVarDecl) {
std::string Code = R"(
struct A;
@@ -770,7 +814,7 @@ TEST(TransferTest, SelfReferentialPointerVarDecl) {
});
}
-TEST(TransferTest, DirectlySelfReferentialReference) {
+TEST_P(TransferTest, DirectlySelfReferentialReference) {
std::string Code = R"(
struct target {
target() {
@@ -792,7 +836,7 @@ TEST(TransferTest, DirectlySelfReferentialReference) {
});
}
-TEST(TransferTest, MultipleVarsDecl) {
+TEST_P(TransferTest, MultipleVarsDecl) {
std::string Code = R"(
void target() {
int Foo, Bar;
@@ -827,7 +871,7 @@ TEST(TransferTest, MultipleVarsDecl) {
});
}
-TEST(TransferTest, JoinVarDecl) {
+TEST_P(TransferTest, JoinVarDecl) {
std::string Code = R"(
void target(bool B) {
int Foo;
@@ -881,7 +925,7 @@ TEST(TransferTest, JoinVarDecl) {
});
}
-TEST(TransferTest, BinaryOperatorAssign) {
+TEST_P(TransferTest, BinaryOperatorAssign) {
std::string Code = R"(
void target() {
int Foo;
@@ -910,7 +954,7 @@ TEST(TransferTest, BinaryOperatorAssign) {
});
}
-TEST(TransferTest, BinaryOperatorAssignIntegerLiteral) {
+TEST_P(TransferTest, BinaryOperatorAssignIntegerLiteral) {
std::string Code = R"(
void target() {
int Foo = 1;
@@ -935,7 +979,7 @@ TEST(TransferTest, BinaryOperatorAssignIntegerLiteral) {
});
}
-TEST(TransferTest, BinaryOperatorAssignUnknown) {
+TEST_P(TransferTest, BinaryOperatorAssignUnknown) {
std::string Code = R"(
int unknown();
@@ -1009,7 +1053,7 @@ TEST(TransferTest, BinaryOperatorAssignUnknown) {
});
}
-TEST(TransferTest, BinaryOperatorAssignFloat) {
+TEST_P(TransferTest, BinaryOperatorAssignFloat) {
using ast_matchers::binaryOperator;
using ast_matchers::hasOperatorName;
using ast_matchers::match;
@@ -1056,7 +1100,7 @@ TEST(TransferTest, BinaryOperatorAssignFloat) {
});
}
-TEST(TransferTest, VarDeclInitAssign) {
+TEST_P(TransferTest, VarDeclInitAssign) {
std::string Code = R"(
void target() {
int Foo;
@@ -1084,7 +1128,7 @@ TEST(TransferTest, VarDeclInitAssign) {
});
}
-TEST(TransferTest, VarDeclInitAssignChained) {
+TEST_P(TransferTest, VarDeclInitAssignChained) {
std::string Code = R"(
void target() {
int Foo;
@@ -1117,7 +1161,7 @@ TEST(TransferTest, VarDeclInitAssignChained) {
});
}
-TEST(TransferTest, VarDeclInitAssignPtrDeref) {
+TEST_P(TransferTest, VarDeclInitAssignPtrDeref) {
std::string Code = R"(
void target() {
int Foo;
@@ -1153,7 +1197,7 @@ TEST(TransferTest, VarDeclInitAssignPtrDeref) {
});
}
-TEST(TransferTest, AssignToAndFromReference) {
+TEST_P(TransferTest, AssignToAndFromReference) {
std::string Code = R"(
void target() {
int Foo;
@@ -1204,7 +1248,7 @@ TEST(TransferTest, AssignToAndFromReference) {
});
}
-TEST(TransferTest, MultipleParamDecls) {
+TEST_P(TransferTest, MultipleParamDecls) {
std::string Code = R"(
void target(int Foo, int Bar) {
(void)0;
@@ -1238,7 +1282,7 @@ TEST(TransferTest, MultipleParamDecls) {
});
}
-TEST(TransferTest, StructParamDecl) {
+TEST_P(TransferTest, StructParamDecl) {
std::string Code = R"(
struct A {
int Bar;
@@ -1278,7 +1322,7 @@ TEST(TransferTest, StructParamDecl) {
});
}
-TEST(TransferTest, ReferenceParamDecl) {
+TEST_P(TransferTest, ReferenceParamDecl) {
std::string Code = R"(
struct A {};
@@ -1302,7 +1346,7 @@ TEST(TransferTest, ReferenceParamDecl) {
});
}
-TEST(TransferTest, PointerParamDecl) {
+TEST_P(TransferTest, PointerParamDecl) {
std::string Code = R"(
struct A {};
@@ -1330,7 +1374,7 @@ TEST(TransferTest, PointerParamDecl) {
});
}
-TEST(TransferTest, StructMember) {
+TEST_P(TransferTest, StructMember) {
std::string Code = R"(
struct A {
int Bar;
@@ -1376,7 +1420,7 @@ TEST(TransferTest, StructMember) {
});
}
-TEST(TransferTest, StructMemberEnum) {
+TEST_P(TransferTest, StructMemberEnum) {
std::string Code = R"(
struct A {
int Bar;
@@ -1398,7 +1442,7 @@ TEST(TransferTest, StructMemberEnum) {
});
}
-TEST(TransferTest, DerivedBaseMemberClass) {
+TEST_P(TransferTest, DerivedBaseMemberClass) {
std::string Code = R"(
class A {
int ADefault;
@@ -1531,7 +1575,7 @@ static void derivedBaseMemberExpectations(
EXPECT_NE(Env.getValue(*FooLoc.getChild(*BarDecl)), nullptr);
}
-TEST(TransferTest, DerivedBaseMemberStructDefault) {
+TEST_P(TransferTest, DerivedBaseMemberStructDefault) {
std::string Code = R"(
struct A {
int Bar;
@@ -1548,7 +1592,7 @@ TEST(TransferTest, DerivedBaseMemberStructDefault) {
runDataflow(Code, derivedBaseMemberExpectations);
}
-TEST(TransferTest, DerivedBaseMemberPrivateFriend) {
+TEST_P(TransferTest, DerivedBaseMemberPrivateFriend) {
// Include an access to `Foo.Bar` to verify the analysis doesn't crash on that
// access.
std::string Code = R"(
@@ -1569,7 +1613,7 @@ TEST(TransferTest, DerivedBaseMemberPrivateFriend) {
runDataflow(Code, derivedBaseMemberExpectations);
}
-TEST(TransferTest, ClassMember) {
+TEST_P(TransferTest, ClassMember) {
std::string Code = R"(
class A {
public:
@@ -1616,7 +1660,7 @@ TEST(TransferTest, ClassMember) {
});
}
-TEST(TransferTest, BaseClassInitializer) {
+TEST_P(TransferTest, BaseClassInitializer) {
using ast_matchers::cxxConstructorDecl;
using ast_matchers::hasName;
using ast_matchers::ofClass;
@@ -1641,6 +1685,9 @@ TEST(TransferTest, BaseClassInitializer) {
AnalysisInputs<NoopAnalysis>(
Code, cxxConstructorDecl(ofClass(hasName("B"))),
[](ASTContext &C, Environment &) { return NoopAnalysis(C); })
+ .withBuiltinOptions({/*ContextSensitiveOpts=*/std::nullopt,
+ /*Logger=*/nullptr,
+ /*BoolModel=*/GetParam()})
.withASTBuildArgs(
{"-fsyntax-only", "-fno-delayed-template-parsing",
"-std=" + std::string(LangStandard::getLangStandardForKind(
@@ -1658,7 +1705,7 @@ TEST(TransferTest, BaseClassInitializer) {
llvm::Succeeded());
}
-TEST(TransferTest, BaseClassInitializerFromSiblingDerivedInstance) {
+TEST_P(TransferTest, BaseClassInitializerFromSiblingDerivedInstance) {
using ast_matchers::cxxConstructorDecl;
using ast_matchers::hasName;
using ast_matchers::ofClass;
@@ -1692,7 +1739,7 @@ TEST(TransferTest, BaseClassInitializerFromSiblingDerivedInstance) {
LangStandard::lang_cxx17, /*ApplyBuiltinTransfer=*/true, "DerivedTwo");
}
-TEST(TransferTest, CopyAssignmentToDerivedToBase) {
+TEST_P(TransferTest, CopyAssignmentToDerivedToBase) {
std::string Code = R"cc(
struct Base {};
@@ -1719,7 +1766,7 @@ struct DerivedTwo : public Base {
LangStandard::lang_cxx17, /*ApplyBuiltinTransfer=*/true, "DerivedTwo");
}
-TEST(TransferTest, FieldsDontHaveValuesInConstructor) {
+TEST_P(TransferTest, FieldsDontHaveValuesInConstructor) {
// In a constructor, unlike in regular member functions, we don't want fields
// to be pre-initialized with values, because doing so is the job of the
// constructor.
@@ -1746,7 +1793,7 @@ TEST(TransferTest, FieldsDontHaveValuesInConstructor) {
});
}
-TEST(TransferTest, FieldsDontHaveValuesInConstructorWithBaseClass) {
+TEST_P(TransferTest, FieldsDontHaveValuesInConstructorWithBaseClass) {
// See above, but for a class with a base class.
std::string Code = R"(
struct Base {
@@ -1781,7 +1828,7 @@ TEST(TransferTest, FieldsDontHaveValuesInConstructorWithBaseClass) {
});
}
-TEST(TransferTest, StructModeledFieldsWithAccessor) {
+TEST_P(TransferTest, StructModeledFieldsWithAccessor) {
std::string Code = R"(
class S {
int *Ptr;
@@ -1839,7 +1886,7 @@ TEST(TransferTest, StructModeledFieldsWithAccessor) {
});
}
-TEST(TransferTest, StructModeledFieldsInTypeid) {
+TEST_P(TransferTest, StructModeledFieldsInTypeid) {
// Test that we model fields mentioned inside a `typeid()` expression only if
// that expression is potentially evaluated -- i.e. if the expression inside
// `typeid()` is a glvalue of polymorphic type (see
@@ -1882,7 +1929,7 @@ TEST(TransferTest, StructModeledFieldsInTypeid) {
});
}
-TEST(TransferTest, StructModeledFieldsWithComplicatedInheritance) {
+TEST_P(TransferTest, StructModeledFieldsWithComplicatedInheritance) {
std::string Code = R"(
struct Base1 {
int base1_1;
@@ -1930,7 +1977,7 @@ TEST(TransferTest, StructModeledFieldsWithComplicatedInheritance) {
});
}
-TEST(TransferTest, StructInitializerListWithComplicatedInheritance) {
+TEST_P(TransferTest, StructInitializerListWithComplicatedInheritance) {
std::string Code = R"(
struct Base1 {
int base1;
@@ -1975,7 +2022,7 @@ TEST(TransferTest, StructInitializerListWithComplicatedInheritance) {
});
}
-TEST(TransferTest, ReferenceMember) {
+TEST_P(TransferTest, ReferenceMember) {
std::string Code = R"(
struct A {
int &Bar;
@@ -2021,7 +2068,7 @@ TEST(TransferTest, ReferenceMember) {
});
}
-TEST(TransferTest, StructThisMember) {
+TEST_P(TransferTest, StructThisMember) {
std::string Code = R"(
struct A {
int Bar;
@@ -2091,7 +2138,7 @@ TEST(TransferTest, StructThisMember) {
});
}
-TEST(TransferTest, ClassThisMember) {
+TEST_P(TransferTest, ClassThisMember) {
std::string Code = R"(
class A {
int Bar;
@@ -2161,7 +2208,7 @@ TEST(TransferTest, ClassThisMember) {
});
}
-TEST(TransferTest, UnionThisMember) {
+TEST_P(TransferTest, UnionThisMember) {
std::string Code = R"(
union A {
int Foo;
@@ -2208,7 +2255,7 @@ TEST(TransferTest, UnionThisMember) {
});
}
-TEST(TransferTest, StructThisInLambda) {
+TEST_P(TransferTest, StructThisInLambda) {
std::string ThisCaptureCode = R"(
struct A {
void frob() {
@@ -2306,7 +2353,7 @@ TEST(TransferTest, StructThisInLambda) {
LangStandard::lang_cxx17, /*ApplyBuiltinTransfer=*/true, "operator()");
}
-TEST(TransferTest, ConstructorInitializer) {
+TEST_P(TransferTest, ConstructorInitializer) {
std::string Code = R"(
struct target {
int Bar;
@@ -2338,7 +2385,7 @@ TEST(TransferTest, ConstructorInitializer) {
});
}
-TEST(TransferTest, DefaultInitializer) {
+TEST_P(TransferTest, DefaultInitializer) {
std::string Code = R"(
struct target {
int Bar;
@@ -2371,7 +2418,7 @@ TEST(TransferTest, DefaultInitializer) {
});
}
-TEST(TransferTest, DefaultInitializerReference) {
+TEST_P(TransferTest, DefaultInitializerReference) {
std::string Code = R"(
struct target {
int &Bar;
@@ -2406,7 +2453,7 @@ TEST(TransferTest, DefaultInitializerReference) {
});
}
-TEST(TransferTest, TemporaryObject) {
+TEST_P(TransferTest, TemporaryObject) {
std::string Code = R"(
struct A {
int Bar;
@@ -2437,7 +2484,7 @@ TEST(TransferTest, TemporaryObject) {
});
}
-TEST(TransferTest, ElidableConstructor) {
+TEST_P(TransferTest, ElidableConstructor) {
// This test is effectively the same as TransferTest.TemporaryObject, but
// the code is compiled as C++14.
std::string Code = R"(
@@ -2471,7 +2518,7 @@ TEST(TransferTest, ElidableConstructor) {
LangStandard::lang_cxx14);
}
-TEST(TransferTest, AssignmentOperator) {
+TEST_P(TransferTest, AssignmentOperator) {
std::string Code = R"(
struct A {
int Baz;
@@ -2558,7 +2605,7 @@ TEST(TransferTest, AssignmentOperator) {
// It's legal for the assignment operator to take its source parameter by value.
// Check that we handle this correctly. (This is a repro -- we used to
// assert-fail on this.)
-TEST(TransferTest, AssignmentOperator_ArgByValue) {
+TEST_P(TransferTest, AssignmentOperator_ArgByValue) {
std::string Code = R"(
struct A {
int Baz;
@@ -2592,7 +2639,7 @@ TEST(TransferTest, AssignmentOperator_ArgByValue) {
});
}
-TEST(TransferTest, AssignmentOperatorFromBase) {
+TEST_P(TransferTest, AssignmentOperatorFromBase) {
std::string Code = R"(
struct Base {
int base;
@@ -2633,7 +2680,7 @@ TEST(TransferTest, AssignmentOperatorFromBase) {
});
}
-TEST(TransferTest, AssignmentOperatorFromCallResult) {
+TEST_P(TransferTest, AssignmentOperatorFromCallResult) {
std::string Code = R"(
struct A {};
A ReturnA();
@@ -2653,7 +2700,7 @@ TEST(TransferTest, AssignmentOperatorFromCallResult) {
});
}
-TEST(TransferTest, AssignmentOperatorWithInitAndInheritance) {
+TEST_P(TransferTest, AssignmentOperatorWithInitAndInheritance) {
// This is a crash repro.
std::string Code = R"(
struct B { int Foo; };
@@ -2673,7 +2720,7 @@ TEST(TransferTest, AssignmentOperatorWithInitAndInheritance) {
ASTContext &ASTCtx) {});
}
-TEST(TransferTest, AssignmentOperatorReturnsVoid) {
+TEST_P(TransferTest, AssignmentOperatorReturnsVoid) {
// This is a crash repro.
std::string Code = R"(
struct S {
@@ -2691,7 +2738,7 @@ TEST(TransferTest, AssignmentOperatorReturnsVoid) {
ASTContext &ASTCtx) {});
}
-TEST(TransferTest, AssignmentOperatorReturnsByValue) {
+TEST_P(TransferTest, AssignmentOperatorReturnsByValue) {
// This is a crash repro.
std::string Code = R"(
struct S {
@@ -2739,7 +2786,7 @@ TEST(TransferTest, AssignmentOperatorReturnsByValue) {
});
}
-TEST(TransferTest, AssignmentOperatorReturnsDifferentTypeByRef) {
+TEST_P(TransferTest, AssignmentOperatorReturnsDifferentTypeByRef) {
// This is a crash repro.
std::string Code = R"(
struct DifferentType {};
@@ -2758,7 +2805,7 @@ TEST(TransferTest, AssignmentOperatorReturnsDifferentTypeByRef) {
ASTContext &ASTCtx) {});
}
-TEST(TransferTest, AssignmentOperatorReturnsDifferentTypeByValue) {
+TEST_P(TransferTest, AssignmentOperatorReturnsDifferentTypeByValue) {
// This is a crash repro.
std::string Code = R"(
struct DifferentType {};
@@ -2777,7 +2824,7 @@ TEST(TransferTest, AssignmentOperatorReturnsDifferentTypeByValue) {
ASTContext &ASTCtx) {});
}
-TEST(TransferTest, InitListExprAsXValue) {
+TEST_P(TransferTest, InitListExprAsXValue) {
// This is a crash repro.
std::string Code = R"(
void target() {
@@ -2795,7 +2842,7 @@ TEST(TransferTest, InitListExprAsXValue) {
});
}
-TEST(TransferTest, ArrayInitListExprOneRecordElement) {
+TEST_P(TransferTest, ArrayInitListExprOneRecordElement) {
// This is a crash repro.
std::string Code = R"cc(
struct S {};
@@ -2810,7 +2857,7 @@ TEST(TransferTest, ArrayInitListExprOneRecordElement) {
});
}
-TEST(TransferTest, InitListExprAsUnion) {
+TEST_P(TransferTest, InitListExprAsUnion) {
// This is a crash repro.
std::string Code = R"cc(
class target {
@@ -2841,7 +2888,7 @@ TEST(TransferTest, InitListExprAsUnion) {
});
}
-TEST(TransferTest, EmptyInitListExprForUnion) {
+TEST_P(TransferTest, EmptyInitListExprForUnion) {
// This is a crash repro.
std::string Code = R"cc(
class target {
@@ -2876,7 +2923,7 @@ TEST(TransferTest, EmptyInitListExprForUnion) {
});
}
-TEST(TransferTest, EmptyInitListExprForStruct) {
+TEST_P(TransferTest, EmptyInitListExprForStruct) {
std::string Code = R"cc(
class target {
struct {
@@ -2909,7 +2956,7 @@ TEST(TransferTest, EmptyInitListExprForStruct) {
});
}
-TEST(TransferTest, CopyConstructor) {
+TEST_P(TransferTest, CopyConstructor) {
std::string Code = R"(
struct A {
int Baz;
@@ -2978,7 +3025,7 @@ TEST(TransferTest, CopyConstructor) {
});
}
-TEST(TransferTest, CopyConstructorWithDefaultArgument) {
+TEST_P(TransferTest, CopyConstructorWithDefaultArgument) {
std::string Code = R"(
struct A {
int Baz;
@@ -3023,7 +3070,7 @@ TEST(TransferTest, CopyConstructorWithDefaultArgument) {
});
}
-TEST(TransferTest, CopyConstructorWithParens) {
+TEST_P(TransferTest, CopyConstructorWithParens) {
std::string Code = R"(
struct A {
int Baz;
@@ -3066,7 +3113,7 @@ TEST(TransferTest, CopyConstructorWithParens) {
});
}
-TEST(TransferTest, CopyConstructorWithInitializerListAsSyntacticSugar) {
+TEST_P(TransferTest, CopyConstructorWithInitializerListAsSyntacticSugar) {
std::string Code = R"(
struct A {
int Baz;
@@ -3099,7 +3146,7 @@ TEST(TransferTest, CopyConstructorWithInitializerListAsSyntacticSugar) {
});
}
-TEST(TransferTest, CopyConstructorArgIsRefReturnedByFunction) {
+TEST_P(TransferTest, CopyConstructorArgIsRefReturnedByFunction) {
// This is a crash repro.
std::string Code = R"(
struct S {};
@@ -3114,7 +3161,7 @@ TEST(TransferTest, CopyConstructorArgIsRefReturnedByFunction) {
ASTContext &ASTCtx) {});
}
-TEST(TransferTest, MoveConstructor) {
+TEST_P(TransferTest, MoveConstructor) {
std::string Code = R"(
namespace std {
@@ -3183,7 +3230,7 @@ TEST(TransferTest, MoveConstructor) {
});
}
-TEST(TransferTest, BindTemporary) {
+TEST_P(TransferTest, BindTemporary) {
std::string Code = R"(
struct A {
virtual ~A() = default;
@@ -3219,7 +3266,7 @@ TEST(TransferTest, BindTemporary) {
});
}
-TEST(TransferTest, ResultObjectLocation) {
+TEST_P(TransferTest, ResultObjectLocation) {
std::string Code = R"(
struct A {
virtual ~A() = default;
@@ -3278,7 +3325,7 @@ TEST(TransferTest, ResultObjectLocation) {
});
}
-TEST(TransferTest, ResultObjectLocationForDefaultArgExpr) {
+TEST_P(TransferTest, ResultObjectLocationForDefaultArgExpr) {
std::string Code = R"(
struct Inner {};
struct Outer {
@@ -3312,7 +3359,7 @@ TEST(TransferTest, ResultObjectLocationForDefaultArgExpr) {
});
}
-TEST(TransferTest, ResultObjectLocationForDefaultInitExpr) {
+TEST_P(TransferTest, ResultObjectLocationForDefaultInitExpr) {
std::string Code = R"(
struct S {};
struct target {
@@ -3351,7 +3398,7 @@ TEST(TransferTest, ResultObjectLocationForDefaultInitExpr) {
// This test ensures that CXXOperatorCallExpr returning prvalues are correctly
// handled by the transfer functions, especially that `getResultObjectLocation`
// correctly returns a storage location for those.
-TEST(TransferTest, ResultObjectLocationForCXXOperatorCallExpr) {
+TEST_P(TransferTest, ResultObjectLocationForCXXOperatorCallExpr) {
std::string Code = R"(
struct A {
A operator+(int);
@@ -3381,7 +3428,7 @@ TEST(TransferTest, ResultObjectLocationForCXXOperatorCallExpr) {
});
}
-TEST(TransferTest, ResultObjectLocationForInitListExpr) {
+TEST_P(TransferTest, ResultObjectLocationForInitListExpr) {
std::string Code = R"cc(
struct Inner {};
@@ -3417,7 +3464,7 @@ TEST(TransferTest, ResultObjectLocationForInitListExpr) {
});
}
-TEST(TransferTest, ResultObjectLocationForParenInitListExpr) {
+TEST_P(TransferTest, ResultObjectLocationForParenInitListExpr) {
std::string Code = R"cc(
struct Inner {};
@@ -3456,7 +3503,7 @@ TEST(TransferTest, ResultObjectLocationForParenInitListExpr) {
// Check that the `std::strong_ordering` object returned by builtin `<=>` has a
// correctly modeled result object location.
-TEST(TransferTest, ResultObjectLocationForBuiltinSpaceshipOperator) {
+TEST_P(TransferTest, ResultObjectLocationForBuiltinSpaceshipOperator) {
std::string Code = R"(
namespace std {
// This is the minimal definition required to get
@@ -3506,7 +3553,7 @@ TEST(TransferTest, ResultObjectLocationForBuiltinSpaceshipOperator) {
LangStandard::lang_cxx20);
}
-TEST(TransferTest, ResultObjectLocationForStdInitializerListExpr) {
+TEST_P(TransferTest, ResultObjectLocationForStdInitializerListExpr) {
std::string Code = R"(
namespace std {
template <typename T>
@@ -3538,7 +3585,7 @@ TEST(TransferTest, ResultObjectLocationForStdInitializerListExpr) {
});
}
-TEST(TransferTest, ResultObjectLocationForStmtExpr) {
+TEST_P(TransferTest, ResultObjectLocationForStmtExpr) {
std::string Code = R"(
struct S {};
void target() {
@@ -3564,7 +3611,7 @@ TEST(TransferTest, ResultObjectLocationForStmtExpr) {
});
}
-TEST(TransferTest, ResultObjectLocationForBuiltinBitCastExpr) {
+TEST_P(TransferTest, ResultObjectLocationForBuiltinBitCastExpr) {
std::string Code = R"(
struct S { int i; };
void target(int i) {
@@ -3590,7 +3637,7 @@ TEST(TransferTest, ResultObjectLocationForBuiltinBitCastExpr) {
});
}
-TEST(TransferTest, ResultObjectLocationForAtomicExpr) {
+TEST_P(TransferTest, ResultObjectLocationForAtomicExpr) {
std::string Code = R"(
struct S {};
void target(_Atomic(S) *ptr) {
@@ -3616,7 +3663,7 @@ TEST(TransferTest, ResultObjectLocationForAtomicExpr) {
});
}
-TEST(TransferTest, ResultObjectLocationPropagatesThroughConditionalOperator) {
+TEST_P(TransferTest, ResultObjectLocationPropagatesThroughConditionalOperator) {
std::string Code = R"(
struct A {
A(int);
@@ -3657,7 +3704,7 @@ TEST(TransferTest, ResultObjectLocationPropagatesThroughConditionalOperator) {
});
}
-TEST(TransferTest, ResultObjectLocationDontVisitNestedRecordDecl) {
+TEST_P(TransferTest, ResultObjectLocationDontVisitNestedRecordDecl) {
// This is a crash repro.
// We used to crash because when propagating result objects, we would visit
// nested record and function declarations, but we don't model fields used
@@ -3679,12 +3726,12 @@ TEST(TransferTest, ResultObjectLocationDontVisitNestedRecordDecl) {
ASTContext &ASTCtx) {});
}
-TEST(TransferTest, ResultObjectLocationDontVisitUnevaluatedContexts) {
+TEST_P(TransferTest, ResultObjectLocationDontVisitUnevaluatedContexts) {
// This is a crash repro.
// We used to crash because when propagating result objects, we would visit
// unevaluated contexts, but we don't model fields used only in these.
- auto testFunction = [](llvm::StringRef Code, llvm::StringRef TargetFun) {
+ auto testFunction = [this](llvm::StringRef Code, llvm::StringRef TargetFun) {
runDataflow(
Code,
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
@@ -3733,7 +3780,7 @@ TEST(TransferTest, ResultObjectLocationDontVisitUnevaluatedContexts) {
testFunction(Code, "noexceptTarget");
}
-TEST(TransferTest, StaticCastNoOp) {
+TEST_P(TransferTest, StaticCastNoOp) {
std::string Code = R"(
void target(int Foo) {
int Bar = static_cast<int>(Foo);
@@ -3768,7 +3815,7 @@ TEST(TransferTest, StaticCastNoOp) {
});
}
-TEST(TransferTest, StaticCastBaseToDerived) {
+TEST_P(TransferTest, StaticCastBaseToDerived) {
std::string Code = R"cc(
struct Base {
char C;
@@ -3831,7 +3878,7 @@ TEST(TransferTest, StaticCastBaseToDerived) {
});
}
-TEST(TransferTest, StaticCastBaseToDerivedUnknown) {
+TEST_P(TransferTest, StaticCastBaseToDerivedUnknown) {
// This code used to crash.
std::string Code = R"(
struct Base {};
@@ -3859,7 +3906,8 @@ TEST(TransferTest, StaticCastBaseToDerivedUnknown) {
});
}
-TEST(TransferTest, StaticCastBaseToDerivedWithSyntheticFieldsNoModeledFields) {
+TEST_P(TransferTest,
+ StaticCastBaseToDerivedWithSyntheticFieldsNoModeledFields) {
std::string Code = R"cc(
struct Base {};
struct Derived : public Base {};
@@ -3889,7 +3937,7 @@ TEST(TransferTest, StaticCastBaseToDerivedWithSyntheticFieldsNoModeledFields) {
llvm::Succeeded());
}
-TEST(TransferTest, MultipleConstructionsFromStaticCastsBaseToDerived) {
+TEST_P(TransferTest, MultipleConstructionsFromStaticCastsBaseToDerived) {
std::string Code = R"cc(
struct Base {};
@@ -3935,7 +3983,7 @@ void target(Base* B) {
});
}
-TEST(TransferTest, CopyConstructionOfBaseAfterStaticCastsBaseToDerived) {
+TEST_P(TransferTest, CopyConstructionOfBaseAfterStaticCastsBaseToDerived) {
std::string Code = R"cc(
struct Base {};
@@ -3966,7 +4014,7 @@ void target(Base* B, Base* OtherB) {
});
}
-TEST(TransferTest, ExplicitDerivedToBaseCast) {
+TEST_P(TransferTest, ExplicitDerivedToBaseCast) {
std::string Code = R"cc(
struct Base {};
struct Derived : public Base {};
@@ -3999,7 +4047,7 @@ TEST(TransferTest, ExplicitDerivedToBaseCast) {
});
}
-TEST(TransferTest, ConstructorConversion) {
+TEST_P(TransferTest, ConstructorConversion) {
std::string Code = R"cc(
struct Base {};
struct Derived : public Base {};
@@ -4027,7 +4075,7 @@ TEST(TransferTest, ConstructorConversion) {
});
}
-TEST(TransferTest, UserDefinedConversion) {
+TEST_P(TransferTest, UserDefinedConversion) {
std::string Code = R"cc(
struct To {};
struct From {
@@ -4057,7 +4105,7 @@ TEST(TransferTest, UserDefinedConversion) {
});
}
-TEST(TransferTest, ImplicitUncheckedDerivedToBaseCast) {
+TEST_P(TransferTest, ImplicitUncheckedDerivedToBaseCast) {
std::string Code = R"cc(
struct Base {
void method();
@@ -4086,7 +4134,7 @@ TEST(TransferTest, ImplicitUncheckedDerivedToBaseCast) {
});
}
-TEST(TransferTest, ImplicitDerivedToBaseCast) {
+TEST_P(TransferTest, ImplicitDerivedToBaseCast) {
std::string Code = R"cc(
struct Base {};
struct Derived : public Base {};
@@ -4117,7 +4165,7 @@ TEST(TransferTest, ImplicitDerivedToBaseCast) {
});
}
-TEST(TransferTest, ReinterpretCast) {
+TEST_P(TransferTest, ReinterpretCast) {
std::string Code = R"cc(
struct S {
int I;
@@ -4165,7 +4213,7 @@ TEST(TransferTest, ReinterpretCast) {
});
}
-TEST(TransferTest, IntegralCast) {
+TEST_P(TransferTest, IntegralCast) {
std::string Code = R"(
void target(int Foo) {
long Bar = Foo;
@@ -4184,7 +4232,7 @@ TEST(TransferTest, IntegralCast) {
});
}
-TEST(TransferTest, IntegraltoBooleanCast) {
+TEST_P(TransferTest, IntegraltoBooleanCast) {
std::string Code = R"(
void target(int Foo) {
bool Bar = Foo;
@@ -4204,7 +4252,7 @@ TEST(TransferTest, IntegraltoBooleanCast) {
});
}
-TEST(TransferTest, IntegralToBooleanCastFromBool) {
+TEST_P(TransferTest, IntegralToBooleanCastFromBool) {
std::string Code = R"(
void target(bool Foo) {
int Zab = Foo;
@@ -4224,7 +4272,7 @@ TEST(TransferTest, IntegralToBooleanCastFromBool) {
});
}
-TEST(TransferTest, WidenBoolValueInIntegerVariable) {
+TEST_P(TransferTest, WidenBoolValueInIntegerVariable) {
// This is a crash repro.
// This test sets up a case where we perform widening on an integer variable
// that contains a `BoolValue` for the previous iteration and an
@@ -4248,7 +4296,7 @@ TEST(TransferTest, WidenBoolValueInIntegerVariable) {
ASTContext &) {});
}
-TEST(TransferTest, NullToPointerCast) {
+TEST_P(TransferTest, NullToPointerCast) {
std::string Code = R"(
using my_nullptr_t = decltype(nullptr);
struct Baz {};
@@ -4313,7 +4361,7 @@ TEST(TransferTest, NullToPointerCast) {
});
}
-TEST(TransferTest, PointerToMemberVariable) {
+TEST_P(TransferTest, PointerToMemberVariable) {
std::string Code = R"(
struct S {
int i;
@@ -4336,7 +4384,7 @@ TEST(TransferTest, PointerToMemberVariable) {
});
}
-TEST(TransferTest, PointerToMemberFunction) {
+TEST_P(TransferTest, PointerToMemberFunction) {
std::string Code = R"(
struct S {
void Method();
@@ -4359,7 +4407,7 @@ TEST(TransferTest, PointerToMemberFunction) {
});
}
-TEST(TransferTest, NullToMemberPointerCast) {
+TEST_P(TransferTest, NullToMemberPointerCast) {
std::string Code = R"(
struct Foo {};
void target() {
@@ -4381,7 +4429,7 @@ TEST(TransferTest, NullToMemberPointerCast) {
});
}
-TEST(TransferTest, AddrOfValue) {
+TEST_P(TransferTest, AddrOfValue) {
std::string Code = R"(
void target() {
int Foo;
@@ -4409,7 +4457,7 @@ TEST(TransferTest, AddrOfValue) {
});
}
-TEST(TransferTest, AddrOfReference) {
+TEST_P(TransferTest, AddrOfReference) {
std::string Code = R"(
void target(int *Foo) {
int *Bar = &(*Foo);
@@ -4435,7 +4483,7 @@ TEST(TransferTest, AddrOfReference) {
});
}
-TEST(TransferTest, Preincrement) {
+TEST_P(TransferTest, Preincrement) {
std::string Code = R"(
void target(int I) {
(void)0; // [[before]]
@@ -4461,7 +4509,7 @@ TEST(TransferTest, Preincrement) {
});
}
-TEST(TransferTest, Postincrement) {
+TEST_P(TransferTest, Postincrement) {
std::string Code = R"(
void target(int I) {
(void)0; // [[before]]
@@ -4488,7 +4536,7 @@ TEST(TransferTest, Postincrement) {
// We test just one of the compound assignment operators because we know the
// code for propagating the storage location is shared among all of them.
-TEST(TransferTest, AddAssign) {
+TEST_P(TransferTest, AddAssign) {
std::string Code = R"(
void target(int I) {
int &IRef = (I += 1);
@@ -4506,7 +4554,7 @@ TEST(TransferTest, AddAssign) {
});
}
-TEST(TransferTest, CannotAnalyzeFunctionTemplate) {
+TEST_P(TransferTest, CannotAnalyzeFunctionTemplate) {
std::string Code = R"(
template <typename T>
void target() {}
@@ -4516,7 +4564,7 @@ TEST(TransferTest, CannotAnalyzeFunctionTemplate) {
llvm::FailedWithMessage("Cannot analyze templated declarations"));
}
-TEST(TransferTest, CannotAnalyzeMethodOfClassTemplate) {
+TEST_P(TransferTest, CannotAnalyzeMethodOfClassTemplate) {
std::string Code = R"(
template <typename T>
struct A {
@@ -4528,7 +4576,7 @@ TEST(TransferTest, CannotAnalyzeMethodOfClassTemplate) {
llvm::FailedWithMessage("Cannot analyze templated declarations"));
}
-TEST(TransferTest, VarDeclInitAssignConditionalOperator) {
+TEST_P(TransferTest, VarDeclInitAssignConditionalOperator) {
std::string Code = R"(
struct A {
int i;
@@ -4562,7 +4610,7 @@ TEST(TransferTest, VarDeclInitAssignConditionalOperator) {
});
}
-TEST(TransferTest, VarDeclInitReferenceAssignConditionalOperator) {
+TEST_P(TransferTest, VarDeclInitReferenceAssignConditionalOperator) {
std::string Code = R"(
struct A {
int i;
@@ -4596,7 +4644,7 @@ TEST(TransferTest, VarDeclInitReferenceAssignConditionalOperator) {
});
}
-TEST(TransferTest, VarDeclInDoWhile) {
+TEST_P(TransferTest, VarDeclInDoWhile) {
std::string Code = R"(
void target(int *Foo) {
do {
@@ -4634,7 +4682,7 @@ TEST(TransferTest, VarDeclInDoWhile) {
});
}
-TEST(TransferTest, UnreachableAfterWhileTrue) {
+TEST_P(TransferTest, UnreachableAfterWhileTrue) {
std::string Code = R"(
void target() {
while (true) {}
@@ -4652,7 +4700,7 @@ TEST(TransferTest, UnreachableAfterWhileTrue) {
});
}
-TEST(TransferTest, AggregateInitialization) {
+TEST_P(TransferTest, AggregateInitialization) {
std::string BracesCode = R"(
struct A {
int Foo;
@@ -4743,7 +4791,7 @@ TEST(TransferTest, AggregateInitialization) {
}
}
-TEST(TransferTest, AggregateInitializationReferenceField) {
+TEST_P(TransferTest, AggregateInitializationReferenceField) {
std::string Code = R"(
struct S {
int &RefField;
@@ -4769,7 +4817,7 @@ TEST(TransferTest, AggregateInitializationReferenceField) {
});
}
-TEST(TransferTest, AggregateInitialization_NotExplicitlyInitializedField) {
+TEST_P(TransferTest, AggregateInitialization_NotExplicitlyInitializedField) {
std::string Code = R"(
struct S {
int i1;
@@ -4802,7 +4850,7 @@ TEST(TransferTest, AggregateInitialization_NotExplicitlyInitializedField) {
});
}
-TEST(TransferTest, AggregateInitializationFunctionPointer) {
+TEST_P(TransferTest, AggregateInitializationFunctionPointer) {
// This is a repro for an assertion failure.
// nullptr takes on the type of a const function pointer, but its type was
// asserted to be equal to the *unqualified* type of Field, which no longer
@@ -4822,7 +4870,7 @@ TEST(TransferTest, AggregateInitializationFunctionPointer) {
ASTContext &ASTCtx) {});
}
-TEST(TransferTest, AssignToUnionMember) {
+TEST_P(TransferTest, AssignToUnionMember) {
std::string Code = R"(
union A {
int Foo;
@@ -4872,7 +4920,7 @@ TEST(TransferTest, AssignToUnionMember) {
});
}
-TEST(TransferTest, AssignFromBoolLiteral) {
+TEST_P(TransferTest, AssignFromBoolLiteral) {
std::string Code = R"(
void target() {
bool Foo = true;
@@ -4906,7 +4954,7 @@ TEST(TransferTest, AssignFromBoolLiteral) {
});
}
-TEST(TransferTest, AssignFromCompositeBoolExpression) {
+TEST_P(TransferTest, AssignFromCompositeBoolExpression) {
{
std::string Code = R"(
void target(bool Foo, bool Bar, bool Qux) {
@@ -5057,7 +5105,7 @@ TEST(TransferTest, AssignFromCompositeBoolExpression) {
}
}
-TEST(TransferTest, AssignFromBoolNegation) {
+TEST_P(TransferTest, AssignFromBoolNegation) {
std::string Code = R"(
void target() {
bool Foo = true;
@@ -5090,7 +5138,7 @@ TEST(TransferTest, AssignFromBoolNegation) {
});
}
-TEST(TransferTest, BuiltinExpect) {
+TEST_P(TransferTest, BuiltinExpect) {
std::string Code = R"(
void target(long Foo) {
long Bar = __builtin_expect(Foo, true);
@@ -5117,7 +5165,7 @@ TEST(TransferTest, BuiltinExpect) {
// `__builtin_expect` takes and returns a `long` argument, so other types
// involve casts. This verifies that we identify the input and output in that
// case.
-TEST(TransferTest, BuiltinExpectBoolArg) {
+TEST_P(TransferTest, BuiltinExpectBoolArg) {
std::string Code = R"(
void target(bool Foo) {
bool Bar = __builtin_expect(Foo, true);
@@ -5141,7 +5189,7 @@ TEST(TransferTest, BuiltinExpectBoolArg) {
});
}
-TEST(TransferTest, BuiltinUnreachable) {
+TEST_P(TransferTest, BuiltinUnreachable) {
std::string Code = R"(
void target(bool Foo) {
bool Bar = false;
@@ -5173,7 +5221,7 @@ TEST(TransferTest, BuiltinUnreachable) {
});
}
-TEST(TransferTest, BuiltinTrap) {
+TEST_P(TransferTest, BuiltinTrap) {
std::string Code = R"(
void target(bool Foo) {
bool Bar = false;
@@ -5204,7 +5252,7 @@ TEST(TransferTest, BuiltinTrap) {
});
}
-TEST(TransferTest, BuiltinDebugTrap) {
+TEST_P(TransferTest, BuiltinDebugTrap) {
std::string Code = R"(
void target(bool Foo) {
bool Bar = false;
@@ -5234,7 +5282,7 @@ TEST(TransferTest, BuiltinDebugTrap) {
});
}
-TEST(TransferTest, StaticIntSingleVarDecl) {
+TEST_P(TransferTest, StaticIntSingleVarDecl) {
std::string Code = R"(
void target() {
static int Foo;
@@ -5259,7 +5307,7 @@ TEST(TransferTest, StaticIntSingleVarDecl) {
});
}
-TEST(TransferTest, StaticIntGroupVarDecl) {
+TEST_P(TransferTest, StaticIntGroupVarDecl) {
std::string Code = R"(
void target() {
static int Foo, Bar;
@@ -5296,7 +5344,7 @@ TEST(TransferTest, StaticIntGroupVarDecl) {
});
}
-TEST(TransferTest, GlobalIntVarDecl) {
+TEST_P(TransferTest, GlobalIntVarDecl) {
std::string Code = R"(
static int Foo;
@@ -5325,7 +5373,7 @@ TEST(TransferTest, GlobalIntVarDecl) {
});
}
-TEST(TransferTest, StaticMemberIntVarDecl) {
+TEST_P(TransferTest, StaticMemberIntVarDecl) {
std::string Code = R"(
struct A {
static int Foo;
@@ -5356,7 +5404,7 @@ TEST(TransferTest, StaticMemberIntVarDecl) {
});
}
-TEST(TransferTest, StaticMemberRefVarDecl) {
+TEST_P(TransferTest, StaticMemberRefVarDecl) {
std::string Code = R"(
struct A {
static int &Foo;
@@ -5387,7 +5435,7 @@ TEST(TransferTest, StaticMemberRefVarDecl) {
});
}
-TEST(TransferTest, AssignMemberBeforeCopy) {
+TEST_P(TransferTest, AssignMemberBeforeCopy) {
std::string Code = R"(
struct A {
int Foo;
@@ -5429,7 +5477,7 @@ TEST(TransferTest, AssignMemberBeforeCopy) {
});
}
-TEST(TransferTest, BooleanEquality) {
+TEST_P(TransferTest, BooleanEquality) {
std::string Code = R"(
void target(bool Bar) {
bool Foo = true;
@@ -5463,7 +5511,7 @@ TEST(TransferTest, BooleanEquality) {
});
}
-TEST(TransferTest, BooleanInequality) {
+TEST_P(TransferTest, BooleanInequality) {
std::string Code = R"(
void target(bool Bar) {
bool Foo = true;
@@ -5497,7 +5545,7 @@ TEST(TransferTest, BooleanInequality) {
});
}
-TEST(TransferTest, PointerEquality) {
+TEST_P(TransferTest, PointerEquality) {
std::string Code = R"cc(
void target() {
int i = 0;
@@ -5557,7 +5605,7 @@ TEST(TransferTest, PointerEquality) {
});
}
-TEST(TransferTest, PointerEqualityUnionMembers) {
+TEST_P(TransferTest, PointerEqualityUnionMembers) {
std::string Code = R"cc(
union U {
int i1;
@@ -5585,7 +5633,7 @@ TEST(TransferTest, PointerEqualityUnionMembers) {
});
}
-TEST(TransferTest, IntegerLiteralEquality) {
+TEST_P(TransferTest, IntegerLiteralEquality) {
std::string Code = R"(
void target() {
bool equal = (42 == 42);
@@ -5604,7 +5652,7 @@ TEST(TransferTest, IntegerLiteralEquality) {
});
}
-TEST(TransferTest, UnsupportedValueEquality) {
+TEST_P(TransferTest, UnsupportedValueEquality) {
std::string Code = R"(
// An explicitly unsupported type by the framework.
enum class EC {
@@ -5639,7 +5687,7 @@ TEST(TransferTest, UnsupportedValueEquality) {
});
}
-TEST(TransferTest, CorrelatedBranches) {
+TEST_P(TransferTest, CorrelatedBranches) {
std::string Code = R"(
void target(bool B, bool C) {
if (B) {
@@ -5682,14 +5730,22 @@ TEST(TransferTest, CorrelatedBranches) {
}
{
+ // `B` is reassigned inside `if (C) { B = true; }` and joined with
+ // `B == false` from the false branch. Proving `C` inside the
+ // subsequent `if (B)` at `p2` requires tracking a relation between
+ // `B` and `C`: `B == true` implies `C`.
const Environment &Env = getEnvironmentAtAnnotation(Results, "p2");
auto &CVal = getFormula(*CDecl, Env);
- EXPECT_TRUE(Env.proves(CVal));
+ if (GetParam() == BoolModelKind::SAT) {
+ EXPECT_TRUE(Env.proves(CVal));
+ } else {
+ EXPECT_FALSE(Env.proves(CVal));
+ }
}
});
}
-TEST(TransferTest, LoopWithAssignmentConverges) {
+TEST_P(TransferTest, LoopWithAssignmentConverges) {
std::string Code = R"(
bool foo();
@@ -5720,7 +5776,37 @@ TEST(TransferTest, LoopWithAssignmentConverges) {
});
}
-TEST(TransferTest, LoopWithStagedAssignments) {
+TEST_P(TransferTest, LoopMergeBlockUpdatesBoolModelWhenLocToValUnchanged) {
+ std::string Code = R"(
+ void target(bool Cond, bool A) {
+ bool First = true;
+ while (Cond) {
+ if (First) {
+ First = false;
+ if (!A) continue;
+ }
+ First = false;
+ /*[[p]]*/
+ }
+ }
+ )";
+ runDataflow(
+ Code,
+ [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
+ ASTContext &ASTCtx) {
+ ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
+ const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
+
+ const ValueDecl *ADecl = findValueDecl(ASTCtx, "A");
+ ASSERT_THAT(ADecl, NotNull());
+
+ if (GetParam() == BoolModelKind::Simple) {
+ EXPECT_FALSE(Env.proves(getFormula(*ADecl, Env)));
+ }
+ });
+}
+
+TEST_P(TransferTest, LoopWithStagedAssignments) {
std::string Code = R"(
bool foo();
@@ -5757,7 +5843,7 @@ TEST(TransferTest, LoopWithStagedAssignments) {
});
}
-TEST(TransferTest, LoopWithReferenceAssignmentConverges) {
+TEST_P(TransferTest, LoopWithReferenceAssignmentConverges) {
std::string Code = R"(
bool &foo();
@@ -5787,7 +5873,7 @@ TEST(TransferTest, LoopWithReferenceAssignmentConverges) {
});
}
-TEST(TransferTest, LoopWithStructReferenceAssignmentConverges) {
+TEST_P(TransferTest, LoopWithStructReferenceAssignmentConverges) {
std::string Code = R"(
struct Lookup {
int x;
@@ -5840,7 +5926,7 @@ TEST(TransferTest, LoopWithStructReferenceAssignmentConverges) {
});
}
-TEST(TransferTest, LoopDereferencingChangingPointerConverges) {
+TEST_P(TransferTest, LoopDereferencingChangingPointerConverges) {
std::string Code = R"cc(
bool some_condition();
@@ -5858,7 +5944,7 @@ TEST(TransferTest, LoopDereferencingChangingPointerConverges) {
ASSERT_THAT_ERROR(checkDataflowWithNoopAnalysis(Code), llvm::Succeeded());
}
-TEST(TransferTest, LoopDereferencingChangingRecordPointerConverges) {
+TEST_P(TransferTest, LoopDereferencingChangingRecordPointerConverges) {
std::string Code = R"cc(
struct Lookup {
int x;
@@ -5880,7 +5966,7 @@ TEST(TransferTest, LoopDereferencingChangingRecordPointerConverges) {
ASSERT_THAT_ERROR(checkDataflowWithNoopAnalysis(Code), llvm::Succeeded());
}
-TEST(TransferTest, LoopWithShortCircuitedConditionConverges) {
+TEST_P(TransferTest, LoopWithShortCircuitedConditionConverges) {
std::string Code = R"cc(
bool foo();
@@ -5894,7 +5980,7 @@ TEST(TransferTest, LoopWithShortCircuitedConditionConverges) {
ASSERT_THAT_ERROR(checkDataflowWithNoopAnalysis(Code), llvm::Succeeded());
}
-TEST(TransferTest, LoopCanProveInvariantForBoolean) {
+TEST_P(TransferTest, LoopCanProveInvariantForBoolean) {
// Check that we can prove `b` is always false in the loop.
// This test exercises the logic in `widenDistinctValues()` that preserves
// information if the boolean can be proved to be either true or false in both
@@ -5922,7 +6008,7 @@ TEST(TransferTest, LoopCanProveInvariantForBoolean) {
});
}
-TEST(TransferTest, DoesNotCrashOnUnionThisExpr) {
+TEST_P(TransferTest, DoesNotCrashOnUnionThisExpr) {
std::string Code = R"cc(
union Union {
int A;
@@ -5944,7 +6030,7 @@ TEST(TransferTest, DoesNotCrashOnUnionThisExpr) {
LangStandard::lang_cxx17, /*ApplyBuiltinTransfer=*/true, "operator=");
}
-TEST(TransferTest, DoesNotCrashOnNullChildren) {
+TEST_P(TransferTest, DoesNotCrashOnNullChildren) {
std::string Code = (CoroutineLibrary + R"cc(
task target() noexcept {
co_return;
@@ -5960,7 +6046,7 @@ TEST(TransferTest, DoesNotCrashOnNullChildren) {
LangStandard::lang_cxx20, /*ApplyBuiltinTransfer=*/true);
}
-TEST(TransferTest, StructuredBindingAssignFromStructIntMembersToRefs) {
+TEST_P(TransferTest, StructuredBindingAssignFromStructIntMembersToRefs) {
std::string Code = R"(
struct A {
int Foo;
@@ -6020,7 +6106,7 @@ TEST(TransferTest, StructuredBindingAssignFromStructIntMembersToRefs) {
});
}
-TEST(TransferTest, StructuredBindingAssignFromStructRefMembersToRefs) {
+TEST_P(TransferTest, StructuredBindingAssignFromStructRefMembersToRefs) {
std::string Code = R"(
struct A {
int &Foo;
@@ -6079,7 +6165,7 @@ TEST(TransferTest, StructuredBindingAssignFromStructRefMembersToRefs) {
});
}
-TEST(TransferTest, StructuredBindingAssignFromStructIntMembersToInts) {
+TEST_P(TransferTest, StructuredBindingAssignFromStructIntMembersToInts) {
std::string Code = R"(
struct A {
int Foo;
@@ -6139,7 +6225,7 @@ TEST(TransferTest, StructuredBindingAssignFromStructIntMembersToInts) {
});
}
-TEST(TransferTest, StructuredBindingAssignFromTupleLikeType) {
+TEST_P(TransferTest, StructuredBindingAssignFromTupleLikeType) {
std::string Code = R"(
namespace std {
using size_t = int;
@@ -6220,7 +6306,7 @@ TEST(TransferTest, StructuredBindingAssignFromTupleLikeType) {
});
}
-TEST(TransferTest, StructuredBindingAssignRefFromTupleLikeType) {
+TEST_P(TransferTest, StructuredBindingAssignRefFromTupleLikeType) {
std::string Code = R"(
namespace std {
using size_t = int;
@@ -6302,7 +6388,7 @@ TEST(TransferTest, StructuredBindingAssignRefFromTupleLikeType) {
});
}
-TEST(TransferTest, BinaryOperatorComma) {
+TEST_P(TransferTest, BinaryOperatorComma) {
std::string Code = R"(
void target(int Foo, int Bar) {
int &Baz = (Foo, Bar);
@@ -6330,7 +6416,7 @@ TEST(TransferTest, BinaryOperatorComma) {
});
}
-TEST(TransferTest, ConditionalOperatorValue) {
+TEST_P(TransferTest, ConditionalOperatorValue) {
std::string Code = R"(
void target(bool Cond, bool B1, bool B2) {
bool JoinSame = Cond ? B1 : B1;
@@ -6364,7 +6450,7 @@ TEST(TransferTest, ConditionalOperatorValue) {
});
}
-TEST(TransferTest, ConditionalOperatorValuesTested) {
+TEST_P(TransferTest, ConditionalOperatorValuesTested) {
// We should be able to show that the result of the conditional operator,
// JoinResultMustBeB1, must be equal to B1, because the condition is checking
// `B1 == B2` and selecting B1 on the false branch, or B2 on the true branch.
@@ -6393,17 +6479,20 @@ TEST(TransferTest, ConditionalOperatorValuesTested) {
auto &JoinResultMustBeB2 =
getValueForDecl<BoolValue>(ASTCtx, Env, "JoinResultMustBeB2");
+ // Proving `((B1 == B2) ? B2 : B1) == B1` for symbolic `B1` and `B2`
+ // requires relational/case-splitting reasoning across the conditional
+ // join, which `SimpleBoolModel` does not track.
const Formula &MustBeB1_Eq_B1 =
Env.arena().makeEquals(JoinResultMustBeB1.formula(), B1.formula());
- EXPECT_TRUE(Env.proves(MustBeB1_Eq_B1));
+ EXPECT_EQ(Env.proves(MustBeB1_Eq_B1), GetParam() == BoolModelKind::SAT);
const Formula &MustBeB2_Eq_B2 =
Env.arena().makeEquals(JoinResultMustBeB2.formula(), B2.formula());
- EXPECT_TRUE(Env.proves(MustBeB2_Eq_B2));
+ EXPECT_EQ(Env.proves(MustBeB2_Eq_B2), GetParam() == BoolModelKind::SAT);
});
}
-TEST(TransferTest, ConditionalOperatorLocation) {
+TEST_P(TransferTest, ConditionalOperatorLocation) {
std::string Code = R"(
void target(bool Cond, int I1, int I2) {
int &JoinSame = Cond ? I1 : I1;
@@ -6430,7 +6519,7 @@ TEST(TransferTest, ConditionalOperatorLocation) {
});
}
-TEST(TransferTest, ConditionalOperatorLocationUpdatedAfter) {
+TEST_P(TransferTest, ConditionalOperatorLocationUpdatedAfter) {
// We don't currently model a Conditional Operator with an LValue result
// as having aliases to the LHS and RHS (if it isn't just the same LValue
// on both sides). We also don't model that the update "may" happen
@@ -6490,7 +6579,7 @@ TEST(TransferTest, ConditionalOperatorLocationUpdatedAfter) {
});
}
-TEST(TransferTest, ConditionalOperatorOnConstantExpr) {
+TEST_P(TransferTest, ConditionalOperatorOnConstantExpr) {
// This is a regression test: We used to crash when a `ConstantExpr` was used
// in the branches of a conditional operator.
std::string Code = R"cc(
@@ -6522,7 +6611,7 @@ TEST(TransferTest, ConditionalOperatorOnConstantExpr) {
LangStandard::lang_cxx20);
}
-TEST(TransferTest, IfStmtBranchExtendsFlowCondition) {
+TEST_P(TransferTest, IfStmtBranchExtendsFlowCondition) {
std::string Code = R"(
void target(bool Foo) {
if (Foo) {
@@ -6555,7 +6644,7 @@ TEST(TransferTest, IfStmtBranchExtendsFlowCondition) {
});
}
-TEST(TransferTest, WhileStmtBranchExtendsFlowCondition) {
+TEST_P(TransferTest, WhileStmtBranchExtendsFlowCondition) {
std::string Code = R"(
void target(bool Foo) {
while (Foo) {
@@ -6589,7 +6678,46 @@ TEST(TransferTest, WhileStmtBranchExtendsFlowCondition) {
});
}
-TEST(TransferTest, DoWhileStmtBranchExtendsFlowCondition) {
+TEST_P(TransferTest, DoWhileStmtBranchExtendsFlowCondition) {
+ std::string Code = R"(
+ void target(bool Bar) {
+ bool Foo = true;
+ do {
+ (void)0;
+ // [[loop_body]]
+ Foo = Bar;
+ } while (Foo);
+ (void)0;
+ // [[after_loop]]
+ }
+ )";
+ runDataflow(
+ Code,
+ [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
+ ASTContext &ASTCtx) {
+ ASSERT_THAT(Results.keys(),
+ UnorderedElementsAre("loop_body", "after_loop"));
+ const Environment &LoopBodyEnv =
+ getEnvironmentAtAnnotation(Results, "loop_body");
+ const Environment &AfterLoopEnv =
+ getEnvironmentAtAnnotation(Results, "after_loop");
+
+ const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
+ ASSERT_THAT(FooDecl, NotNull());
+
+ auto &LoopBodyFooVal = getFormula(*FooDecl, LoopBodyEnv);
+ EXPECT_TRUE(LoopBodyEnv.proves(LoopBodyFooVal));
+
+ auto &AfterLoopFooVal = getFormula(*FooDecl, AfterLoopEnv);
+ EXPECT_TRUE(
+ AfterLoopEnv.proves(AfterLoopEnv.arena().makeNot(AfterLoopFooVal)));
+ });
+}
+
+// On the first iteration, `Bar` is `true` and `Foo` is unconstrained; on
+// subsequent iterations via the backedge, `Bar` is `false` and `Foo` is `true`.
+// Proving the loop body invariant `Bar || Foo` requires SAT solving.
+TEST_P(TransferTest, DoWhileStmtDisjunctiveLoopInvariant) {
std::string Code = R"(
void target(bool Foo) {
bool Bar = true;
@@ -6620,10 +6748,10 @@ TEST(TransferTest, DoWhileStmtBranchExtendsFlowCondition) {
const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
ASSERT_THAT(BarDecl, NotNull());
- auto &LoopBodyFooVal= getFormula(*FooDecl, LoopBodyEnv);
+ auto &LoopBodyFooVal = getFormula(*FooDecl, LoopBodyEnv);
auto &LoopBodyBarVal = getFormula(*BarDecl, LoopBodyEnv);
- EXPECT_TRUE(
- LoopBodyEnv.proves(A.makeOr(LoopBodyBarVal, LoopBodyFooVal)));
+ EXPECT_EQ(LoopBodyEnv.proves(A.makeOr(LoopBodyBarVal, LoopBodyFooVal)),
+ GetParam() == BoolModelKind::SAT);
auto &AfterLoopFooVal = getFormula(*FooDecl, AfterLoopEnv);
auto &AfterLoopBarVal = getFormula(*BarDecl, AfterLoopEnv);
@@ -6632,7 +6760,7 @@ TEST(TransferTest, DoWhileStmtBranchExtendsFlowCondition) {
});
}
-TEST(TransferTest, ForStmtBranchExtendsFlowCondition) {
+TEST_P(TransferTest, ForStmtBranchExtendsFlowCondition) {
std::string Code = R"(
void target(bool Foo) {
for (; Foo;) {
@@ -6666,7 +6794,7 @@ TEST(TransferTest, ForStmtBranchExtendsFlowCondition) {
});
}
-TEST(TransferTest, ForStmtBranchWithoutConditionDoesNotExtendFlowCondition) {
+TEST_P(TransferTest, ForStmtBranchWithoutConditionDoesNotExtendFlowCondition) {
std::string Code = R"(
void target(bool Foo) {
for (;;) {
@@ -6691,7 +6819,7 @@ TEST(TransferTest, ForStmtBranchWithoutConditionDoesNotExtendFlowCondition) {
});
}
-TEST(TransferTest, ContextSensitiveOptionDisabled) {
+TEST_P(TransferTest, ContextSensitiveOptionDisabled) {
std::string Code = R"(
bool GiveBool();
void SetBool(bool &Var) { Var = true; }
@@ -6719,7 +6847,7 @@ TEST(TransferTest, ContextSensitiveOptionDisabled) {
{BuiltinOptions{/*.ContextSensitiveOpts=*/std::nullopt}});
}
-TEST(TransferTest, ContextSensitiveReturnReference) {
+TEST_P(TransferTest, ContextSensitiveReturnReference) {
std::string Code = R"(
class S {};
S& target(bool b, S &s) {
@@ -6745,7 +6873,7 @@ TEST(TransferTest, ContextSensitiveReturnReference) {
}
// This test is a regression test, based on a real crash.
-TEST(TransferTest, ContextSensitiveReturnReferenceWithConditionalOperator) {
+TEST_P(TransferTest, ContextSensitiveReturnReferenceWithConditionalOperator) {
std::string Code = R"(
class S {};
S& target(bool b, S &s) {
@@ -6774,7 +6902,7 @@ TEST(TransferTest, ContextSensitiveReturnReferenceWithConditionalOperator) {
{BuiltinOptions{ContextSensitiveOptions{}}});
}
-TEST(TransferTest, ContextSensitiveReturnOneOfTwoReferences) {
+TEST_P(TransferTest, ContextSensitiveReturnOneOfTwoReferences) {
std::string Code = R"(
class S {};
S &callee(bool b, S &s1_parm, S &s2_parm) {
@@ -6825,7 +6953,7 @@ TEST(TransferTest, ContextSensitiveReturnOneOfTwoReferences) {
{BuiltinOptions{ContextSensitiveOptions{}}});
}
-TEST(TransferTest, ContextSensitiveDepthZero) {
+TEST_P(TransferTest, ContextSensitiveDepthZero) {
std::string Code = R"(
bool GiveBool();
void SetBool(bool &Var) { Var = true; }
@@ -6853,7 +6981,7 @@ TEST(TransferTest, ContextSensitiveDepthZero) {
{BuiltinOptions{ContextSensitiveOptions{/*.Depth=*/0}}});
}
-TEST(TransferTest, ContextSensitiveSetTrue) {
+TEST_P(TransferTest, ContextSensitiveSetTrue) {
std::string Code = R"(
bool GiveBool();
void SetBool(bool &Var) { Var = true; }
@@ -6880,7 +7008,7 @@ TEST(TransferTest, ContextSensitiveSetTrue) {
{BuiltinOptions{ContextSensitiveOptions{}}});
}
-TEST(TransferTest, ContextSensitiveSetFalse) {
+TEST_P(TransferTest, ContextSensitiveSetFalse) {
std::string Code = R"(
bool GiveBool();
void SetBool(bool &Var) { Var = false; }
@@ -6907,7 +7035,7 @@ TEST(TransferTest, ContextSensitiveSetFalse) {
{BuiltinOptions{ContextSensitiveOptions{}}});
}
-TEST(TransferTest, ContextSensitiveSetBothTrueAndFalse) {
+TEST_P(TransferTest, ContextSensitiveSetBothTrueAndFalse) {
std::string Code = R"(
bool GiveBool();
void SetBool(bool &Var, bool Val) { Var = Val; }
@@ -6945,7 +7073,7 @@ TEST(TransferTest, ContextSensitiveSetBothTrueAndFalse) {
{BuiltinOptions{ContextSensitiveOptions{}}});
}
-TEST(TransferTest, ContextSensitiveSetTwoLayersDepthOne) {
+TEST_P(TransferTest, ContextSensitiveSetTwoLayersDepthOne) {
std::string Code = R"(
bool GiveBool();
void SetBool1(bool &Var) { Var = true; }
@@ -6974,7 +7102,7 @@ TEST(TransferTest, ContextSensitiveSetTwoLayersDepthOne) {
{BuiltinOptions{ContextSensitiveOptions{/*.Depth=*/1}}});
}
-TEST(TransferTest, ContextSensitiveSetTwoLayersDepthTwo) {
+TEST_P(TransferTest, ContextSensitiveSetTwoLayersDepthTwo) {
std::string Code = R"(
bool GiveBool();
void SetBool1(bool &Var) { Var = true; }
@@ -7002,7 +7130,7 @@ TEST(TransferTest, ContextSensitiveSetTwoLayersDepthTwo) {
{BuiltinOptions{ContextSensitiveOptions{/*.Depth=*/2}}});
}
-TEST(TransferTest, ContextSensitiveSetThreeLayersDepthTwo) {
+TEST_P(TransferTest, ContextSensitiveSetThreeLayersDepthTwo) {
std::string Code = R"(
bool GiveBool();
void SetBool1(bool &Var) { Var = true; }
@@ -7032,7 +7160,7 @@ TEST(TransferTest, ContextSensitiveSetThreeLayersDepthTwo) {
{BuiltinOptions{ContextSensitiveOptions{/*.Depth=*/2}}});
}
-TEST(TransferTest, ContextSensitiveSetThreeLayersDepthThree) {
+TEST_P(TransferTest, ContextSensitiveSetThreeLayersDepthThree) {
std::string Code = R"(
bool GiveBool();
void SetBool1(bool &Var) { Var = true; }
@@ -7061,7 +7189,7 @@ TEST(TransferTest, ContextSensitiveSetThreeLayersDepthThree) {
{BuiltinOptions{ContextSensitiveOptions{/*.Depth=*/3}}});
}
-TEST(TransferTest, ContextSensitiveMutualRecursion) {
+TEST_P(TransferTest, ContextSensitiveMutualRecursion) {
std::string Code = R"(
bool Pong(bool X, bool Y);
@@ -7105,7 +7233,7 @@ TEST(TransferTest, ContextSensitiveMutualRecursion) {
{BuiltinOptions{ContextSensitiveOptions{/*.Depth=*/4}}});
}
-TEST(TransferTest, ContextSensitiveSetMultipleLines) {
+TEST_P(TransferTest, ContextSensitiveSetMultipleLines) {
std::string Code = R"(
void SetBools(bool &Var1, bool &Var2) {
Var1 = true;
@@ -7143,7 +7271,7 @@ TEST(TransferTest, ContextSensitiveSetMultipleLines) {
{BuiltinOptions{ContextSensitiveOptions{}}});
}
-TEST(TransferTest, ContextSensitiveSetMultipleBlocks) {
+TEST_P(TransferTest, ContextSensitiveSetMultipleBlocks) {
std::string Code = R"(
void IfCond(bool Cond, bool &Then, bool &Else) {
if (Cond) {
@@ -7185,7 +7313,7 @@ TEST(TransferTest, ContextSensitiveSetMultipleBlocks) {
{BuiltinOptions{ContextSensitiveOptions{}}});
}
-TEST(TransferTest, ContextSensitiveReturnVoid) {
+TEST_P(TransferTest, ContextSensitiveReturnVoid) {
std::string Code = R"(
void Noop() { return; }
@@ -7204,7 +7332,7 @@ TEST(TransferTest, ContextSensitiveReturnVoid) {
{BuiltinOptions{ContextSensitiveOptions{}}});
}
-TEST(TransferTest, ContextSensitiveReturnTrue) {
+TEST_P(TransferTest, ContextSensitiveReturnTrue) {
std::string Code = R"(
bool GiveBool() { return true; }
@@ -7229,7 +7357,7 @@ TEST(TransferTest, ContextSensitiveReturnTrue) {
{BuiltinOptions{ContextSensitiveOptions{}}});
}
-TEST(TransferTest, ContextSensitiveReturnFalse) {
+TEST_P(TransferTest, ContextSensitiveReturnFalse) {
std::string Code = R"(
bool GiveBool() { return false; }
@@ -7254,7 +7382,7 @@ TEST(TransferTest, ContextSensitiveReturnFalse) {
{BuiltinOptions{ContextSensitiveOptions{}}});
}
-TEST(TransferTest, ContextSensitiveReturnArg) {
+TEST_P(TransferTest, ContextSensitiveReturnArg) {
std::string Code = R"(
bool GiveBool();
bool GiveBack(bool Arg) { return Arg; }
@@ -7282,7 +7410,7 @@ TEST(TransferTest, ContextSensitiveReturnArg) {
{BuiltinOptions{ContextSensitiveOptions{}}});
}
-TEST(TransferTest, ContextSensitiveReturnInt) {
+TEST_P(TransferTest, ContextSensitiveReturnInt) {
std::string Code = R"(
int identity(int x) { return x; }
@@ -7301,7 +7429,7 @@ TEST(TransferTest, ContextSensitiveReturnInt) {
{BuiltinOptions{ContextSensitiveOptions{}}});
}
-TEST(TransferTest, ContextSensitiveReturnRecord) {
+TEST_P(TransferTest, ContextSensitiveReturnRecord) {
std::string Code = R"(
struct S {
bool B;
@@ -7334,7 +7462,7 @@ TEST(TransferTest, ContextSensitiveReturnRecord) {
{BuiltinOptions{ContextSensitiveOptions{}}});
}
-TEST(TransferTest, ContextSensitiveReturnSelfReferentialRecord) {
+TEST_P(TransferTest, ContextSensitiveReturnSelfReferentialRecord) {
std::string Code = R"(
struct S {
S() { self = this; }
@@ -7366,7 +7494,7 @@ TEST(TransferTest, ContextSensitiveReturnSelfReferentialRecord) {
{BuiltinOptions{ContextSensitiveOptions{}}});
}
-TEST(TransferTest, ContextSensitiveMethodLiteral) {
+TEST_P(TransferTest, ContextSensitiveMethodLiteral) {
std::string Code = R"(
class MyClass {
public:
@@ -7395,7 +7523,7 @@ TEST(TransferTest, ContextSensitiveMethodLiteral) {
{BuiltinOptions{ContextSensitiveOptions{}}});
}
-TEST(TransferTest, ContextSensitiveMethodGetter) {
+TEST_P(TransferTest, ContextSensitiveMethodGetter) {
std::string Code = R"(
class MyClass {
public:
@@ -7427,7 +7555,7 @@ TEST(TransferTest, ContextSensitiveMethodGetter) {
{BuiltinOptions{ContextSensitiveOptions{}}});
}
-TEST(TransferTest, ContextSensitiveMethodSetter) {
+TEST_P(TransferTest, ContextSensitiveMethodSetter) {
std::string Code = R"(
class MyClass {
public:
@@ -7459,7 +7587,7 @@ TEST(TransferTest, ContextSensitiveMethodSetter) {
{BuiltinOptions{ContextSensitiveOptions{}}});
}
-TEST(TransferTest, ContextSensitiveMethodGetterAndSetter) {
+TEST_P(TransferTest, ContextSensitiveMethodGetterAndSetter) {
std::string Code = R"(
class MyClass {
public:
@@ -7493,8 +7621,7 @@ TEST(TransferTest, ContextSensitiveMethodGetterAndSetter) {
{BuiltinOptions{ContextSensitiveOptions{}}});
}
-
-TEST(TransferTest, ContextSensitiveMethodTwoLayersVoid) {
+TEST_P(TransferTest, ContextSensitiveMethodTwoLayersVoid) {
std::string Code = R"(
class MyClass {
public:
@@ -7528,7 +7655,7 @@ TEST(TransferTest, ContextSensitiveMethodTwoLayersVoid) {
{BuiltinOptions{ContextSensitiveOptions{}}});
}
-TEST(TransferTest, ContextSensitiveMethodTwoLayersReturn) {
+TEST_P(TransferTest, ContextSensitiveMethodTwoLayersReturn) {
std::string Code = R"(
class MyClass {
public:
@@ -7562,7 +7689,7 @@ TEST(TransferTest, ContextSensitiveMethodTwoLayersReturn) {
{BuiltinOptions{ContextSensitiveOptions{}}});
}
-TEST(TransferTest, ContextSensitiveConstructorBody) {
+TEST_P(TransferTest, ContextSensitiveConstructorBody) {
std::string Code = R"(
class MyClass {
public:
@@ -7593,7 +7720,7 @@ TEST(TransferTest, ContextSensitiveConstructorBody) {
{BuiltinOptions{ContextSensitiveOptions{}}});
}
-TEST(TransferTest, ContextSensitiveConstructorInitializer) {
+TEST_P(TransferTest, ContextSensitiveConstructorInitializer) {
std::string Code = R"(
class MyClass {
public:
@@ -7624,7 +7751,7 @@ TEST(TransferTest, ContextSensitiveConstructorInitializer) {
{BuiltinOptions{ContextSensitiveOptions{}}});
}
-TEST(TransferTest, ContextSensitiveConstructorDefault) {
+TEST_P(TransferTest, ContextSensitiveConstructorDefault) {
std::string Code = R"(
class MyClass {
public:
@@ -7655,7 +7782,7 @@ TEST(TransferTest, ContextSensitiveConstructorDefault) {
{BuiltinOptions{ContextSensitiveOptions{}}});
}
-TEST(TransferTest, ContextSensitiveSelfReferentialClass) {
+TEST_P(TransferTest, ContextSensitiveSelfReferentialClass) {
// Test that the `this` pointer seen in the constructor has the same value
// as the address of the variable the object is constructed into.
std::string Code = R"(
@@ -7690,7 +7817,7 @@ TEST(TransferTest, ContextSensitiveSelfReferentialClass) {
{BuiltinOptions{ContextSensitiveOptions{}}});
}
-TEST(TransferTest, UnnamedBitfieldInitializer) {
+TEST_P(TransferTest, UnnamedBitfieldInitializer) {
std::string Code = R"(
struct B {};
struct A {
@@ -7716,7 +7843,7 @@ TEST(TransferTest, UnnamedBitfieldInitializer) {
// Repro for a crash that used to occur with chained short-circuiting logical
// operators.
-TEST(TransferTest, ChainedLogicalOps) {
+TEST_P(TransferTest, ChainedLogicalOps) {
std::string Code = R"(
bool target() {
bool b = true || false || false || false;
@@ -7736,7 +7863,7 @@ TEST(TransferTest, ChainedLogicalOps) {
// Repro for a crash that used to occur when we call a `noreturn` function
// within one of the operands of a `&&` or `||` operator.
-TEST(TransferTest, NoReturnFunctionInsideShortCircuitedBooleanOp) {
+TEST_P(TransferTest, NoReturnFunctionInsideShortCircuitedBooleanOp) {
std::string Code = R"(
__attribute__((noreturn)) int doesnt_return();
bool some_condition();
@@ -7802,7 +7929,7 @@ TEST(TransferTest, NoReturnFunctionInsideShortCircuitedBooleanOp) {
});
}
-TEST(TransferTest, NewExpressions) {
+TEST_P(TransferTest, NewExpressions) {
std::string Code = R"(
void target() {
int *p = new int(42);
@@ -7822,7 +7949,7 @@ TEST(TransferTest, NewExpressions) {
});
}
-TEST(TransferTest, NewExpressions_Structs) {
+TEST_P(TransferTest, NewExpressions_Structs) {
std::string Code = R"(
struct Inner {
int InnerField;
@@ -7861,7 +7988,7 @@ TEST(TransferTest, NewExpressions_Structs) {
});
}
-TEST(TransferTest, FunctionToPointerDecayHasValue) {
+TEST_P(TransferTest, FunctionToPointerDecayHasValue) {
std::string Code = R"(
struct A { static void static_member_func(); };
void target() {
@@ -7902,7 +8029,7 @@ TEST(TransferTest, FunctionToPointerDecayHasValue) {
// Check that a builtin function is not associated with a value. (It's only
// possible to call builtin functions directly, not take their address.)
-TEST(TransferTest, BuiltinFunctionModeled) {
+TEST_P(TransferTest, BuiltinFunctionModeled) {
std::string Code = R"(
void target() {
__builtin_expect(0, 0);
@@ -7940,7 +8067,7 @@ TEST(TransferTest, BuiltinFunctionModeled) {
// type).
// We want to make sure that we produce a pointer value for the callee in this
// specific scenario and that its storage location is durable (for convergence).
-TEST(TransferTest, MemberOperatorCallModelsPointerForCallee) {
+TEST_P(TransferTest, MemberOperatorCallModelsPointerForCallee) {
std::string Code = R"(
struct S {
bool operator!=(S s);
@@ -7987,7 +8114,7 @@ TEST(TransferTest, MemberOperatorCallModelsPointerForCallee) {
}
// Check that fields of anonymous records are modeled.
-TEST(TransferTest, AnonymousStruct) {
+TEST_P(TransferTest, AnonymousStruct) {
std::string Code = R"(
struct S {
struct {
@@ -8019,7 +8146,7 @@ TEST(TransferTest, AnonymousStruct) {
});
}
-TEST(TransferTest, AnonymousStructWithInitializer) {
+TEST_P(TransferTest, AnonymousStructWithInitializer) {
std::string Code = R"(
struct target {
target() {
@@ -8050,7 +8177,7 @@ TEST(TransferTest, AnonymousStructWithInitializer) {
});
}
-TEST(TransferTest, AnonymousStructWithReferenceField) {
+TEST_P(TransferTest, AnonymousStructWithReferenceField) {
std::string Code = R"(
int global_i = 0;
struct target {
@@ -8083,7 +8210,7 @@ TEST(TransferTest, AnonymousStructWithReferenceField) {
});
}
-TEST(TransferTest, EvaluateBlockWithUnreachablePreds) {
+TEST_P(TransferTest, EvaluateBlockWithUnreachablePreds) {
// This is a crash repro.
// `false` block may not have been processed when we try to evaluate the `||`
// after visiting `true`, because it is not necessary (and therefore the edge
@@ -8103,7 +8230,7 @@ TEST(TransferTest, EvaluateBlockWithUnreachablePreds) {
ASTContext &ASTCtx) {});
}
-TEST(TransferTest, LambdaCaptureByCopy) {
+TEST_P(TransferTest, LambdaCaptureByCopy) {
std::string Code = R"(
void target(int Foo, int Bar) {
[Foo]() {
@@ -8136,7 +8263,7 @@ TEST(TransferTest, LambdaCaptureByCopy) {
});
}
-TEST(TransferTest, LambdaCaptureByReference) {
+TEST_P(TransferTest, LambdaCaptureByReference) {
std::string Code = R"(
void target(int Foo, int Bar) {
[&Foo]() {
@@ -8169,7 +8296,7 @@ TEST(TransferTest, LambdaCaptureByReference) {
});
}
-TEST(TransferTest, LambdaCaptureWithInitializer) {
+TEST_P(TransferTest, LambdaCaptureWithInitializer) {
std::string Code = R"(
void target(int Bar) {
[Foo=Bar]() {
@@ -8202,7 +8329,7 @@ TEST(TransferTest, LambdaCaptureWithInitializer) {
});
}
-TEST(TransferTest, LambdaCaptureByCopyImplicit) {
+TEST_P(TransferTest, LambdaCaptureByCopyImplicit) {
std::string Code = R"(
void target(int Foo, int Bar) {
[=]() {
@@ -8237,7 +8364,7 @@ TEST(TransferTest, LambdaCaptureByCopyImplicit) {
});
}
-TEST(TransferTest, LambdaCaptureByReferenceImplicit) {
+TEST_P(TransferTest, LambdaCaptureByReferenceImplicit) {
std::string Code = R"(
void target(int Foo, int Bar) {
[&]() {
@@ -8272,7 +8399,7 @@ TEST(TransferTest, LambdaCaptureByReferenceImplicit) {
});
}
-TEST(TransferTest, LambdaCaptureThis) {
+TEST_P(TransferTest, LambdaCaptureThis) {
std::string Code = R"(
struct Bar {
int Foo;
@@ -8307,9 +8434,12 @@ TEST(TransferTest, LambdaCaptureThis) {
});
}
-// This test verifies correct modeling of a relational dependency that goes
-// through unmodeled functions (the simple `cond()` in this case).
-TEST(TransferTest, ConditionalRelation) {
+// This test verifies modeling of a relational invariant across nested joins:
+// `b` is only set to `false` after going through the outer `if` branch where
+// `a` is set to `false` (note that the two `cond()` calls are
+// independent unmodeled calls). So `!b => !a` (i.e. `!(a && !b)`) at `[[p]]`,
+// but proving that requires reasoning for disjunctions.
+TEST_P(TransferTest, ConditionalRelation) {
std::string Code = R"(
bool cond();
void target() {
@@ -8334,14 +8464,18 @@ TEST(TransferTest, ConditionalRelation) {
auto &VarA = getValueForDecl<BoolValue>(ASTCtx, Env, "a").formula();
auto &VarB = getValueForDecl<BoolValue>(ASTCtx, Env, "b").formula();
- EXPECT_FALSE(Env.allows(A.makeAnd(VarA, A.makeNot(VarB))));
+ if (GetParam() == BoolModelKind::SAT) {
+ EXPECT_FALSE(Env.allows(A.makeAnd(VarA, A.makeNot(VarB))));
+ } else {
+ EXPECT_TRUE(Env.allows(A.makeAnd(VarA, A.makeNot(VarB))));
+ }
});
}
// This is a crash repro.
// We used to crash while transferring `S().i` because Clang contained a bug
// causing the AST to be malformed.
-TEST(TransferTest, AnonymousUnionMemberExprInTemplate) {
+TEST_P(TransferTest, AnonymousUnionMemberExprInTemplate) {
using ast_matchers::functionDecl;
using ast_matchers::hasName;
using ast_matchers::unless;
diff --git a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
index f34568a072c32..f9e528de0ac11 100644
--- a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
@@ -12,6 +12,8 @@
#include "TestingSupport.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
+#include "clang/Basic/LLVM.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Frontend/TextDiagnostic.h"
#include "clang/Tooling/Tooling.h"
@@ -47,11 +49,13 @@ static void ReplaceAllOccurrences(std::string &S, const std::string &Pattern,
struct OptionalTypeIdentifier {
std::string NamespaceName;
std::string TypeName;
+ BoolModelKind BoolModel = BoolModelKind::SAT;
};
static raw_ostream &operator<<(raw_ostream &OS,
const OptionalTypeIdentifier &TypeId) {
- OS << TypeId.NamespaceName << "::" << TypeId.TypeName;
+ OS << TypeId.NamespaceName << "::" << TypeId.TypeName << " ("
+ << (TypeId.BoolModel == BoolModelKind::SAT ? "SAT" : "Simple") << ")";
return OS;
}
@@ -97,6 +101,9 @@ class UncheckedOptionalAccessTest
bool IgnoreSmartPointerDereference) {
ReplaceAllOccurrences(SourceCode, "$ns", GetParam().NamespaceName);
ReplaceAllOccurrences(SourceCode, "$optional", GetParam().TypeName);
+ ReplaceAllOccurrences(
+ SourceCode, "$unsafe_if_simple",
+ GetParam().BoolModel == BoolModelKind::Simple ? "// [[unsafe]]" : "");
auto Headers = getMockHeaders();
Headers.emplace_back("unchecked_optional_access_test.h", R"(
@@ -119,6 +126,9 @@ class UncheckedOptionalAccessTest
[](ASTContext &Ctx, Environment &Env) {
return UncheckedOptionalAccessModel(Ctx, Env);
})
+ .withBuiltinOptions({/*ContextSensitiveOpts=*/std::nullopt,
+ /*Logger=*/nullptr,
+ /*BoolModel=*/GetParam().BoolModel})
.withDiagnosisCallbacks(
{/*Before=*/[&Diagnostics,
Diagnoser =
@@ -179,11 +189,16 @@ class UncheckedOptionalAccessTest
INSTANTIATE_TEST_SUITE_P(
UncheckedOptionalUseTestInst, UncheckedOptionalAccessTest,
- ::testing::Values(OptionalTypeIdentifier{"std", "optional"},
- OptionalTypeIdentifier{"absl", "optional"},
- OptionalTypeIdentifier{"base", "Optional"}),
+ ::testing::Values(
+ OptionalTypeIdentifier{"std", "optional", BoolModelKind::SAT},
+ OptionalTypeIdentifier{"std", "optional", BoolModelKind::Simple},
+ OptionalTypeIdentifier{"absl", "optional", BoolModelKind::SAT},
+ OptionalTypeIdentifier{"absl", "optional", BoolModelKind::Simple},
+ OptionalTypeIdentifier{"base", "Optional", BoolModelKind::SAT},
+ OptionalTypeIdentifier{"base", "Optional", BoolModelKind::Simple}),
[](const ::testing::TestParamInfo<OptionalTypeIdentifier> &Info) {
- return Info.param.NamespaceName;
+ return Info.param.NamespaceName + "_" +
+ (Info.param.BoolModel == BoolModelKind::SAT ? "SAT" : "Simple");
});
// Verifies that similarly-named types are ignored.
@@ -1821,14 +1836,16 @@ TEST_P(UncheckedOptionalAccessTest, AssignThroughLvalueReferencePtr) {
)");
}
-TEST_P(UncheckedOptionalAccessTest, CorrelatedBranches) {
+TEST_P(UncheckedOptionalAccessTest, CorrelatedBranchesShortCircuit) {
+ // `SimpleBoolModel` does not retain disjunctive formulas across
+ // short-circuit `||`, so it cannot prove `opt.has_value()`.
ExpectDiagnosticsFor(R"code(
#include "unchecked_optional_access_test.h"
void target(bool b, $ns::$optional<int> opt) {
if (b || opt.has_value()) {
if (!b) {
- opt.value();
+ opt.value(); $unsafe_if_simple
}
}
}
@@ -1840,11 +1857,13 @@ TEST_P(UncheckedOptionalAccessTest, CorrelatedBranches) {
void target(bool b, $ns::$optional<int> opt) {
if (b && !opt.has_value()) return;
if (b) {
- opt.value();
+ opt.value(); $unsafe_if_simple
}
}
)code");
+}
+TEST_P(UncheckedOptionalAccessTest, CorrelatedBranchesMutatedCondition) {
ExpectDiagnosticsFor(
R"code(
#include "unchecked_optional_access_test.h"
@@ -1864,11 +1883,14 @@ TEST_P(UncheckedOptionalAccessTest, CorrelatedBranches) {
if (b) return;
if (opt.has_value()) b = true;
if (b) {
- opt.value();
+ opt.value(); $unsafe_if_simple
}
}
)code");
+}
+TEST_P(UncheckedOptionalAccessTest,
+ CorrelatedBranchesNoShortCircuitNoMutation) {
ExpectDiagnosticsFor(R"(
#include "unchecked_optional_access_test.h"
@@ -1892,7 +1914,9 @@ TEST_P(UncheckedOptionalAccessTest, CorrelatedBranches) {
}
}
)");
+}
+TEST_P(UncheckedOptionalAccessTest, CorrelatedBranchesDeadCode) {
ExpectDiagnosticsFor(R"(
#include "unchecked_optional_access_test.h"
@@ -2638,7 +2662,9 @@ TEST_P(UncheckedOptionalAccessTest, SmartPointerAccessorMixed) {
/*IgnoreSmartPointerDereference=*/false);
}
-TEST_P(UncheckedOptionalAccessTest, ConstBoolAccessor) {
+TEST_P(UncheckedOptionalAccessTest, ConstBoolAccessorCorrelatedBranches) {
+ // `SimpleBoolModel` does not track path-conditioned correlations across the
+ // join after `if (a.isFoo()) { opt = 1; }`.
ExpectDiagnosticsFor(R"cc(
#include "unchecked_optional_access_test.h"
@@ -2653,7 +2679,7 @@ TEST_P(UncheckedOptionalAccessTest, ConstBoolAccessor) {
opt = 1;
}
if (a.isFoo()) {
- opt.value();
+ opt.value(); $unsafe_if_simple
}
}
)cc");
>From b0ace2ac8506a01bc399f8e189e925705661cfd6 Mon Sep 17 00:00:00 2001
From: Jan Voung <jvoung at gmail.com>
Date: Mon, 12 Oct 2026 17:01:18 +0000
Subject: [PATCH 2/3] Address code review suggestions
- use structured bindings, and try_emplace
- clean up redundant HasContradiction check
---
.../FlowSensitive/DataflowEnvironment.cpp | 30 ++++++++-----------
1 file changed, 12 insertions(+), 18 deletions(-)
diff --git a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
index 268ee8af63796..31ffd2576b8d2 100644
--- a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
+++ b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
@@ -88,8 +88,8 @@ class SimpleBoolModel : public Environment::BoolModel {
// already fully represented by `AtomToVal` (though it also drops
// disjunctive formulas that were reduced using a branch-local atom, such
// as on the RHS of a short-circuit `&&` or `||`).
- // TODO(jvoung): We may want to retain some disjunctive formulas, but
- // would need to profile and see how much it helps vs how much it costs.
+ // TODO: We may want to retain some disjunctive formulas, but would need
+ // to profile and see how much it helps vs how much it costs.
if (std::optional<bool> Val = getLiteralValue(F)) {
if (!*Val) {
HasContradiction = true;
@@ -108,7 +108,7 @@ class SimpleBoolModel : public Environment::BoolModel {
// branch-local and discarded at a subsequent `join`, where the formula
// itself still holds on both branches.
bool Changed = true;
- while (Changed && !HasContradiction) {
+ while (Changed) {
Changed = false;
for (const Formula *Pending : AssumedFormulas) {
unsigned Before = AtomToVal.size();
@@ -146,9 +146,9 @@ class SimpleBoolModel : public Environment::BoolModel {
Environment &JoinedEnv) override {
const auto &S1 = cast<SimpleBoolModel>(Model1);
const auto &S2 = cast<SimpleBoolModel>(Model2);
- if (S1.HasContradiction && !S2.HasContradiction)
+ if (S1.HasContradiction)
return Val2;
- if (S2.HasContradiction && !S1.HasContradiction)
+ if (S2.HasContradiction)
return Val1;
if (auto V1 = S1.getLiteralValue(Val1.formula());
V1.has_value() && V1 == S2.getLiteralValue(Val2.formula()))
@@ -173,10 +173,10 @@ class SimpleBoolModel : public Environment::BoolModel {
AssumedFormulas = SA.AssumedFormulas;
} else {
// Intersect the AtomToVal maps.
- for (const auto &Entry : SA.AtomToVal) {
- auto It = SB.AtomToVal.find(Entry.first);
- if (It != SB.AtomToVal.end() && Entry.second == It->second)
- AtomToVal.insert({Entry.first, Entry.second});
+ for (const auto &[A, V] : SA.AtomToVal) {
+ auto It = SB.AtomToVal.find(A);
+ if (It != SB.AtomToVal.end() && V == It->second)
+ AtomToVal.insert({A, V});
}
// Keep assumed formulas that hold on both branches: either because both
@@ -244,8 +244,6 @@ class SimpleBoolModel : public Environment::BoolModel {
return It->second;
}
- void setAtomValue(Atom A, bool B) { AtomToVal[A] = B; }
-
std::optional<bool> getLiteralValue(const Formula &F) const {
switch (F.kind()) {
case Formula::AtomRef:
@@ -331,13 +329,9 @@ class SimpleBoolModel : public Environment::BoolModel {
// Base case: `F` is a single atom, so assuming `F == AssumedVal` directly
// binds this atom to `AssumedVal`, or marks the environment as having a
// contradiction if the atom was already bound to the opposite value.
- Atom A = F.getAtom();
- if (std::optional<bool> Existing = getAtomValue(A)) {
- if (*Existing != AssumedVal)
- HasContradiction = true;
- break;
- }
- setAtomValue(A, AssumedVal);
+ auto [It, Inserted] = AtomToVal.try_emplace(F.getAtom(), AssumedVal);
+ if (!Inserted && It->second != AssumedVal)
+ HasContradiction = true;
break;
}
case Formula::Literal:
>From f4f79a5a11608f549a2f97b92c748eab0c23d504 Mon Sep 17 00:00:00 2001
From: Jan Voung <jvoung at gmail.com>
Date: Tue, 13 Oct 2026 01:50:12 +0000
Subject: [PATCH 3/3] Small fix to join for AssumedFormulas and add test for
that (loop)
---
.../FlowSensitive/DataflowEnvironment.cpp | 7 ++--
.../Analysis/FlowSensitive/TransferTest.cpp | 35 +++++++++++++++++--
2 files changed, 36 insertions(+), 6 deletions(-)
diff --git a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
index 31ffd2576b8d2..5dc2cc49b2da0 100644
--- a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
+++ b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
@@ -179,17 +179,18 @@ class SimpleBoolModel : public Environment::BoolModel {
AtomToVal.insert({A, V});
}
- // Keep assumed formulas that hold on both branches: either because both
+ // Keep assumed formulas that hold on both branches (and are not already
+ // reduced to `true` by the joined `AtomToVal`): either because both
// environments still carry `F` as an unresolved formula, or because one
// branch carries `F` and the other branch's `AtomToVal` assignments
// already prove `F`. Any branch-local formulas not proved by the other
// branch are dropped here, which shrinks `AssumedFormulas` at joins.
for (const Formula *F : SA.AssumedFormulas) {
- if (SB.AssumedFormulas.contains(F) || SB.proves(*F))
+ if (!proves(*F) && (SB.AssumedFormulas.contains(F) || SB.proves(*F)))
AssumedFormulas.insert(F);
}
for (const Formula *F : SB.AssumedFormulas) {
- if (SA.proves(*F))
+ if (!proves(*F) && SA.proves(*F))
AssumedFormulas.insert(F);
}
}
diff --git a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
index 6810f578be5b4..7d1b8d4f8c776 100644
--- a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
@@ -5800,9 +5800,38 @@ TEST_P(TransferTest, LoopMergeBlockUpdatesBoolModelWhenLocToValUnchanged) {
const ValueDecl *ADecl = findValueDecl(ASTCtx, "A");
ASSERT_THAT(ADecl, NotNull());
- if (GetParam() == BoolModelKind::Simple) {
- EXPECT_FALSE(Env.proves(getFormula(*ADecl, Env)));
- }
+ EXPECT_FALSE(Env.proves(getFormula(*ADecl, Env)));
+ });
+}
+
+TEST_P(TransferTest, LoopWithDisjunctionProvedByLaterAssumptionConverges) {
+ std::string Code = R"(
+ bool foo();
+
+ void target(bool X) {
+ while (foo()) {
+ if (!(X || foo())) return;
+ if (!X) return;
+ (void)0;
+ /*[[p]]*/
+ }
+ }
+ )";
+ // Each iteration assumes `X || foo()` (with a fresh atom for `foo()`) while
+ // `X` is unknown, and then assumes `X == true`. At the backedge join/widen,
+ // `X == true` already proves `X || foo()`, so the per-iteration formulas
+ // should not accumulate in `AssumedFormulas` and prevent convergence.
+ runDataflow(
+ Code,
+ [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
+ ASTContext &ASTCtx) {
+ ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
+ const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
+
+ const ValueDecl *XDecl = findValueDecl(ASTCtx, "X");
+ ASSERT_THAT(XDecl, NotNull());
+
+ EXPECT_TRUE(Env.proves(getFormula(*XDecl, Env)));
});
}
More information about the cfe-commits
mailing list