Skip to content

Commit 0685f09

Browse files
committed
[analyzer] Avoid use of CallEvents with obsolete state
The method `ExprEngine::evalCall` handles multiple state transitions and activates various checker callbacks that take a `CallEvent` parameter (among other parameters). Unfortunately some of these callbacks (EvalCall and pointer escape) were called with a `CallEvent` instance whose attached state was obsolete. This commit fixes this inconsistency by attaching the right state to the `CallEvent`s before their state becomes relevant. I found these inconsistencies as I was trying to understand this part of the source code, so I don't know about any concrete bugs that are caused by them -- but they are definitely fishy. I think it would be nice to handle the "has right state" / "has undefined state" distinction statically within the type system (to prevent the reoccurrence of similar inconsistencies), but I opted for just fixing the current issues as a first step.
1 parent d62505f commit 0685f09

File tree

2 files changed

+48
-36
lines changed

2 files changed

+48
-36
lines changed

clang/lib/StaticAnalyzer/Core/CheckerManager.cpp

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -731,33 +731,36 @@ void CheckerManager::runCheckersForEvalCall(ExplodedNodeSet &Dst,
731731
ExplodedNodeSet checkDst;
732732
NodeBuilder B(Pred, checkDst, Eng.getBuilderContext());
733733

734+
ProgramStateRef State = Pred->getState();
735+
CallEventRef<> UpdatedCall = Call.cloneWithState(State);
736+
734737
// Check if any of the EvalCall callbacks can evaluate the call.
735738
for (const auto &EvalCallChecker : EvalCallCheckers) {
736739
// TODO: Support the situation when the call doesn't correspond
737740
// to any Expr.
738741
ProgramPoint L = ProgramPoint::getProgramPoint(
739-
Call.getOriginExpr(), ProgramPoint::PostStmtKind,
742+
UpdatedCall->getOriginExpr(), ProgramPoint::PostStmtKind,
740743
Pred->getLocationContext(), EvalCallChecker.Checker);
741744
bool evaluated = false;
742745
{ // CheckerContext generates transitions(populates checkDest) on
743746
// destruction, so introduce the scope to make sure it gets properly
744747
// populated.
745748
CheckerContext C(B, Eng, Pred, L);
746-
evaluated = EvalCallChecker(Call, C);
749+
evaluated = EvalCallChecker(*UpdatedCall, C);
747750
}
748751
#ifndef NDEBUG
749752
if (evaluated && evaluatorChecker) {
750-
const auto toString = [](const CallEvent &Call) -> std::string {
753+
const auto toString = [](CallEventRef<> Call) -> std::string {
751754
std::string Buf;
752755
llvm::raw_string_ostream OS(Buf);
753-
Call.dump(OS);
756+
Call->dump(OS);
754757
return Buf;
755758
};
756759
std::string AssertionMessage = llvm::formatv(
757760
"The '{0}' call has been already evaluated by the {1} checker, "
758761
"while the {2} checker also tried to evaluate the same call. At "
759762
"most one checker supposed to evaluate a call.",
760-
toString(Call), evaluatorChecker,
763+
toString(UpdatedCall), evaluatorChecker,
761764
EvalCallChecker.Checker->getDebugTag());
762765
llvm_unreachable(AssertionMessage.c_str());
763766
}
@@ -774,7 +777,7 @@ void CheckerManager::runCheckersForEvalCall(ExplodedNodeSet &Dst,
774777
// If none of the checkers evaluated the call, ask ExprEngine to handle it.
775778
if (!evaluatorChecker) {
776779
NodeBuilder B(Pred, Dst, Eng.getBuilderContext());
777-
Eng.defaultEvalCall(B, Pred, Call, CallOpts);
780+
Eng.defaultEvalCall(B, Pred, *UpdatedCall, CallOpts);
778781
}
779782
}
780783
}

clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,8 @@ void ExprEngine::VisitCallExpr(const CallExpr *CE, ExplodedNode *Pred,
628628

629629
ProgramStateRef ExprEngine::finishArgumentConstruction(ProgramStateRef State,
630630
const CallEvent &Call) {
631+
// WARNING: The state attached to 'Call' may be obsolete, do not call any
632+
// methods that rely on it!
631633
const Expr *E = Call.getOriginExpr();
632634
// FIXME: Constructors to placement arguments of operator new
633635
// are not supported yet.
@@ -653,6 +655,8 @@ ProgramStateRef ExprEngine::finishArgumentConstruction(ProgramStateRef State,
653655
void ExprEngine::finishArgumentConstruction(ExplodedNodeSet &Dst,
654656
ExplodedNode *Pred,
655657
const CallEvent &Call) {
658+
// WARNING: The state attached to 'Call' may be obsolete, do not call any
659+
// methods that rely on it!
656660
ProgramStateRef State = Pred->getState();
657661
ProgramStateRef CleanedState = finishArgumentConstruction(State, Call);
658662
if (CleanedState == State) {
@@ -670,35 +674,40 @@ void ExprEngine::finishArgumentConstruction(ExplodedNodeSet &Dst,
670674
}
671675

672676
void ExprEngine::evalCall(ExplodedNodeSet &Dst, ExplodedNode *Pred,
673-
const CallEvent &Call) {
674-
// WARNING: At this time, the state attached to 'Call' may be older than the
675-
// state in 'Pred'. This is a minor optimization since CheckerManager will
676-
// use an updated CallEvent instance when calling checkers, but if 'Call' is
677-
// ever used directly in this function all callers should be updated to pass
678-
// the most recent state. (It is probably not worth doing the work here since
679-
// for some callers this will not be necessary.)
677+
const CallEvent &CallTemplate) {
678+
// WARNING: As this function performs transitions between several different
679+
// states (perhaps in a branching structure) we must be careful to avoid
680+
// referencing obsolete or irrelevant states. In particular, 'CallEvent'
681+
// instances have an attached state (because this is is convenient within the
682+
// checker callbacks) and it is our responsibility to keep these up-to-date.
683+
// In fact, the parameter 'CallTemplate' is a "template" because its attached
684+
// state may be older than the state of 'Pred' (which will be further
685+
// transformed by the transitions within this method).
686+
// (Note that 'runCheckersFor*Call' and 'finishArgumentConstruction' are
687+
// prepared to take this template and and attach the proper state before
688+
// forwarding it to the checkers.)
680689

681690
// Run any pre-call checks using the generic call interface.
682691
ExplodedNodeSet dstPreVisit;
683-
getCheckerManager().runCheckersForPreCall(dstPreVisit, Pred,
684-
Call, *this);
692+
getCheckerManager().runCheckersForPreCall(dstPreVisit, Pred, CallTemplate,
693+
*this);
685694

686695
// Actually evaluate the function call. We try each of the checkers
687696
// to see if the can evaluate the function call, and get a callback at
688697
// defaultEvalCall if all of them fail.
689698
ExplodedNodeSet dstCallEvaluated;
690-
getCheckerManager().runCheckersForEvalCall(dstCallEvaluated, dstPreVisit,
691-
Call, *this, EvalCallOptions());
699+
getCheckerManager().runCheckersForEvalCall(
700+
dstCallEvaluated, dstPreVisit, CallTemplate, *this, EvalCallOptions());
692701

693702
// If there were other constructors called for object-type arguments
694703
// of this call, clean them up.
695704
ExplodedNodeSet dstArgumentCleanup;
696705
for (ExplodedNode *I : dstCallEvaluated)
697-
finishArgumentConstruction(dstArgumentCleanup, I, Call);
706+
finishArgumentConstruction(dstArgumentCleanup, I, CallTemplate);
698707

699708
ExplodedNodeSet dstPostCall;
700709
getCheckerManager().runCheckersForPostCall(dstPostCall, dstArgumentCleanup,
701-
Call, *this);
710+
CallTemplate, *this);
702711

703712
// Escaping symbols conjured during invalidating the regions above.
704713
// Note that, for inlined calls the nodes were put back into the worklist,
@@ -708,12 +717,13 @@ void ExprEngine::evalCall(ExplodedNodeSet &Dst, ExplodedNode *Pred,
708717
// Run pointerEscape callback with the newly conjured symbols.
709718
SmallVector<std::pair<SVal, SVal>, 8> Escaped;
710719
for (ExplodedNode *I : dstPostCall) {
711-
NodeBuilder B(I, Dst, *currBldrCtx);
712720
ProgramStateRef State = I->getState();
721+
CallEventRef<> Call = CallTemplate.cloneWithState(State);
722+
NodeBuilder B(I, Dst, *currBldrCtx);
713723
Escaped.clear();
714724
{
715725
unsigned Arg = -1;
716-
for (const ParmVarDecl *PVD : Call.parameters()) {
726+
for (const ParmVarDecl *PVD : Call->parameters()) {
717727
++Arg;
718728
QualType ParamTy = PVD->getType();
719729
if (ParamTy.isNull() ||
@@ -722,13 +732,13 @@ void ExprEngine::evalCall(ExplodedNodeSet &Dst, ExplodedNode *Pred,
722732
QualType Pointee = ParamTy->getPointeeType();
723733
if (Pointee.isConstQualified() || Pointee->isVoidType())
724734
continue;
725-
if (const MemRegion *MR = Call.getArgSVal(Arg).getAsRegion())
735+
if (const MemRegion *MR = Call->getArgSVal(Arg).getAsRegion())
726736
Escaped.emplace_back(loc::MemRegionVal(MR), State->getSVal(MR, Pointee));
727737
}
728738
}
729739

730740
State = processPointerEscapedOnBind(State, Escaped, I->getLocationContext(),
731-
PSK_EscapeOutParameters, &Call);
741+
PSK_EscapeOutParameters, &*Call);
732742

733743
if (State == I->getState())
734744
Dst.insert(I);
@@ -1212,59 +1222,58 @@ static bool isTrivialObjectAssignment(const CallEvent &Call) {
12121222
}
12131223

12141224
void ExprEngine::defaultEvalCall(NodeBuilder &Bldr, ExplodedNode *Pred,
1215-
const CallEvent &CallTemplate,
1225+
const CallEvent &Call,
12161226
const EvalCallOptions &CallOpts) {
12171227
// Make sure we have the most recent state attached to the call.
12181228
ProgramStateRef State = Pred->getState();
1219-
CallEventRef<> Call = CallTemplate.cloneWithState(State);
12201229

12211230
// Special-case trivial assignment operators.
1222-
if (isTrivialObjectAssignment(*Call)) {
1223-
performTrivialCopy(Bldr, Pred, *Call);
1231+
if (isTrivialObjectAssignment(Call)) {
1232+
performTrivialCopy(Bldr, Pred, Call);
12241233
return;
12251234
}
12261235

12271236
// Try to inline the call.
12281237
// The origin expression here is just used as a kind of checksum;
12291238
// this should still be safe even for CallEvents that don't come from exprs.
1230-
const Expr *E = Call->getOriginExpr();
1239+
const Expr *E = Call.getOriginExpr();
12311240

12321241
ProgramStateRef InlinedFailedState = getInlineFailedState(State, E);
12331242
if (InlinedFailedState) {
12341243
// If we already tried once and failed, make sure we don't retry later.
12351244
State = InlinedFailedState;
12361245
} else {
1237-
RuntimeDefinition RD = Call->getRuntimeDefinition();
1238-
Call->setForeign(RD.isForeign());
1246+
RuntimeDefinition RD = Call.getRuntimeDefinition();
1247+
Call.setForeign(RD.isForeign());
12391248
const Decl *D = RD.getDecl();
1240-
if (shouldInlineCall(*Call, D, Pred, CallOpts)) {
1249+
if (shouldInlineCall(Call, D, Pred, CallOpts)) {
12411250
if (RD.mayHaveOtherDefinitions()) {
12421251
AnalyzerOptions &Options = getAnalysisManager().options;
12431252

12441253
// Explore with and without inlining the call.
12451254
if (Options.getIPAMode() == IPAK_DynamicDispatchBifurcate) {
1246-
BifurcateCall(RD.getDispatchRegion(), *Call, D, Bldr, Pred);
1255+
BifurcateCall(RD.getDispatchRegion(), Call, D, Bldr, Pred);
12471256
return;
12481257
}
12491258

12501259
// Don't inline if we're not in any dynamic dispatch mode.
12511260
if (Options.getIPAMode() != IPAK_DynamicDispatch) {
1252-
conservativeEvalCall(*Call, Bldr, Pred, State);
1261+
conservativeEvalCall(Call, Bldr, Pred, State);
12531262
return;
12541263
}
12551264
}
1256-
ctuBifurcate(*Call, D, Bldr, Pred, State);
1265+
ctuBifurcate(Call, D, Bldr, Pred, State);
12571266
return;
12581267
}
12591268
}
12601269

12611270
// If we can't inline it, clean up the state traits used only if the function
12621271
// is inlined.
12631272
State = removeStateTraitsUsedForArrayEvaluation(
1264-
State, dyn_cast_or_null<CXXConstructExpr>(E), Call->getLocationContext());
1273+
State, dyn_cast_or_null<CXXConstructExpr>(E), Call.getLocationContext());
12651274

12661275
// Also handle the return value and invalidate the regions.
1267-
conservativeEvalCall(*Call, Bldr, Pred, State);
1276+
conservativeEvalCall(Call, Bldr, Pred, State);
12681277
}
12691278

12701279
void ExprEngine::BifurcateCall(const MemRegion *BifurReg,

0 commit comments

Comments
 (0)