Skip to content

Commit f9026cf

Browse files
kinumartinboehme
authored andcommitted
[clang][dataflow] Fix Record initialization with InitListExpr and inheritances
Usually RecordValues for record objects (e.g. struct) are initialized with `Environment::createValue()` which internally calls `getObjectFields()` to collects all fields from the current and base classes, and then filter them with `ModeledValues` via `DACtx::getModeledFields()` so that the fields that are actually referenced are modeled. The consistent set of fields should be initialized when a record is initialized with an initializer list (InitListExpr), however the existing code's behavior was different. Before this patch: * When a struct is initialized with InitListExpr, its fields are initialized based on what is returned by `getFieldsForInitListExpr()`, which only collects the direct fields in the current class, but not from the base classes. Moreover, if the base classes have their own InitListExpr, values that are initialized by their InitListExpr's weren't merged into the child objects. After this patch: * When a struct is initialized with InitListExpr, it collects and merges the fields in the base classes that were initialized by their InitListExpr's. The code also asserts that the consistent set of fields are initialized with the ModeledFields. Reviewed By: mboehme Differential Revision: https://reviews.llvm.org/D159284
1 parent c22c0c4 commit f9026cf

File tree

2 files changed

+172
-10
lines changed

2 files changed

+172
-10
lines changed

clang/lib/Analysis/FlowSensitive/Transfer.cpp

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@
2727
#include "clang/Analysis/FlowSensitive/Value.h"
2828
#include "clang/Basic/Builtins.h"
2929
#include "clang/Basic/OperatorKinds.h"
30-
#include "llvm/ADT/STLExtras.h"
3130
#include "llvm/Support/Casting.h"
32-
#include "llvm/Support/ErrorHandling.h"
31+
#include "llvm/Support/Debug.h"
32+
#include <assert.h>
3333
#include <cassert>
34-
#include <memory>
35-
#include <tuple>
34+
35+
#define DEBUG_TYPE "dataflow"
3636

3737
namespace clang {
3838
namespace dataflow {
@@ -629,17 +629,66 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
629629
return;
630630
}
631631

632-
std::vector<FieldDecl *> Fields =
633-
getFieldsForInitListExpr(Type->getAsRecordDecl());
634632
llvm::DenseMap<const ValueDecl *, StorageLocation *> FieldLocs;
635633

636-
for (auto [Field, Init] : llvm::zip(Fields, S->inits())) {
637-
assert(Field != nullptr);
638-
assert(Init != nullptr);
634+
// This only contains the direct fields for the given type.
635+
std::vector<FieldDecl *> FieldsForInit =
636+
getFieldsForInitListExpr(Type->getAsRecordDecl());
639637

640-
FieldLocs.insert({Field, &Env.createObject(Field->getType(), Init)});
638+
// `S->inits()` contains all the initializer epressions, including the
639+
// ones for direct base classes.
640+
auto Inits = S->inits();
641+
size_t InitIdx = 0;
642+
643+
// Initialize base classes.
644+
if (auto* R = S->getType()->getAsCXXRecordDecl()) {
645+
assert(FieldsForInit.size() + R->getNumBases() == Inits.size());
646+
for ([[maybe_unused]] const CXXBaseSpecifier &Base : R->bases()) {
647+
assert(InitIdx < Inits.size());
648+
auto Init = Inits[InitIdx++];
649+
assert(Base.getType().getCanonicalType() ==
650+
Init->getType().getCanonicalType());
651+
auto* BaseVal = cast_or_null<RecordValue>(Env.getValue(*Init));
652+
if (!BaseVal)
653+
BaseVal = cast<RecordValue>(Env.createValue(Init->getType()));
654+
// Take ownership of the fields of the `RecordValue` for the base class
655+
// and incorporate them into the "flattened" set of fields for the
656+
// derived class.
657+
auto Children = BaseVal->getLoc().children();
658+
FieldLocs.insert(Children.begin(), Children.end());
659+
}
641660
}
642661

662+
assert(FieldsForInit.size() == Inits.size() - InitIdx);
663+
for (auto Field : FieldsForInit) {
664+
assert(InitIdx < Inits.size());
665+
auto Init = Inits[InitIdx++];
666+
assert(
667+
// The types are same, or
668+
Field->getType().getCanonicalType().getUnqualifiedType() ==
669+
Init->getType().getCanonicalType() ||
670+
// The field's type is T&, and initializer is T
671+
(Field->getType()->isReferenceType() &&
672+
Field->getType().getCanonicalType()->getPointeeType() ==
673+
Init->getType().getCanonicalType()));
674+
auto& Loc = Env.createObject(Field->getType(), Init);
675+
FieldLocs.insert({Field, &Loc});
676+
}
677+
678+
LLVM_DEBUG({
679+
// Check that we satisfy the invariant that a `RecordStorageLoation`
680+
// contains exactly the set of modeled fields for that type.
681+
// `ModeledFields` includes fields from all the bases, but only the
682+
// modeled ones. However, if a class type is initialized with an
683+
// `InitListExpr`, all fields in the class, including those from base
684+
// classes, are included in the set of modeled fields. The code above
685+
// should therefore populate exactly the modeled fields.
686+
auto ModeledFields = Env.getDataflowAnalysisContext().getModeledFields(Type);
687+
assert(ModeledFields.size() == FieldLocs.size());
688+
for ([[maybe_unused]] auto [Field, Loc] : FieldLocs)
689+
assert(ModeledFields.contains(cast_or_null<FieldDecl>(Field)));
690+
});
691+
643692
auto &Loc =
644693
Env.getDataflowAnalysisContext().arena().create<RecordStorageLocation>(
645694
Type, std::move(FieldLocs));

clang/unittests/Analysis/FlowSensitive/TransferTest.cpp

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,6 +1446,99 @@ TEST(TransferTest, BaseClassInitializer) {
14461446
llvm::Succeeded());
14471447
}
14481448

1449+
TEST(TransferTest, StructModeledFieldsWithComplicatedInheritance) {
1450+
std::string Code = R"(
1451+
struct Base1 {
1452+
int base1_1;
1453+
int base1_2;
1454+
};
1455+
struct Intermediate : Base1 {
1456+
int intermediate_1;
1457+
int intermediate_2;
1458+
};
1459+
struct Base2 {
1460+
int base2_1;
1461+
int base2_2;
1462+
};
1463+
struct MostDerived : public Intermediate, Base2 {
1464+
int most_derived_1;
1465+
int most_derived_2;
1466+
};
1467+
1468+
void target() {
1469+
MostDerived MD;
1470+
MD.base1_2 = 1;
1471+
MD.intermediate_2 = 1;
1472+
MD.base2_2 = 1;
1473+
MD.most_derived_2 = 1;
1474+
// [[p]]
1475+
}
1476+
)";
1477+
runDataflow(
1478+
Code,
1479+
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
1480+
ASTContext &ASTCtx) {
1481+
const Environment &Env =
1482+
getEnvironmentAtAnnotation(Results, "p");
1483+
1484+
// Only the accessed fields should exist in the model.
1485+
auto &MDLoc = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "MD");
1486+
std::vector<const ValueDecl*> Fields;
1487+
for (auto [Field, _] : MDLoc.children())
1488+
Fields.push_back(Field);
1489+
ASSERT_THAT(Fields, UnorderedElementsAre(
1490+
findValueDecl(ASTCtx, "base1_2"),
1491+
findValueDecl(ASTCtx, "intermediate_2"),
1492+
findValueDecl(ASTCtx, "base2_2"),
1493+
findValueDecl(ASTCtx, "most_derived_2")));
1494+
});
1495+
}
1496+
1497+
TEST(TransferTest, StructInitializerListWithComplicatedInheritance) {
1498+
std::string Code = R"(
1499+
struct Base1 {
1500+
int base1;
1501+
};
1502+
struct Intermediate : Base1 {
1503+
int intermediate;
1504+
};
1505+
struct Base2 {
1506+
int base2;
1507+
};
1508+
struct MostDerived : public Intermediate, Base2 {
1509+
int most_derived;
1510+
};
1511+
1512+
void target() {
1513+
MostDerived MD = {};
1514+
// [[p]]
1515+
}
1516+
)";
1517+
runDataflow(
1518+
Code,
1519+
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
1520+
ASTContext &ASTCtx) {
1521+
const Environment &Env =
1522+
getEnvironmentAtAnnotation(Results, "p");
1523+
1524+
// When a struct is initialized with a initializer list, all the
1525+
// fields are considered "accessed", and therefore do exist.
1526+
auto &MD = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "MD");
1527+
ASSERT_THAT(cast<IntegerValue>(
1528+
getFieldValue(&MD, *findValueDecl(ASTCtx, "base1"), Env)),
1529+
NotNull());
1530+
ASSERT_THAT(cast<IntegerValue>(
1531+
getFieldValue(&MD, *findValueDecl(ASTCtx, "intermediate"), Env)),
1532+
NotNull());
1533+
ASSERT_THAT(cast<IntegerValue>(
1534+
getFieldValue(&MD, *findValueDecl(ASTCtx, "base2"), Env)),
1535+
NotNull());
1536+
ASSERT_THAT(cast<IntegerValue>(
1537+
getFieldValue(&MD, *findValueDecl(ASTCtx, "most_derived"), Env)),
1538+
NotNull());
1539+
});
1540+
}
1541+
14491542
TEST(TransferTest, ReferenceMember) {
14501543
std::string Code = R"(
14511544
struct A {
@@ -2051,6 +2144,26 @@ TEST(TransferTest, AssignmentOperatorFromCallResult) {
20512144
});
20522145
}
20532146

2147+
TEST(TransferTest, AssignmentOperatorWithInitAndInheritance) {
2148+
// This is a crash repro.
2149+
std::string Code = R"(
2150+
struct B { int Foo; };
2151+
struct S : public B {};
2152+
void target() {
2153+
S S1 = { 1 };
2154+
S S2;
2155+
S S3;
2156+
S1 = S2; // Only Dst has InitListExpr.
2157+
S3 = S1; // Only Src has InitListExpr.
2158+
// [[p]]
2159+
}
2160+
)";
2161+
runDataflow(
2162+
Code,
2163+
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
2164+
ASTContext &ASTCtx) {});
2165+
}
2166+
20542167
TEST(TransferTest, CopyConstructor) {
20552168
std::string Code = R"(
20562169
struct A {

0 commit comments

Comments
 (0)