Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion clang/lib/Analysis/FlowSensitive/Transfer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -769,8 +769,29 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
StorageLocation *TrueLoc = TrueEnv->getStorageLocation(*S->getTrueExpr());
StorageLocation *FalseLoc =
FalseEnv->getStorageLocation(*S->getFalseExpr());
if (TrueLoc == FalseLoc && TrueLoc != nullptr)
if (TrueLoc == FalseLoc && TrueLoc != nullptr) {
Env.setStorageLocation(*S, *TrueLoc);
} else if (!S->getType()->isRecordType()) {
// Ideally, we would have something like an "alias set" to say that the
// result StorageLocation can be either of the locations from the
// TrueEnv or FalseEnv. Then, when this ConditionalOperator is
// (a) used in an LValueToRValue cast, the value is the join of all of
// the values in the alias set.
// (b) or, used in an assignment to the resulting LValue, the assignment
// *may* update all of the locations in the alias set.
// For now, we do the simpler thing of creating a new StorageLocation
// and joining the values right away, handling only case (a).
// Otherwise, the dataflow framework needs to be updated be able to
// represent alias sets and weak updates (for the "may").
StorageLocation &Loc = Env.createStorageLocation(*S);
Env.setStorageLocation(*S, Loc);
if (Value *Val = Environment::joinValues(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the join fails (Val == nullptr), do we still want the newly created Loc to be associated with *S? Or, should line 787 be grouped with line 792?

S->getType(), TrueEnv->getValue(*S->getTrueExpr()), *TrueEnv,
FalseEnv->getValue(*S->getFalseExpr()), *FalseEnv, Env,
Model)) {
Env.setValue(Loc, *Val);
}
}
} else if (!S->getType()->isRecordType()) {
// The conditional operator can evaluate to either of the values of the
// two branches. To model this, join these two values together to yield
Expand Down
131 changes: 131 additions & 0 deletions clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
#include "clang/Analysis/FlowSensitive/Formula.h"
#include "clang/Analysis/FlowSensitive/NoopAnalysis.h"
#include "clang/Analysis/FlowSensitive/NoopLattice.h"
#include "clang/Analysis/FlowSensitive/RecordOps.h"
Expand Down Expand Up @@ -4382,6 +4383,40 @@ TEST(TransferTest, VarDeclInitAssignConditionalOperator) {
});
}

TEST(TransferTest, VarDeclInitReferenceAssignConditionalOperator) {
std::string Code = R"(
struct A {
int i;
};

void target(A Foo, A Bar, bool Cond) {
A &Baz = Cond ? Foo : Bar;
// Make sure A::i is modeled.
Baz.i;
/*[[p]]*/
}
)";
runDataflow(
Code,
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
ASTContext &ASTCtx) {
const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

auto *FooIVal = cast<IntegerValue>(getFieldValue(
&getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "Foo"), "i",
ASTCtx, Env));
auto *BarIVal = cast<IntegerValue>(getFieldValue(
&getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "Bar"), "i",
ASTCtx, Env));
auto *BazIVal = cast<IntegerValue>(getFieldValue(
&getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "Baz"), "i",
ASTCtx, Env));

EXPECT_NE(BazIVal, FooIVal);
EXPECT_NE(BazIVal, BarIVal);
});
}

TEST(TransferTest, VarDeclInDoWhile) {
std::string Code = R"(
void target(int *Foo) {
Expand Down Expand Up @@ -6177,6 +6212,102 @@ TEST(TransferTest, ConditionalOperatorLocation) {
});
}

TEST(TransferTest, ConditionalOperatorValuesTested) {
// Even though the LHS and the RHS of the conditional operator have different
// StorageLocations, we get constraints from the condition that the values
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't follow this text. Could you rephrase?

// must be equal to B1 for JoinDifferentIsB1, or B2 for JoinDifferentIsB2.
std::string Code = R"(
void target(bool B1, bool B2) {
bool JoinDifferentIsB1 = (B1 == B2) ? B2 : B1;
bool JoinDifferentIsB2 = (B1 == B2) ? B1 : B2;
// [[p]]
}
)";
runDataflow(
Code,
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
ASTContext &ASTCtx) {
Environment Env = getEnvironmentAtAnnotation(Results, "p").fork();

auto &B1 = getValueForDecl<BoolValue>(ASTCtx, Env, "B1");
auto &B2 = getValueForDecl<BoolValue>(ASTCtx, Env, "B2");
auto &JoinDifferentIsB1 =
getValueForDecl<BoolValue>(ASTCtx, Env, "JoinDifferentIsB1");
auto &JoinDifferentIsB2 =
getValueForDecl<BoolValue>(ASTCtx, Env, "JoinDifferentIsB2");

const Formula &JoinDifferentIsB1EqB1 =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comment explaining the intent here? The name is a little hard to parse.

Env.arena().makeEquals(JoinDifferentIsB1.formula(), B1.formula());
EXPECT_TRUE(Env.allows(JoinDifferentIsB1EqB1));
EXPECT_TRUE(Env.proves(JoinDifferentIsB1EqB1));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why both? If "proves" is true, then "allows" is definitely true.


const Formula &JoinDifferentIsB2EqB2 =
Env.arena().makeEquals(JoinDifferentIsB2.formula(), B2.formula());
EXPECT_TRUE(Env.allows(JoinDifferentIsB2EqB2));
EXPECT_TRUE(Env.proves(JoinDifferentIsB2EqB2));
});
}

TEST(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
// (a weak update). So, we don't consider the LHS and RHS as being weakly
// updated at [[after_diff]].
std::string Code = R"(
void target(bool Cond, bool B1, bool B2) {
(void)0;
// [[before_same]]
(Cond ? B1 : B1) = !B1;
// [[after_same]]
(Cond ? B1 : B2) = !B1;
// [[after_diff]]
}
)";
runDataflow(
Code,
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
ASTContext &ASTCtx) {
Environment BeforeSameEnv =
getEnvironmentAtAnnotation(Results, "before_same").fork();
Environment AfterSameEnv =
getEnvironmentAtAnnotation(Results, "after_same").fork();
Environment AfterDiffEnv =
getEnvironmentAtAnnotation(Results, "after_diff").fork();

auto &BeforeSameB1 =
getValueForDecl<BoolValue>(ASTCtx, BeforeSameEnv, "B1");
auto &AfterSameB1 =
getValueForDecl<BoolValue>(ASTCtx, AfterSameEnv, "B1");
auto &AfterDiffB1 =
getValueForDecl<BoolValue>(ASTCtx, AfterDiffEnv, "B1");

EXPECT_NE(&BeforeSameB1, &AfterSameB1);
EXPECT_NE(&BeforeSameB1, &AfterDiffB1);
// FIXME: The formula for AfterSameB1 should be different from
// AfterDiffB1 to reflect that B1 may be updated.
EXPECT_EQ(&AfterSameB1, &AfterDiffB1);

// The value of B1 is definitely different from before_same vs
// after_same.
const Formula &B1ChangedForSame =
AfterSameEnv.arena().makeNot(AfterSameEnv.arena().makeEquals(
AfterSameB1.formula(), BeforeSameB1.formula()));
EXPECT_TRUE(AfterSameEnv.allows(B1ChangedForSame));
EXPECT_TRUE(AfterSameEnv.proves(B1ChangedForSame));

// FIXME: It should be possible that B1 *may* be updated, so it may be
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would more clear to me that only the allows expectation needs to change if this comment were moved to precede that line, after this decl.

// that AfterSameB1 != AfterDiffB1 or AfterSameB1 == AfterDiffB1.
const Formula &B1ChangedForDiff =
AfterDiffEnv.arena().makeNot(AfterDiffEnv.arena().makeEquals(
AfterDiffB1.formula(), AfterSameB1.formula()));
EXPECT_FALSE(AfterSameEnv.allows(B1ChangedForDiff));
// proves() should be false, since B1 may or may not have changed
// depending on `Cond`.
EXPECT_FALSE(AfterSameEnv.proves(B1ChangedForDiff));
});
}

TEST(TransferTest, ConditionalOperatorOnConstantExpr) {
// This is a regression test: We used to crash when a `ConstantExpr` was used
// in the branches of a conditional operator.
Expand Down