Skip to content

Commit ca10343

Browse files
authored
[clang][dataflow] Fix an issue with Environment::getResultObjectLocation(). (llvm#75483)
So far, if there was a chain of record type prvalues, `getResultObjectLocation()` would assign a different result object location to each one. This makes no sense, of course, as all of these prvalues end up initializing the same result object. This patch fixes this by propagating storage locations up through the entire chain of prvalues. The new implementation also has the desirable effect of making it possible to make `getResultObjectLocation()` const, which seems appropriate given that, logically, it is just an accessor.
1 parent 9bb47f7 commit ca10343

File tree

4 files changed

+123
-47
lines changed

4 files changed

+123
-47
lines changed

clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,8 @@ class Environment {
325325
///
326326
/// Requirements:
327327
/// `E` must be a prvalue of record type.
328-
RecordStorageLocation &getResultObjectLocation(const Expr &RecordPRValue);
328+
RecordStorageLocation &
329+
getResultObjectLocation(const Expr &RecordPRValue) const;
329330

330331
/// Returns the return value of the current function. This can be null if:
331332
/// - The function has a void return type
@@ -434,24 +435,14 @@ class Environment {
434435

435436
/// Assigns `Val` as the value of the prvalue `E` in the environment.
436437
///
437-
/// If `E` is not yet associated with a storage location, associates it with
438-
/// a newly created storage location. In any case, associates the storage
439-
/// location of `E` with `Val`.
440-
///
441-
/// Once the migration to strict handling of value categories is complete
442-
/// (see https://discourse.llvm.org/t/70086), this function will be renamed to
443-
/// `setValue()`. At this point, prvalue expressions will be associated
444-
/// directly with `Value`s, and the legacy behavior of associating prvalue
445-
/// expressions with storage locations (as described above) will be
446-
/// eliminated.
447-
///
448438
/// Requirements:
449439
///
450-
/// `E` must be a prvalue
451-
/// If `Val` is a `RecordValue`, its `RecordStorageLocation` must be the
452-
/// same as that of any `RecordValue` that has already been associated with
453-
/// `E`. This is to guarantee that the result object initialized by a prvalue
454-
/// `RecordValue` has a durable storage location.
440+
/// - `E` must be a prvalue
441+
/// - If `Val` is a `RecordValue`, its `RecordStorageLocation` must be
442+
/// `getResultObjectLocation(E)`. An exception to this is if `E` is an
443+
/// expression that originally creates a `RecordValue` (such as a
444+
/// `CXXConstructExpr` or `CallExpr`), as these establish the location of
445+
/// the result object in the first place.
455446
void setValue(const Expr &E, Value &Val);
456447

457448
/// Returns the value assigned to `Loc` in the environment or null if `Loc`
@@ -608,14 +599,6 @@ class Environment {
608599
// The copy-constructor is for use in fork() only.
609600
Environment(const Environment &) = default;
610601

611-
/// Internal version of `setStorageLocation()` that doesn't check if the
612-
/// expression is a prvalue.
613-
void setStorageLocationInternal(const Expr &E, StorageLocation &Loc);
614-
615-
/// Internal version of `getStorageLocation()` that doesn't check if the
616-
/// expression is a prvalue.
617-
StorageLocation *getStorageLocationInternal(const Expr &E) const;
618-
619602
/// Creates a value appropriate for `Type`, if `Type` is supported, otherwise
620603
/// return null.
621604
///

clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -726,27 +726,70 @@ void Environment::setStorageLocation(const Expr &E, StorageLocation &Loc) {
726726
// so allow these as an exception.
727727
assert(E.isGLValue() ||
728728
E.getType()->isSpecificBuiltinType(BuiltinType::BuiltinFn));
729-
setStorageLocationInternal(E, Loc);
729+
const Expr &CanonE = ignoreCFGOmittedNodes(E);
730+
assert(!ExprToLoc.contains(&CanonE));
731+
ExprToLoc[&CanonE] = &Loc;
730732
}
731733

732734
StorageLocation *Environment::getStorageLocation(const Expr &E) const {
733735
// See comment in `setStorageLocation()`.
734736
assert(E.isGLValue() ||
735737
E.getType()->isSpecificBuiltinType(BuiltinType::BuiltinFn));
736-
return getStorageLocationInternal(E);
738+
auto It = ExprToLoc.find(&ignoreCFGOmittedNodes(E));
739+
return It == ExprToLoc.end() ? nullptr : &*It->second;
740+
}
741+
742+
// Returns whether a prvalue of record type is the one that originally
743+
// constructs the object (i.e. it doesn't propagate it from one of its
744+
// children).
745+
static bool isOriginalRecordConstructor(const Expr &RecordPRValue) {
746+
if (auto *Init = dyn_cast<InitListExpr>(&RecordPRValue))
747+
return !Init->isSemanticForm() || !Init->isTransparent();
748+
return isa<CXXConstructExpr>(RecordPRValue) || isa<CallExpr>(RecordPRValue) ||
749+
isa<LambdaExpr>(RecordPRValue) ||
750+
// The framework currently does not propagate the objects created in
751+
// the two branches of a `ConditionalOperator` because there is no way
752+
// to reconcile their storage locations, which are different. We
753+
// therefore claim that the `ConditionalOperator` is the expression
754+
// that originally constructs the object.
755+
// Ultimately, this will be fixed by propagating locations down from
756+
// the result object, rather than up from the original constructor as
757+
// we do now (see also the FIXME in the documentation for
758+
// `getResultObjectLocation()`).
759+
isa<ConditionalOperator>(RecordPRValue);
737760
}
738761

739762
RecordStorageLocation &
740-
Environment::getResultObjectLocation(const Expr &RecordPRValue) {
763+
Environment::getResultObjectLocation(const Expr &RecordPRValue) const {
741764
assert(RecordPRValue.getType()->isRecordType());
742765
assert(RecordPRValue.isPRValue());
743766

744-
if (StorageLocation *ExistingLoc = getStorageLocationInternal(RecordPRValue))
745-
return *cast<RecordStorageLocation>(ExistingLoc);
746-
auto &Loc = cast<RecordStorageLocation>(
747-
DACtx->getStableStorageLocation(RecordPRValue));
748-
setStorageLocationInternal(RecordPRValue, Loc);
749-
return Loc;
767+
// Returns a storage location that we can use if assertions fail.
768+
auto FallbackForAssertFailure =
769+
[this, &RecordPRValue]() -> RecordStorageLocation & {
770+
return cast<RecordStorageLocation>(
771+
DACtx->getStableStorageLocation(RecordPRValue));
772+
};
773+
774+
if (isOriginalRecordConstructor(RecordPRValue)) {
775+
auto *Val = cast_or_null<RecordValue>(getValue(RecordPRValue));
776+
// The builtin transfer function should have created a `RecordValue` for all
777+
// original record constructors.
778+
assert(Val);
779+
if (!Val)
780+
return FallbackForAssertFailure();
781+
return Val->getLoc();
782+
}
783+
784+
// Expression nodes that propagate a record prvalue should have exactly one
785+
// child.
786+
llvm::SmallVector<const Stmt *> children(RecordPRValue.child_begin(),
787+
RecordPRValue.child_end());
788+
assert(children.size() == 1);
789+
if (children.empty())
790+
return FallbackForAssertFailure();
791+
792+
return getResultObjectLocation(*cast<Expr>(children[0]));
750793
}
751794

752795
PointerValue &Environment::getOrCreateNullPointerValue(QualType PointeeType) {
@@ -760,6 +803,11 @@ void Environment::setValue(const StorageLocation &Loc, Value &Val) {
760803
}
761804

762805
void Environment::setValue(const Expr &E, Value &Val) {
806+
if (auto *RecordVal = dyn_cast<RecordValue>(&Val)) {
807+
assert(isOriginalRecordConstructor(E) ||
808+
&RecordVal->getLoc() == &getResultObjectLocation(E));
809+
}
810+
763811
assert(E.isPRValue());
764812
ExprToVal[&E] = &Val;
765813
}
@@ -799,18 +847,6 @@ Value *Environment::createValue(QualType Type) {
799847
return Val;
800848
}
801849

802-
void Environment::setStorageLocationInternal(const Expr &E,
803-
StorageLocation &Loc) {
804-
const Expr &CanonE = ignoreCFGOmittedNodes(E);
805-
assert(!ExprToLoc.contains(&CanonE));
806-
ExprToLoc[&CanonE] = &Loc;
807-
}
808-
809-
StorageLocation *Environment::getStorageLocationInternal(const Expr &E) const {
810-
auto It = ExprToLoc.find(&ignoreCFGOmittedNodes(E));
811-
return It == ExprToLoc.end() ? nullptr : &*It->second;
812-
}
813-
814850
Value *Environment::createValueUnlessSelfReferential(
815851
QualType Type, llvm::DenseSet<QualType> &Visited, int Depth,
816852
int &CreatedValuesCount) {
@@ -1044,6 +1080,7 @@ RecordValue &refreshRecordValue(const Expr &Expr, Environment &Env) {
10441080
if (auto *ExistingVal = cast_or_null<RecordValue>(Env.getValue(Expr))) {
10451081
auto &NewVal = Env.create<RecordValue>(ExistingVal->getLoc());
10461082
Env.setValue(Expr, NewVal);
1083+
Env.setValue(NewVal.getLoc(), NewVal);
10471084
return NewVal;
10481085
}
10491086

clang/lib/Analysis/FlowSensitive/Transfer.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,6 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
489489
if (S->getType()->isRecordType()) {
490490
auto &InitialVal = *cast<RecordValue>(Env.createValue(S->getType()));
491491
Env.setValue(*S, InitialVal);
492-
copyRecord(InitialVal.getLoc(), Env.getResultObjectLocation(*S), Env);
493492
}
494493

495494
transferInlineCall(S, ConstructorDecl);
@@ -582,6 +581,14 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
582581
Env.setValue(*S, *ArgVal);
583582
} else if (const FunctionDecl *F = S->getDirectCallee()) {
584583
transferInlineCall(S, F);
584+
585+
// If this call produces a prvalue of record type, make sure that we have
586+
// a `RecordValue` for it. This is required so that
587+
// `Environment::getResultObjectLocation()` is able to return a location
588+
// for this `CallExpr`.
589+
if (S->getType()->isRecordType() && S->isPRValue())
590+
if (Env.getValue(*S) == nullptr)
591+
refreshRecordValue(*S, Env);
585592
}
586593
}
587594

clang/unittests/Analysis/FlowSensitive/TransferTest.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2635,6 +2635,55 @@ TEST(TransferTest, BindTemporary) {
26352635
});
26362636
}
26372637

2638+
TEST(TransferTest, ResultObjectLocation) {
2639+
std::string Code = R"(
2640+
struct A {
2641+
virtual ~A() = default;
2642+
};
2643+
2644+
void target() {
2645+
A();
2646+
(void)0; // [[p]]
2647+
}
2648+
)";
2649+
using ast_matchers::cxxBindTemporaryExpr;
2650+
using ast_matchers::cxxTemporaryObjectExpr;
2651+
using ast_matchers::exprWithCleanups;
2652+
using ast_matchers::has;
2653+
using ast_matchers::match;
2654+
using ast_matchers::selectFirst;
2655+
using ast_matchers::traverse;
2656+
runDataflow(
2657+
Code,
2658+
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
2659+
ASTContext &ASTCtx) {
2660+
const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
2661+
2662+
// The expresssion `A()` in the code above produces the following
2663+
// structure, consisting of three prvalues of record type.
2664+
// `Env.getResultObjectLocation()` should return the same location for
2665+
// all of these.
2666+
auto MatchResult = match(
2667+
traverse(TK_AsIs,
2668+
exprWithCleanups(
2669+
has(cxxBindTemporaryExpr(
2670+
has(cxxTemporaryObjectExpr().bind("toe")))
2671+
.bind("bte")))
2672+
.bind("ewc")),
2673+
ASTCtx);
2674+
auto *TOE = selectFirst<CXXTemporaryObjectExpr>("toe", MatchResult);
2675+
ASSERT_NE(TOE, nullptr);
2676+
auto *EWC = selectFirst<ExprWithCleanups>("ewc", MatchResult);
2677+
ASSERT_NE(EWC, nullptr);
2678+
auto *BTE = selectFirst<CXXBindTemporaryExpr>("bte", MatchResult);
2679+
ASSERT_NE(BTE, nullptr);
2680+
2681+
RecordStorageLocation &Loc = Env.getResultObjectLocation(*TOE);
2682+
EXPECT_EQ(&Loc, &Env.getResultObjectLocation(*EWC));
2683+
EXPECT_EQ(&Loc, &Env.getResultObjectLocation(*BTE));
2684+
});
2685+
}
2686+
26382687
TEST(TransferTest, StaticCast) {
26392688
std::string Code = R"(
26402689
void target(int Foo) {

0 commit comments

Comments
 (0)