Skip to content

Commit 0017b47

Browse files
NagyDonatgithub-actions[bot]
authored andcommitted
Automerge: [analyzer] Avoid use of CallEvents with obsolete state (#160707)
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 evaluated this change on 10+ open source projects and it did not change the set of analyzer findings.
2 parents 3508da0 + 6420da6 commit 0017b47

File tree

2 files changed

+39
-34
lines changed

2 files changed

+39
-34
lines changed

clang/lib/StaticAnalyzer/Core/CheckerManager.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -731,19 +731,22 @@ 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;
742-
{ // CheckerContext generates transitions(populates checkDest) on
745+
{ // 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) {
@@ -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: 32 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,33 @@ 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+
// NOTE: CallTemplate is called a "template" because its attached state may
679+
// be obsolete (compared to the state of Pred). The state-dependent methods
680+
// of CallEvent should be used only after a `cloneWithState` call that
681+
// attaches the up-to-date state to this template object.
680682

681683
// Run any pre-call checks using the generic call interface.
682684
ExplodedNodeSet dstPreVisit;
683-
getCheckerManager().runCheckersForPreCall(dstPreVisit, Pred,
684-
Call, *this);
685+
getCheckerManager().runCheckersForPreCall(dstPreVisit, Pred, CallTemplate,
686+
*this);
685687

686688
// Actually evaluate the function call. We try each of the checkers
687689
// to see if the can evaluate the function call, and get a callback at
688690
// defaultEvalCall if all of them fail.
689691
ExplodedNodeSet dstCallEvaluated;
690-
getCheckerManager().runCheckersForEvalCall(dstCallEvaluated, dstPreVisit,
691-
Call, *this, EvalCallOptions());
692+
getCheckerManager().runCheckersForEvalCall(
693+
dstCallEvaluated, dstPreVisit, CallTemplate, *this, EvalCallOptions());
692694

693695
// If there were other constructors called for object-type arguments
694696
// of this call, clean them up.
695697
ExplodedNodeSet dstArgumentCleanup;
696698
for (ExplodedNode *I : dstCallEvaluated)
697-
finishArgumentConstruction(dstArgumentCleanup, I, Call);
699+
finishArgumentConstruction(dstArgumentCleanup, I, CallTemplate);
698700

699701
ExplodedNodeSet dstPostCall;
700702
getCheckerManager().runCheckersForPostCall(dstPostCall, dstArgumentCleanup,
701-
Call, *this);
703+
CallTemplate, *this);
702704

703705
// Escaping symbols conjured during invalidating the regions above.
704706
// Note that, for inlined calls the nodes were put back into the worklist,
@@ -708,12 +710,13 @@ void ExprEngine::evalCall(ExplodedNodeSet &Dst, ExplodedNode *Pred,
708710
// Run pointerEscape callback with the newly conjured symbols.
709711
SmallVector<std::pair<SVal, SVal>, 8> Escaped;
710712
for (ExplodedNode *I : dstPostCall) {
711-
NodeBuilder B(I, Dst, *currBldrCtx);
712713
ProgramStateRef State = I->getState();
714+
CallEventRef<> Call = CallTemplate.cloneWithState(State);
715+
NodeBuilder B(I, Dst, *currBldrCtx);
713716
Escaped.clear();
714717
{
715718
unsigned Arg = -1;
716-
for (const ParmVarDecl *PVD : Call.parameters()) {
719+
for (const ParmVarDecl *PVD : Call->parameters()) {
717720
++Arg;
718721
QualType ParamTy = PVD->getType();
719722
if (ParamTy.isNull() ||
@@ -722,13 +725,13 @@ void ExprEngine::evalCall(ExplodedNodeSet &Dst, ExplodedNode *Pred,
722725
QualType Pointee = ParamTy->getPointeeType();
723726
if (Pointee.isConstQualified() || Pointee->isVoidType())
724727
continue;
725-
if (const MemRegion *MR = Call.getArgSVal(Arg).getAsRegion())
728+
if (const MemRegion *MR = Call->getArgSVal(Arg).getAsRegion())
726729
Escaped.emplace_back(loc::MemRegionVal(MR), State->getSVal(MR, Pointee));
727730
}
728731
}
729732

730733
State = processPointerEscapedOnBind(State, Escaped, I->getLocationContext(),
731-
PSK_EscapeOutParameters, &Call);
734+
PSK_EscapeOutParameters, &*Call);
732735

733736
if (State == I->getState())
734737
Dst.insert(I);
@@ -1212,59 +1215,58 @@ static bool isTrivialObjectAssignment(const CallEvent &Call) {
12121215
}
12131216

12141217
void ExprEngine::defaultEvalCall(NodeBuilder &Bldr, ExplodedNode *Pred,
1215-
const CallEvent &CallTemplate,
1218+
const CallEvent &Call,
12161219
const EvalCallOptions &CallOpts) {
12171220
// Make sure we have the most recent state attached to the call.
12181221
ProgramStateRef State = Pred->getState();
1219-
CallEventRef<> Call = CallTemplate.cloneWithState(State);
12201222

12211223
// Special-case trivial assignment operators.
1222-
if (isTrivialObjectAssignment(*Call)) {
1223-
performTrivialCopy(Bldr, Pred, *Call);
1224+
if (isTrivialObjectAssignment(Call)) {
1225+
performTrivialCopy(Bldr, Pred, Call);
12241226
return;
12251227
}
12261228

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

12321234
ProgramStateRef InlinedFailedState = getInlineFailedState(State, E);
12331235
if (InlinedFailedState) {
12341236
// If we already tried once and failed, make sure we don't retry later.
12351237
State = InlinedFailedState;
12361238
} else {
1237-
RuntimeDefinition RD = Call->getRuntimeDefinition();
1238-
Call->setForeign(RD.isForeign());
1239+
RuntimeDefinition RD = Call.getRuntimeDefinition();
1240+
Call.setForeign(RD.isForeign());
12391241
const Decl *D = RD.getDecl();
1240-
if (shouldInlineCall(*Call, D, Pred, CallOpts)) {
1242+
if (shouldInlineCall(Call, D, Pred, CallOpts)) {
12411243
if (RD.mayHaveOtherDefinitions()) {
12421244
AnalyzerOptions &Options = getAnalysisManager().options;
12431245

12441246
// Explore with and without inlining the call.
12451247
if (Options.getIPAMode() == IPAK_DynamicDispatchBifurcate) {
1246-
BifurcateCall(RD.getDispatchRegion(), *Call, D, Bldr, Pred);
1248+
BifurcateCall(RD.getDispatchRegion(), Call, D, Bldr, Pred);
12471249
return;
12481250
}
12491251

12501252
// Don't inline if we're not in any dynamic dispatch mode.
12511253
if (Options.getIPAMode() != IPAK_DynamicDispatch) {
1252-
conservativeEvalCall(*Call, Bldr, Pred, State);
1254+
conservativeEvalCall(Call, Bldr, Pred, State);
12531255
return;
12541256
}
12551257
}
1256-
ctuBifurcate(*Call, D, Bldr, Pred, State);
1258+
ctuBifurcate(Call, D, Bldr, Pred, State);
12571259
return;
12581260
}
12591261
}
12601262

12611263
// If we can't inline it, clean up the state traits used only if the function
12621264
// is inlined.
12631265
State = removeStateTraitsUsedForArrayEvaluation(
1264-
State, dyn_cast_or_null<CXXConstructExpr>(E), Call->getLocationContext());
1266+
State, dyn_cast_or_null<CXXConstructExpr>(E), Call.getLocationContext());
12651267

12661268
// Also handle the return value and invalidate the regions.
1267-
conservativeEvalCall(*Call, Bldr, Pred, State);
1269+
conservativeEvalCall(Call, Bldr, Pred, State);
12681270
}
12691271

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

0 commit comments

Comments
 (0)