Skip to content

Commit 6441358

Browse files
committed
[clang][dataflow] Add support for return values of reference type.
This patch changes the way `Environment::ReturnLoc` is set: Whereas previously it was set by the caller, it is now set by the callee (obviously, as we otherwise would not be able to return references). The patch also introduces `Environment::ReturnVal`, which is used for non-reference-type return values. This allows these to be handled with the correct value category semantics; see also https://discourse.llvm.org/t/70086, which describes the ongoing migration to strict value category semantics. Depends On D150776 Reviewed By: ymandel, xazax.hun Differential Revision: https://reviews.llvm.org/D151194
1 parent 6fdc77e commit 6441358

File tree

4 files changed

+239
-63
lines changed

4 files changed

+239
-63
lines changed

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

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,8 @@ class Environment {
192192

193193
/// Moves gathered information back into `this` from a `CalleeEnv` created via
194194
/// `pushCall`.
195-
void popCall(const Environment &CalleeEnv);
195+
void popCall(const CallExpr *Call, const Environment &CalleeEnv);
196+
void popCall(const CXXConstructExpr *Call, const Environment &CalleeEnv);
196197

197198
/// Returns true if and only if the environment is equivalent to `Other`, i.e
198199
/// the two environments:
@@ -323,8 +324,51 @@ class Environment {
323324
/// in the environment.
324325
StorageLocation *getThisPointeeStorageLocation() const;
325326

326-
/// Returns the storage location of the return value or null, if unset.
327-
StorageLocation *getReturnStorageLocation() const;
327+
/// Returns the return value of the current function. This can be null if:
328+
/// - The function has a void return type
329+
/// - No return value could be determined for the function, for example
330+
/// because it calls a function without a body.
331+
///
332+
/// Requirements:
333+
/// The current function must have a non-reference return type.
334+
Value *getReturnValue() const {
335+
assert(getCurrentFunc() != nullptr &&
336+
!getCurrentFunc()->getReturnType()->isReferenceType());
337+
return ReturnVal;
338+
}
339+
340+
/// Returns the storage location for the reference returned by the current
341+
/// function. This can be null if function doesn't return a single consistent
342+
/// reference.
343+
///
344+
/// Requirements:
345+
/// The current function must have a reference return type.
346+
StorageLocation *getReturnStorageLocation() const {
347+
assert(getCurrentFunc() != nullptr &&
348+
getCurrentFunc()->getReturnType()->isReferenceType());
349+
return ReturnLoc;
350+
}
351+
352+
/// Sets the return value of the current function.
353+
///
354+
/// Requirements:
355+
/// The current function must have a non-reference return type.
356+
void setReturnValue(Value *Val) {
357+
assert(getCurrentFunc() != nullptr &&
358+
!getCurrentFunc()->getReturnType()->isReferenceType());
359+
ReturnVal = Val;
360+
}
361+
362+
/// Sets the storage location for the reference returned by the current
363+
/// function.
364+
///
365+
/// Requirements:
366+
/// The current function must have a reference return type.
367+
void setReturnStorageLocation(StorageLocation *Loc) {
368+
assert(getCurrentFunc() != nullptr &&
369+
getCurrentFunc()->getReturnType()->isReferenceType());
370+
ReturnLoc = Loc;
371+
}
328372

329373
/// Returns a pointer value that represents a null pointer. Calls with
330374
/// `PointeeType` that are canonically equivalent will return the same result.
@@ -472,6 +516,12 @@ class Environment {
472516
/// returns null.
473517
const DeclContext *getDeclCtx() const { return CallStack.back(); }
474518

519+
/// Returns the function currently being analyzed, or null if the code being
520+
/// analyzed isn't part of a function.
521+
const FunctionDecl *getCurrentFunc() const {
522+
return dyn_cast<FunctionDecl>(getDeclCtx());
523+
}
524+
475525
/// Returns whether this `Environment` can be extended to analyze the given
476526
/// `Callee` (i.e. if `pushCall` can be used), with recursion disallowed and a
477527
/// given `MaxDepth`.
@@ -515,16 +565,18 @@ class Environment {
515565
// `DACtx` is not null and not owned by this object.
516566
DataflowAnalysisContext *DACtx;
517567

518-
519-
// FIXME: move the fields `CallStack`, `ReturnLoc` and `ThisPointeeLoc` into a
520-
// separate call-context object, shared between environments in the same call.
568+
// FIXME: move the fields `CallStack`, `ReturnVal`, `ReturnLoc` and
569+
// `ThisPointeeLoc` into a separate call-context object, shared between
570+
// environments in the same call.
521571
// https://github.com/llvm/llvm-project/issues/59005
522572

523573
// `DeclContext` of the block being analysed if provided.
524574
std::vector<const DeclContext *> CallStack;
525575

526-
// In a properly initialized `Environment`, `ReturnLoc` should only be null if
527-
// its `DeclContext` could not be cast to a `FunctionDecl`.
576+
// Value returned by the function (if it has non-reference return type).
577+
Value *ReturnVal = nullptr;
578+
// Storage location of the reference returned by the function (if it has
579+
// reference return type).
528580
StorageLocation *ReturnLoc = nullptr;
529581
// The storage location of the `this` pointee. Should only be null if the
530582
// function being analyzed is only a function and not a method.

clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp

Lines changed: 74 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -267,9 +267,10 @@ Environment::Environment(DataflowAnalysisContext &DACtx)
267267

268268
Environment::Environment(const Environment &Other)
269269
: DACtx(Other.DACtx), CallStack(Other.CallStack),
270-
ReturnLoc(Other.ReturnLoc), ThisPointeeLoc(Other.ThisPointeeLoc),
271-
DeclToLoc(Other.DeclToLoc), ExprToLoc(Other.ExprToLoc),
272-
LocToVal(Other.LocToVal), MemberLocToStruct(Other.MemberLocToStruct),
270+
ReturnVal(Other.ReturnVal), ReturnLoc(Other.ReturnLoc),
271+
ThisPointeeLoc(Other.ThisPointeeLoc), DeclToLoc(Other.DeclToLoc),
272+
ExprToLoc(Other.ExprToLoc), LocToVal(Other.LocToVal),
273+
MemberLocToStruct(Other.MemberLocToStruct),
273274
FlowConditionToken(&DACtx->forkFlowCondition(*Other.FlowConditionToken)) {
274275
}
275276

@@ -302,9 +303,6 @@ Environment::Environment(DataflowAnalysisContext &DACtx,
302303
createValue(ParamDecl->getType().getNonReferenceType()))
303304
setValue(ParamLoc, *ParamVal);
304305
}
305-
306-
QualType ReturnType = FuncDecl->getReturnType();
307-
ReturnLoc = &createStorageLocation(ReturnType);
308306
}
309307

310308
if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(&DeclCtx)) {
@@ -331,9 +329,6 @@ bool Environment::canDescend(unsigned MaxDepth,
331329
Environment Environment::pushCall(const CallExpr *Call) const {
332330
Environment Env(*this);
333331

334-
// FIXME: Support references here.
335-
Env.ReturnLoc = getStorageLocation(*Call, SkipPast::Reference);
336-
337332
if (const auto *MethodCall = dyn_cast<CXXMemberCallExpr>(Call)) {
338333
if (const Expr *Arg = MethodCall->getImplicitObjectArgument()) {
339334
if (!isa<CXXThisExpr>(Arg))
@@ -352,10 +347,9 @@ Environment Environment::pushCall(const CallExpr *Call) const {
352347
Environment Environment::pushCall(const CXXConstructExpr *Call) const {
353348
Environment Env(*this);
354349

355-
// FIXME: Support references here.
356-
Env.ReturnLoc = getStorageLocation(*Call, SkipPast::Reference);
357-
358-
Env.ThisPointeeLoc = Env.ReturnLoc;
350+
Env.ThisPointeeLoc = &Env.createStorageLocation(Call->getType());
351+
if (Value *Val = Env.createValue(Call->getType()))
352+
Env.setValue(*Env.ThisPointeeLoc, *Val);
359353

360354
Env.pushCallInternal(Call->getConstructor(),
361355
llvm::ArrayRef(Call->getArgs(), Call->getNumArgs()));
@@ -365,6 +359,12 @@ Environment Environment::pushCall(const CXXConstructExpr *Call) const {
365359

366360
void Environment::pushCallInternal(const FunctionDecl *FuncDecl,
367361
ArrayRef<const Expr *> Args) {
362+
// Canonicalize to the definition of the function. This ensures that we're
363+
// putting arguments into the same `ParamVarDecl`s` that the callee will later
364+
// be retrieving them from.
365+
assert(FuncDecl->getDefinition() != nullptr);
366+
FuncDecl = FuncDecl->getDefinition();
367+
368368
CallStack.push_back(FuncDecl);
369369

370370
initFieldsGlobalsAndFuncs(FuncDecl);
@@ -399,23 +399,46 @@ void Environment::pushCallInternal(const FunctionDecl *FuncDecl,
399399
}
400400
}
401401

402-
void Environment::popCall(const Environment &CalleeEnv) {
402+
void Environment::popCall(const CallExpr *Call, const Environment &CalleeEnv) {
403403
// We ignore `DACtx` because it's already the same in both. We don't want the
404-
// callee's `DeclCtx`, `ReturnLoc` or `ThisPointeeLoc`. We don't bring back
405-
// `DeclToLoc` and `ExprToLoc` because we want to be able to later analyze the
406-
// same callee in a different context, and `setStorageLocation` requires there
407-
// to not already be a storage location assigned. Conceptually, these maps
408-
// capture information from the local scope, so when popping that scope, we do
409-
// not propagate the maps.
404+
// callee's `DeclCtx`, `ReturnVal`, `ReturnLoc` or `ThisPointeeLoc`. We don't
405+
// bring back `DeclToLoc` and `ExprToLoc` because we want to be able to later
406+
// analyze the same callee in a different context, and `setStorageLocation`
407+
// requires there to not already be a storage location assigned. Conceptually,
408+
// these maps capture information from the local scope, so when popping that
409+
// scope, we do not propagate the maps.
410410
this->LocToVal = std::move(CalleeEnv.LocToVal);
411411
this->MemberLocToStruct = std::move(CalleeEnv.MemberLocToStruct);
412412
this->FlowConditionToken = std::move(CalleeEnv.FlowConditionToken);
413+
414+
if (Call->isGLValue()) {
415+
if (CalleeEnv.ReturnLoc != nullptr)
416+
setStorageLocationStrict(*Call, *CalleeEnv.ReturnLoc);
417+
} else if (!Call->getType()->isVoidType()) {
418+
if (CalleeEnv.ReturnVal != nullptr)
419+
setValueStrict(*Call, *CalleeEnv.ReturnVal);
420+
}
421+
}
422+
423+
void Environment::popCall(const CXXConstructExpr *Call,
424+
const Environment &CalleeEnv) {
425+
// See also comment in `popCall(const CallExpr *, const Environment &)` above.
426+
this->LocToVal = std::move(CalleeEnv.LocToVal);
427+
this->MemberLocToStruct = std::move(CalleeEnv.MemberLocToStruct);
428+
this->FlowConditionToken = std::move(CalleeEnv.FlowConditionToken);
429+
430+
if (Value *Val = CalleeEnv.getValue(*CalleeEnv.ThisPointeeLoc)) {
431+
setValueStrict(*Call, *Val);
432+
}
413433
}
414434

415435
bool Environment::equivalentTo(const Environment &Other,
416436
Environment::ValueModel &Model) const {
417437
assert(DACtx == Other.DACtx);
418438

439+
if (ReturnVal != Other.ReturnVal)
440+
return false;
441+
419442
if (ReturnLoc != Other.ReturnLoc)
420443
return false;
421444

@@ -453,6 +476,7 @@ bool Environment::equivalentTo(const Environment &Other,
453476
LatticeJoinEffect Environment::widen(const Environment &PrevEnv,
454477
Environment::ValueModel &Model) {
455478
assert(DACtx == PrevEnv.DACtx);
479+
assert(ReturnVal == PrevEnv.ReturnVal);
456480
assert(ReturnLoc == PrevEnv.ReturnLoc);
457481
assert(ThisPointeeLoc == PrevEnv.ThisPointeeLoc);
458482
assert(CallStack == PrevEnv.CallStack);
@@ -514,7 +538,6 @@ LatticeJoinEffect Environment::widen(const Environment &PrevEnv,
514538
LatticeJoinEffect Environment::join(const Environment &Other,
515539
Environment::ValueModel &Model) {
516540
assert(DACtx == Other.DACtx);
517-
assert(ReturnLoc == Other.ReturnLoc);
518541
assert(ThisPointeeLoc == Other.ThisPointeeLoc);
519542
assert(CallStack == Other.CallStack);
520543

@@ -523,9 +546,38 @@ LatticeJoinEffect Environment::join(const Environment &Other,
523546
Environment JoinedEnv(*DACtx);
524547

525548
JoinedEnv.CallStack = CallStack;
526-
JoinedEnv.ReturnLoc = ReturnLoc;
527549
JoinedEnv.ThisPointeeLoc = ThisPointeeLoc;
528550

551+
if (ReturnVal == nullptr || Other.ReturnVal == nullptr) {
552+
// `ReturnVal` might not always get set -- for example if we have a return
553+
// statement of the form `return some_other_func()` and we decide not to
554+
// analyze `some_other_func()`.
555+
// In this case, we can't say anything about the joined return value -- we
556+
// don't simply want to propagate the return value that we do have, because
557+
// it might not be the correct one.
558+
// This occurs for example in the test `ContextSensitiveMutualRecursion`.
559+
JoinedEnv.ReturnVal = nullptr;
560+
} else if (areEquivalentValues(*ReturnVal, *Other.ReturnVal)) {
561+
JoinedEnv.ReturnVal = ReturnVal;
562+
} else {
563+
assert(!CallStack.empty());
564+
// FIXME: Make `CallStack` a vector of `FunctionDecl` so we don't need this
565+
// cast.
566+
auto *Func = dyn_cast<FunctionDecl>(CallStack.back());
567+
assert(Func != nullptr);
568+
if (Value *MergedVal =
569+
mergeDistinctValues(Func->getReturnType(), *ReturnVal, *this,
570+
*Other.ReturnVal, Other, JoinedEnv, Model)) {
571+
JoinedEnv.ReturnVal = MergedVal;
572+
Effect = LatticeJoinEffect::Changed;
573+
}
574+
}
575+
576+
if (ReturnLoc == Other.ReturnLoc)
577+
JoinedEnv.ReturnLoc = ReturnLoc;
578+
else
579+
JoinedEnv.ReturnLoc = nullptr;
580+
529581
// FIXME: Once we're able to remove declarations from `DeclToLoc` when their
530582
// lifetime ends, add an assertion that there aren't any entries in
531583
// `DeclToLoc` and `Other.DeclToLoc` that map the same declaration to
@@ -659,10 +711,6 @@ StorageLocation *Environment::getThisPointeeStorageLocation() const {
659711
return ThisPointeeLoc;
660712
}
661713

662-
StorageLocation *Environment::getReturnStorageLocation() const {
663-
return ReturnLoc;
664-
}
665-
666714
PointerValue &Environment::getOrCreateNullPointerValue(QualType PointeeType) {
667715
return DACtx->getOrCreateNullPointerValue(PointeeType);
668716
}

clang/lib/Analysis/FlowSensitive/Transfer.cpp

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -503,22 +503,21 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
503503
if (Ret == nullptr)
504504
return;
505505

506-
auto *Val = Env.getValue(*Ret, SkipPast::None);
507-
if (Val == nullptr)
508-
return;
509-
510-
// FIXME: Support reference-type returns.
511-
if (Val->getKind() == Value::Kind::Reference)
512-
return;
506+
if (Ret->isPRValue()) {
507+
auto *Val = Env.getValueStrict(*Ret);
508+
if (Val == nullptr)
509+
return;
513510

514-
auto *Loc = Env.getReturnStorageLocation();
515-
assert(Loc != nullptr);
516-
// FIXME: Support reference-type returns.
517-
if (Loc->getType()->isReferenceType())
518-
return;
511+
// FIXME: Model NRVO.
512+
Env.setReturnValue(Val);
513+
} else {
514+
auto *Loc = Env.getStorageLocationStrict(*Ret);
515+
if (Loc == nullptr)
516+
return;
519517

520-
// FIXME: Model NRVO.
521-
Env.setValue(*Loc, *Val);
518+
// FIXME: Model NRVO.
519+
Env.setReturnStorageLocation(Loc);
520+
}
522521
}
523522

524523
void VisitMemberExpr(const MemberExpr *S) {
@@ -857,13 +856,6 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
857856

858857
auto ExitBlock = CFCtx->getCFG().getExit().getBlockID();
859858

860-
if (const auto *NonConstructExpr = dyn_cast<CallExpr>(S)) {
861-
// Note that it is important for the storage location of `S` to be set
862-
// before `pushCall`, because the latter uses it to set the storage
863-
// location for `return`.
864-
auto &ReturnLoc = Env.createStorageLocation(*S);
865-
Env.setStorageLocation(*S, ReturnLoc);
866-
}
867859
auto CalleeEnv = Env.pushCall(S);
868860

869861
// FIXME: Use the same analysis as the caller for the callee. Note,
@@ -882,7 +874,7 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
882874
auto ExitState = (*BlockToOutputState)[ExitBlock];
883875
assert(ExitState);
884876

885-
Env.popCall(ExitState->Env);
877+
Env.popCall(S, ExitState->Env);
886878
}
887879

888880
const StmtToEnvMap &StmtToEnv;

0 commit comments

Comments
 (0)