Skip to content

Commit

Permalink
[clang][dataflow] When analyzing ctors, don't initialize fields of `*…
Browse files Browse the repository at this point in the history
…this` with values. (#84164)

This is the constructor's job, and we want to be able to test that it
does this.
  • Loading branch information
martinboehme committed Mar 8, 2024
1 parent 23c658a commit 2d539db
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,11 @@ class Environment {
return createObjectInternal(&D, D.getType(), InitExpr);
}

/// Initializes the fields (including synthetic fields) of `Loc` with values,
/// unless values of the field type are not supported or we hit one of the
/// limits at which we stop producing values.
void initializeFieldsWithValues(RecordStorageLocation &Loc);

/// Assigns `Val` as the value of `Loc` in the environment.
void setValue(const StorageLocation &Loc, Value &Val);

Expand Down
21 changes: 19 additions & 2 deletions clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -432,8 +432,15 @@ void Environment::initialize() {
}
} else if (MethodDecl->isImplicitObjectMemberFunction()) {
QualType ThisPointeeType = MethodDecl->getFunctionObjectParameterType();
setThisPointeeStorageLocation(
cast<RecordStorageLocation>(createObject(ThisPointeeType)));
auto &ThisLoc =
cast<RecordStorageLocation>(createStorageLocation(ThisPointeeType));
setThisPointeeStorageLocation(ThisLoc);
refreshRecordValue(ThisLoc, *this);
// Initialize fields of `*this` with values, but only if we're not
// analyzing a constructor; after all, it's the constructor's job to do
// this (and we want to be able to test that).
if (!isa<CXXConstructorDecl>(MethodDecl))
initializeFieldsWithValues(ThisLoc);
}
}
}
Expand Down Expand Up @@ -819,6 +826,16 @@ PointerValue &Environment::getOrCreateNullPointerValue(QualType PointeeType) {
return DACtx->getOrCreateNullPointerValue(PointeeType);
}

void Environment::initializeFieldsWithValues(RecordStorageLocation &Loc) {
llvm::DenseSet<QualType> Visited;
int CreatedValuesCount = 0;
initializeFieldsWithValues(Loc, Visited, 0, CreatedValuesCount);
if (CreatedValuesCount > MaxCompositeValueSize) {
llvm::errs() << "Attempting to initialize a huge value of type: "
<< Loc.getType() << '\n';
}
}

void Environment::setValue(const StorageLocation &Loc, Value &Val) {
assert(!isa<RecordValue>(&Val) || &cast<RecordValue>(&Val)->getLoc() == &Loc);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,6 @@ builtinTransferInitializer(const CFGInitializer &Elt,
}
}
assert(Member != nullptr);
assert(MemberLoc != nullptr);

// FIXME: Instead of these case distinctions, we would ideally want to be able
// to simply use `Environment::createObject()` here, the same way that we do
Expand All @@ -422,6 +421,7 @@ builtinTransferInitializer(const CFGInitializer &Elt,

ParentLoc->setChild(*Member, InitExprLoc);
} else if (auto *InitExprVal = Env.getValue(*InitExpr)) {
assert(MemberLoc != nullptr);
if (Member->getType()->isRecordType()) {
auto *InitValStruct = cast<RecordValue>(InitExprVal);
// FIXME: Rather than performing a copy here, we should really be
Expand Down
5 changes: 4 additions & 1 deletion clang/unittests/Analysis/FlowSensitive/TestingSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,10 @@ llvm::Error test::checkDataflowWithNoopAnalysis(
// -fnodelayed-template-parsing is the default everywhere but on Windows.
// Set it explicitly so that tests behave the same on Windows as on other
// platforms.
"-fsyntax-only", "-fno-delayed-template-parsing",
// Set -Wno-unused-value because it's often desirable in tests to write
// expressions with unused value, and we don't want the output to be
// cluttered with warnings about them.
"-fsyntax-only", "-fno-delayed-template-parsing", "-Wno-unused-value",
"-std=" +
std::string(LangStandard::getLangStandardForKind(Std).getName())};
AnalysisInputs<NoopAnalysis> AI(
Expand Down
63 changes: 63 additions & 0 deletions clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1476,6 +1476,69 @@ TEST(TransferTest, BaseClassInitializer) {
llvm::Succeeded());
}

TEST(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.
std::string Code = R"(
struct target {
target() {
0;
// [[p]]
// Mention the field so it is modeled;
Val;
}
int Val;
};
)";
runDataflow(
Code,
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
ASTContext &ASTCtx) {
const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
EXPECT_EQ(getFieldValue(Env.getThisPointeeStorageLocation(), "Val",
ASTCtx, Env),
nullptr);
});
}

TEST(TransferTest, FieldsDontHaveValuesInConstructorWithBaseClass) {
// See above, but for a class with a base class.
std::string Code = R"(
struct Base {
int BaseVal;
};
struct target : public Base {
target() {
0;
// [[p]]
// Mention the fields so they are modeled.
BaseVal;
Val;
}
int Val;
};
)";
runDataflow(
Code,
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
ASTContext &ASTCtx) {
const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
// FIXME: The field of the base class should already have been
// initialized with a value by the base constructor. This test documents
// the current buggy behavior.
EXPECT_EQ(getFieldValue(Env.getThisPointeeStorageLocation(), "BaseVal",
ASTCtx, Env),
nullptr);
EXPECT_EQ(getFieldValue(Env.getThisPointeeStorageLocation(), "Val",
ASTCtx, Env),
nullptr);
});
}

TEST(TransferTest, StructModeledFieldsWithAccessor) {
std::string Code = R"(
class S {
Expand Down

0 comments on commit 2d539db

Please sign in to comment.