Author: DonĂ¡t Nagy Date: 2026-03-16T14:26:14+01:00 New Revision: 9ea084289a86b959fefbeb6e9a52ed537f96ecab
URL: https://github.com/llvm/llvm-project/commit/9ea084289a86b959fefbeb6e9a52ed537f96ecab DIFF: https://github.com/llvm/llvm-project/commit/9ea084289a86b959fefbeb6e9a52ed537f96ecab.diff LOG: [NFC][analyzer] Refactor ExprEngine::processCallExit (#186182) This commit converts `ExprEngine::processCallExit` to the new paradigm introduced in 1c424bfb03d6dd4b994a0d549e1f3e23852f1e16 where the current `LocationContext` and `Block` is populated near the beginning of the `dispatchWorkItem` call (= elementary analysis step) and remains available during the whole step. Unfortunately the first half of the `CallExit` procedure (`removeDead`) happens within the callee context, while the second half (`PostCall` and similar callbacks) happen in the caller context -- so I need to change the current `LocationContext` and `Block` at the middle of this big method. This means that I need to discard my invariant that `setCurrLocationContextAndBlock` is only called once per each `dispatchWorkItem`; but I think this exceptional case (first half in callee, second half in caller) is still clear enough. In addition to this main goal, I perform many small changes to clarify and modernize the code of this old method. Added: Modified: clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp Removed: ################################################################################ diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h index 2023a7a5b1ac8..b3ef09d464685 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h @@ -246,10 +246,15 @@ class ExprEngine { // This implementation is a temporary measure to allow a gradual transition. void setCurrLocationContextAndBlock(const LocationContext *LC, const CFGBlock *B) { - // Note that there is a call to resetCurrLocationContextAndBlock at the - // beginning of dispatchWorkItem. + // The current LocationContext and Block is reset at the beginning of + // dispacthWorkItem. Ideally, this method should be called only once per + // dipatchWorkItem call (= elementary analysis step); so the following + // assertion is there to catch accidental repeated calls. If the current + // LocationContext and Block needs to change in the middle of a single step + // (which currently happens only once, in processCallExit), use an explicit + // call to resetCurrLocationContextAndBlock. assert(!currBldrCtx && !OwnedCurrBldrCtx && - "This should be called at most once per call to dispatchWorkItem"); + "The current LocationContext and Block is already set"); OwnedCurrBldrCtx.emplace(Engine, B, LC); currBldrCtx = &*OwnedCurrBldrCtx; } diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp index a4a22ce10952c..f6ba3699312ec 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp @@ -247,30 +247,34 @@ ProgramStateRef ExprEngine::removeStateTraitsUsedForArrayEvaluation( /// 1. CallExitBegin (triggers the start of call exit sequence) /// 2. Bind the return value /// 3. Run Remove dead bindings to clean up the dead symbols from the callee. -/// 4. CallExitEnd (switch to the caller context) +/// 4. CallExitEnd /// 5. PostStmt<CallExpr> +/// Steps 1-3. happen in the callee context; but there is a context switch and +/// steps 4-5. happen in the caller context. void ExprEngine::processCallExit(ExplodedNode *CEBNode) { // Step 1 CEBNode was generated before the call. - const StackFrameContext *calleeCtx = CEBNode->getStackFrame(); + const StackFrameContext *CalleeCtx = CEBNode->getStackFrame(); // The parent context might not be a stack frame, so make sure we // look up the first enclosing stack frame. - const StackFrameContext *callerCtx = - calleeCtx->getParent()->getStackFrame(); + const StackFrameContext *CallerCtx = CalleeCtx->getParent()->getStackFrame(); - const Stmt *CE = calleeCtx->getCallSite(); - ProgramStateRef state = CEBNode->getState(); + const Stmt *CE = CalleeCtx->getCallSite(); + ProgramStateRef State = CEBNode->getState(); // Find the last statement in the function and the corresponding basic block. - const Stmt *LastSt = nullptr; - const CFGBlock *Blk = nullptr; - std::tie(LastSt, Blk) = getLastStmt(CEBNode); + auto [LastSt, Blk] = getLastStmt(CEBNode); + + const CFGBlock *PrePurgeBlock = + isa_and_nonnull<ReturnStmt>(LastSt) ? Blk : &CEBNode->getCFG().getExit(); + // The first half of this process happens in the callee context: + setCurrLocationContextAndBlock(CalleeCtx, PrePurgeBlock); - // Generate a CallEvent /before/ cleaning the state, so that we can get the + // Generate a CallEvent /before/ cleaning the State, so that we can get the // correct value for 'this' (if necessary). CallEventManager &CEMgr = getStateManager().getCallEventManager(); - CallEventRef<> Call = CEMgr.getCaller(calleeCtx, state); + CallEventRef<> Call = CEMgr.getCaller(CalleeCtx, State); - // Step 2: generate node with bound return value: CEBNode -> BindedRetNode. + // Step 2: generate node with bound return value: CEBNode -> BoundRetNode. // If this variable is set to 'true' the analyzer will evaluate the call // statement we are about to exit again, instead of continuing the execution @@ -281,11 +285,11 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) { if (const auto *DtorDecl = dyn_cast_or_null<CXXDestructorDecl>(Call->getDecl())) { - if (auto Idx = getPendingArrayDestruction(state, callerCtx)) { + if (auto Idx = getPendingArrayDestruction(State, CallerCtx)) { ShouldRepeatCall = *Idx > 0; - auto ThisVal = svalBuilder.getCXXThis(DtorDecl->getParent(), calleeCtx); - state = state->killBinding(ThisVal); + auto ThisVal = svalBuilder.getCXXThis(DtorDecl->getParent(), CalleeCtx); + State = State->killBinding(ThisVal); } } @@ -293,12 +297,12 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) { if (CE) { if (const ReturnStmt *RS = dyn_cast_or_null<ReturnStmt>(LastSt)) { const LocationContext *LCtx = CEBNode->getLocationContext(); - SVal V = state->getSVal(RS, LCtx); + SVal V = State->getSVal(RS, LCtx); // Ensure that the return type matches the type of the returned Expr. - if (wasDifferentDeclUsedForInlining(Call, calleeCtx)) { + if (wasDifferentDeclUsedForInlining(Call, CalleeCtx)) { QualType ReturnedTy = - CallEvent::getDeclaredResultType(calleeCtx->getDecl()); + CallEvent::getDeclaredResultType(CalleeCtx->getDecl()); if (!ReturnedTy.isNull()) { if (const Expr *Ex = dyn_cast<Expr>(CE)) { V = adjustReturnValue(V, Ex->getType(), ReturnedTy, @@ -307,18 +311,18 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) { } } - state = state->BindExpr(CE, callerCtx, V); + State = State->BindExpr(CE, CallerCtx, V); } // Bind the constructed object value to CXXConstructExpr. if (const CXXConstructExpr *CCE = dyn_cast<CXXConstructExpr>(CE)) { loc::MemRegionVal This = - svalBuilder.getCXXThis(CCE->getConstructor()->getParent(), calleeCtx); - SVal ThisV = state->getSVal(This); - ThisV = state->getSVal(ThisV.castAs<Loc>()); - state = state->BindExpr(CCE, callerCtx, ThisV); + svalBuilder.getCXXThis(CCE->getConstructor()->getParent(), CalleeCtx); + SVal ThisV = State->getSVal(This); + ThisV = State->getSVal(ThisV.castAs<Loc>()); + State = State->BindExpr(CCE, CallerCtx, ThisV); - ShouldRepeatCall = shouldRepeatCtorCall(state, CCE, callerCtx); + ShouldRepeatCall = shouldRepeatCtorCall(State, CCE, CallerCtx); } if (const auto *CNE = dyn_cast<CXXNewExpr>(CE)) { @@ -327,92 +331,85 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) { // region for later use. // Additionally cast the return value of the inlined operator new // (which is of type 'void *') to the correct object type. - SVal AllocV = state->getSVal(CNE, callerCtx); + SVal AllocV = State->getSVal(CNE, CallerCtx); AllocV = svalBuilder.evalCast( AllocV, CNE->getType(), getContext().getPointerType(getContext().VoidTy)); - state = addObjectUnderConstruction(state, CNE, calleeCtx->getParent(), + State = addObjectUnderConstruction(State, CNE, CalleeCtx->getParent(), AllocV); } } if (!ShouldRepeatCall) { - state = removeStateTraitsUsedForArrayEvaluation( - state, dyn_cast_or_null<CXXConstructExpr>(CE), callerCtx); + State = removeStateTraitsUsedForArrayEvaluation( + State, dyn_cast_or_null<CXXConstructExpr>(CE), CallerCtx); } - // Step 3: BindedRetNode -> CleanedNodes + // Step 3: BoundRetNode -> CleanedNodes // If we can find a statement and a block in the inlined function, run remove // dead bindings before returning from the call. This is important to ensure // that we report the issues such as leaks in the stack contexts in which // they occurred. ExplodedNodeSet CleanedNodes; if (LastSt && Blk && AMgr.options.AnalysisPurgeOpt != PurgeNone) { - static SimpleProgramPointTag retValBind("ExprEngine", "Bind Return Value"); + static SimpleProgramPointTag RetValBind("ExprEngine", "Bind Return Value"); auto Loc = isa<ReturnStmt>(LastSt) - ? ProgramPoint{PostStmt(LastSt, calleeCtx, &retValBind)} - : ProgramPoint{EpsilonPoint(calleeCtx, /*Data1=*/nullptr, - /*Data2=*/nullptr, &retValBind)}; - const CFGBlock *PrePurgeBlock = - isa<ReturnStmt>(LastSt) ? Blk : &CEBNode->getCFG().getExit(); - bool isNew; - ExplodedNode *BindedRetNode = G.getNode(Loc, state, false, &isNew); - BindedRetNode->addPredecessor(CEBNode, G); - if (!isNew) + ? ProgramPoint{PostStmt(LastSt, CalleeCtx, &RetValBind)} + : ProgramPoint{EpsilonPoint(CalleeCtx, /*Data1=*/nullptr, + /*Data2=*/nullptr, &RetValBind)}; + + ExplodedNode *BoundRetNode = Engine.makeNode(Loc, State, CEBNode); + if (!BoundRetNode) return; - NodeBuilderContext Ctx(getCoreEngine(), PrePurgeBlock, BindedRetNode); - currBldrCtx = &Ctx; - // Here, we call the Symbol Reaper with 0 statement and callee location - // context, telling it to clean up everything in the callee's context - // (and its children). We use the callee's function body as a diagnostic - // statement, with which the program point will be associated. - removeDead(BindedRetNode, CleanedNodes, nullptr, calleeCtx, - calleeCtx->getAnalysisDeclContext()->getBody(), - ProgramPoint::PostStmtPurgeDeadSymbolsKind); - currBldrCtx = nullptr; + // We call removeDead in the context of the callee. + removeDead( + BoundRetNode, CleanedNodes, /*ReferenceStmt=*/nullptr, CalleeCtx, + /*DiagnosticStmt=*/CalleeCtx->getAnalysisDeclContext()->getBody(), + ProgramPoint::PostStmtPurgeDeadSymbolsKind); } else { CleanedNodes.Add(CEBNode); } + // The second half of this process happens in the caller context. This is an + // exception to the general rule that the current LocationContext and Block + // stay the same within a single call to dispatchWorkItem. + resetCurrLocationContextAndBlock(); + setCurrLocationContextAndBlock(CallerCtx, CalleeCtx->getCallSiteBlock()); + SaveAndRestore CBISave(currStmtIdx, CalleeCtx->getIndex()); + for (ExplodedNode *N : CleanedNodes) { - // Step 4: Generate the CallExit and leave the callee's context. + // Step 4: Generate the CallExitEnd node. // CleanedNodes -> CEENode - CallExitEnd Loc(calleeCtx, callerCtx); - bool isNew; - ProgramStateRef CEEState = (N == CEBNode) ? state : N->getState(); + CallExitEnd Loc(CalleeCtx, CallerCtx); + ProgramStateRef CEEState = (N == CEBNode) ? State : N->getState(); - ExplodedNode *CEENode = G.getNode(Loc, CEEState, false, &isNew); - CEENode->addPredecessor(N, G); - if (!isNew) + ExplodedNode *CEENode = Engine.makeNode(Loc, CEEState, N); + if (!CEENode) return; // Step 5: Perform the post-condition check of the CallExpr and enqueue the // result onto the work list. // CEENode -> Dst -> WorkList - NodeBuilderContext Ctx(Engine, calleeCtx->getCallSiteBlock(), CEENode); - SaveAndRestore<const NodeBuilderContext *> NBCSave(currBldrCtx, &Ctx); - SaveAndRestore CBISave(currStmtIdx, calleeCtx->getIndex()); CallEventRef<> UpdatedCall = Call.cloneWithState(CEEState); + ExplodedNodeSet DstPostPostCallCallback; + getCheckerManager().runCheckersForPostCall(DstPostPostCallCallback, CEENode, + *UpdatedCall, *this, + /*wasInlined=*/true); ExplodedNodeSet DstPostCall; if (llvm::isa_and_nonnull<CXXNewExpr>(CE)) { - ExplodedNodeSet DstPostPostCallCallback; - getCheckerManager().runCheckersForPostCall(DstPostPostCallCallback, - CEENode, *UpdatedCall, *this, - /*wasInlined=*/true); for (ExplodedNode *I : DstPostPostCallCallback) { getCheckerManager().runCheckersForNewAllocator( cast<CXXAllocatorCall>(*UpdatedCall), DstPostCall, I, *this, /*wasInlined=*/true); } } else { - getCheckerManager().runCheckersForPostCall(DstPostCall, CEENode, - *UpdatedCall, *this, - /*wasInlined=*/true); + DstPostCall.insert(DstPostPostCallCallback); } + ExplodedNodeSet Dst; if (const ObjCMethodCall *Msg = dyn_cast<ObjCMethodCall>(Call)) { getCheckerManager().runCheckersForPostObjCMessage(Dst, DstPostCall, *Msg, @@ -428,11 +425,11 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) { } // Enqueue the next element in the block. - for (ExplodedNodeSet::iterator PSI = Dst.begin(), PSE = Dst.end(); - PSI != PSE; ++PSI) { - unsigned Idx = calleeCtx->getIndex() + (ShouldRepeatCall ? 0 : 1); + for (ExplodedNode *DstNode : Dst) { + unsigned Idx = CalleeCtx->getIndex() + (ShouldRepeatCall ? 0 : 1); - Engine.getWorkList()->enqueue(*PSI, calleeCtx->getCallSiteBlock(), Idx); + Engine.getWorkList()->enqueue(DstNode, CalleeCtx->getCallSiteBlock(), + Idx); } } } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
