From 6846eb2b4507f2434d3f15d2449caea3b33a9c2c Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Mon, 26 Jan 2026 22:18:33 +1100 Subject: [PATCH 01/32] refactor AE, use worklist algorithm(naive) --- .../AE/Svfexe/AbstractInterpretation.h | 32 ++ svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 312 ++++++++++++++++-- 2 files changed, 310 insertions(+), 34 deletions(-) diff --git a/svf/include/AE/Svfexe/AbstractInterpretation.h b/svf/include/AE/Svfexe/AbstractInterpretation.h index 53a76becf..e06322887 100644 --- a/svf/include/AE/Svfexe/AbstractInterpretation.h +++ b/svf/include/AE/Svfexe/AbstractInterpretation.h @@ -224,6 +224,37 @@ class AbstractInterpretation void handleWTOComponent(const ICFGWTOComp* wtoComp); + /** + * Handle a function using worklist algorithm + * + * @param funEntry The entry node of the function to handle + */ + void handleFunction(const ICFGNode* funEntry); + + /** + * Handle an ICFG node by merging states and processing statements + * + * @param node The ICFG node to handle + * @return true if state changed, false if fixpoint reached or infeasible + */ + bool handleICFGNode(const ICFGNode* node); + + /** + * Get the next nodes of a node within the same function + * + * @param node The node to get successors for + * @return Vector of successor nodes + */ + std::vector getNextNodes(const ICFGNode* node) const; + + /** + * Get the next nodes outside a cycle + * + * @param cycle The cycle to get exit successors for + * @return Vector of successor nodes outside the cycle + */ + std::vector getNextNodesOfCycle(const ICFGCycleWTO* cycle) const; + /** * handle SVF Statement like CmpStmt, CallStmt, GepStmt, LoadStmt, StoreStmt, etc. @@ -299,6 +330,7 @@ class AbstractInterpretation Map funcToWTO; Set> nonRecursiveCallSites; Set recursiveFuns; + Map cycleHeadToCycle; bool hasAbsStateFromTrace(const ICFGNode* node) diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index b3aed674f..c5ef275db 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -142,6 +142,27 @@ void AbstractInterpretation::initWTO() funcToWTO[it->second->getFunction()] = iwto; } } + + // Build cycleHeadToCycle map for all functions + // This maps cycle head nodes to their corresponding WTO cycles for efficient lookup + for (auto& [func, wto] : funcToWTO) + { + // Recursive lambda to collect cycle heads from nested WTO components + std::function&)> collectCycleHeads = + [&](const std::list& comps) + { + for (const ICFGWTOComp* comp : comps) + { + if (const ICFGCycleWTO* cycle = SVFUtil::dyn_cast(comp)) + { + cycleHeadToCycle[cycle->head()->getICFGNode()] = cycle; + // Recursively collect nested cycle heads + collectCycleHeads(cycle->getWTOComponents()); + } + } + }; + collectCycleHeads(wto->getWTOComponents()); + } } /// Program entry @@ -154,8 +175,9 @@ void AbstractInterpretation::analyse() icfg->getGlobalICFGNode())[PAG::getPAG()->getBlkPtr()] = IntervalValue::top(); if (const CallGraphNode* cgn = svfir->getCallGraph()->getCallGraphNode("main")) { - const ICFGWTO* wto = funcToWTO[cgn->getFunction()]; - handleWTOComponents(wto->getWTOComponents()); + // Use worklist-based function handling instead of recursive WTO component handling + const ICFGNode* mainEntry = icfg->getFunEntryICFGNode(cgn->getFunction()); + handleFunction(mainEntry); } } @@ -576,6 +598,215 @@ void AbstractInterpretation::handleSingletonWTO(const ICFGSingletonWTO *icfgSing stat->countStateSize(); } +/** + * Handle an ICFG node by merging states from predecessors and processing statements + * Returns true if the abstract state has changed, false if fixpoint reached or infeasible + */ +bool AbstractInterpretation::handleICFGNode(const ICFGNode* node) +{ + // Store the previous state for fixpoint detection + AbstractState prevState; + bool hadPrevState = hasAbsStateFromTrace(node); + if (hadPrevState) + prevState = abstractTrace[node]; + + // For function entry nodes, initialize state from caller or global + bool isFunEntry = SVFUtil::isa(node); + if (isFunEntry) + { + // Try to merge from predecessors first (handles call edges) + if (!mergeStatesFromPredecessors(node)) + { + // No predecessors with state - initialize from caller or global + if (!callSiteStack.empty()) + { + // Get state from the most recent call site + const CallICFGNode* caller = callSiteStack.back(); + if (hasAbsStateFromTrace(caller)) + { + abstractTrace[node] = abstractTrace[caller]; + } + else + { + abstractTrace[node] = AbstractState(); + } + } + else + { + // This is the main function entry, inherit from global node + const ICFGNode* globalNode = icfg->getGlobalICFGNode(); + if (hasAbsStateFromTrace(globalNode)) + { + abstractTrace[node] = abstractTrace[globalNode]; + } + else + { + abstractTrace[node] = AbstractState(); + } + } + } + } + else + { + // Merge states from predecessors + if (!mergeStatesFromPredecessors(node)) + return false; + } + + stat->getBlockTrace()++; + stat->getICFGNodeTrace()++; + + // Handle SVF statements + for (const SVFStmt *stmt: node->getSVFStmts()) + { + handleSVFStatement(stmt); + } + + // Handle call sites + if (const CallICFGNode* callNode = SVFUtil::dyn_cast(node)) + { + handleCallSite(callNode); + } + + // Run detectors + for (auto& detector: detectors) + detector->detect(getAbsStateFromTrace(node), node); + stat->countStateSize(); + + // Check if state changed (for fixpoint detection) + // For entry nodes on first visit, always return true to process successors + if (isFunEntry && !hadPrevState) + return true; + + if (abstractTrace[node] == prevState) + return false; + + return true; +} + +/** + * Get the next nodes of a node within the same function + * For CallICFGNode, includes shortcut to RetICFGNode + */ +std::vector AbstractInterpretation::getNextNodes(const ICFGNode* node) const +{ + std::vector outEdges; + for (const ICFGEdge* edge : node->getOutEdges()) + { + const ICFGNode* dst = edge->getDstNode(); + // Only nodes inside the same function are included + if (dst->getFun() == node->getFun()) + { + outEdges.push_back(dst); + } + } + if (const CallICFGNode* callNode = SVFUtil::dyn_cast(node)) + { + // Shortcut to the RetICFGNode + const ICFGNode* retNode = callNode->getRetICFGNode(); + outEdges.push_back(retNode); + } + return outEdges; +} + +/** + * Get the next nodes outside a cycle + * Inner cycles are skipped since their next nodes cannot be outside the outer cycle + */ +std::vector AbstractInterpretation::getNextNodesOfCycle(const ICFGCycleWTO* cycle) const +{ + Set cycleNodes; + // Insert the head of the cycle and all component nodes + cycleNodes.insert(cycle->head()->getICFGNode()); + for (const ICFGWTOComp* comp : cycle->getWTOComponents()) + { + if (const ICFGSingletonWTO* singleton = SVFUtil::dyn_cast(comp)) + { + cycleNodes.insert(singleton->getICFGNode()); + } + else if (const ICFGCycleWTO* subCycle = SVFUtil::dyn_cast(comp)) + { + cycleNodes.insert(subCycle->head()->getICFGNode()); + } + } + + std::vector outEdges; + + // Check head's successors + std::vector nextNodes = getNextNodes(cycle->head()->getICFGNode()); + for (const ICFGNode* nextNode : nextNodes) + { + // Only nodes that point outside the cycle are included + if (cycleNodes.find(nextNode) == cycleNodes.end()) + { + outEdges.push_back(nextNode); + } + } + + // Check each component's successors + for (const ICFGWTOComp* comp : cycle->getWTOComponents()) + { + if (const ICFGSingletonWTO* singleton = SVFUtil::dyn_cast(comp)) + { + std::vector compNextNodes = getNextNodes(singleton->getICFGNode()); + for (const ICFGNode* nextNode : compNextNodes) + { + if (cycleNodes.find(nextNode) == cycleNodes.end()) + { + outEdges.push_back(nextNode); + } + } + } + // Skip inner cycles - they are handled within the outer cycle + } + return outEdges; +} + +/** + * Handle a function using worklist algorithm + * This replaces the recursive WTO component handling with explicit worklist iteration + */ +void AbstractInterpretation::handleFunction(const ICFGNode* funEntry) +{ + FIFOWorkList worklist; + worklist.push(funEntry); + + while (!worklist.empty()) + { + const ICFGNode* node = worklist.pop(); + + // Check if this node is a cycle head + if (cycleHeadToCycle.find(node) != cycleHeadToCycle.end()) + { + const ICFGCycleWTO* cycle = cycleHeadToCycle[node]; + handleCycleWTO(cycle); + + // Push nodes outside the cycle to the worklist + std::vector cycleNextNodes = getNextNodesOfCycle(cycle); + for (const ICFGNode* nextNode : cycleNextNodes) + { + worklist.push(nextNode); + } + } + else + { + // Handle regular node + if (!handleICFGNode(node)) + { + // Fixpoint reached or infeasible, skip successors + continue; + } + + // Push successor nodes to the worklist + std::vector nextNodes = getNextNodes(node); + for (const ICFGNode* nextNode : nextNodes) + { + worklist.push(nextNode); + } + } + } +} + /** * @brief Handle two types of WTO components (singleton and cycle) */ @@ -721,8 +952,9 @@ void AbstractInterpretation::directCallFunPass(const CallICFGNode *callNode) callSiteStack.push_back(callNode); - const ICFGWTO* wto = funcToWTO[calleeFun]; - handleWTOComponents(wto->getWTOComponents()); + // Use worklist-based function handling instead of recursive WTO component handling + const ICFGNode* calleeEntry = icfg->getFunEntryICFGNode(calleeFun); + handleFunction(calleeEntry); callSiteStack.pop_back(); // handle Ret node @@ -762,8 +994,10 @@ void AbstractInterpretation::indirectCallFunPass(const CallICFGNode *callNode) callSiteStack.push_back(callNode); abstractTrace[callNode] = as; - const ICFGWTO* wto = funcToWTO[callfun]; - handleWTOComponents(wto->getWTOComponents()); + // Use worklist-based function handling instead of recursive WTO component handling + const ICFGNode* calleeEntry = icfg->getFunEntryICFGNode(callfun); + handleFunction(calleeEntry); + callSiteStack.pop_back(); // handle Ret node const RetICFGNode* retNode = callNode->getRetICFGNode(); @@ -771,26 +1005,32 @@ void AbstractInterpretation::indirectCallFunPass(const CallICFGNode *callNode) } } -/// handle wto cycle (loop) -void AbstractInterpretation::handleCycleWTO(const ICFGCycleWTO*cycle) +/// handle wto cycle (loop) using worklist-compatible widening/narrowing iteration +void AbstractInterpretation::handleCycleWTO(const ICFGCycleWTO* cycle) { const ICFGNode* cycle_head = cycle->head()->getICFGNode(); - // Flag to indicate if we are in the increasing phase + // Flag to indicate if we are in the increasing (widening) phase bool increasing = true; - // Infinite loop until a fixpoint is reached, + u32_t widen_delay = Options::WidenDelay(); + + // Infinite loop until a fixpoint is reached for (u32_t cur_iter = 0;; cur_iter++) { - // Start widening or narrowing if cur_iter >= widen threshold (widen delay) - if (cur_iter >= Options::WidenDelay()) + // Get the abstract state before processing the cycle head + AbstractState prev_head_state; + if (hasAbsStateFromTrace(cycle_head)) + prev_head_state = abstractTrace[cycle_head]; + + // Process the cycle head node + handleICFGNode(cycle_head); + AbstractState cur_head_state = abstractTrace[cycle_head]; + + // Start widening or narrowing if cur_iter >= widen delay threshold + if (cur_iter >= widen_delay) { - // Widen or narrow after processing cycle head node - AbstractState prev_head_state = abstractTrace[cycle_head]; - handleWTOComponent(cycle->head()); - AbstractState cur_head_state = abstractTrace[cycle_head]; if (increasing) { - - if (isRecursiveFun(cycle->head()->getICFGNode()->getFun()) && + if (isRecursiveFun(cycle_head->getFun()) && !(Options::HandleRecur() == WIDEN_ONLY || Options::HandleRecur() == WIDEN_NARROW)) { @@ -799,21 +1039,22 @@ void AbstractInterpretation::handleCycleWTO(const ICFGCycleWTO*cycle) assert(false && "Recursion mode TOP should not reach here!"); } - // Widening + // Apply widening operator abstractTrace[cycle_head] = prev_head_state.widening(cur_head_state); if (abstractTrace[cycle_head] == prev_head_state) { + // Widening fixpoint reached, switch to narrowing phase increasing = false; continue; } } else { - // Narrowing, use different modes for nodes within recursions - if (isRecursiveFun(cycle->head()->getICFGNode()->getFun())) + // Narrowing phase - use different modes for nodes within recursions + if (isRecursiveFun(cycle_head->getFun())) { - // For nodes in recursions, skip narrowing in WIDEN_TOP mode + // For nodes in recursions, skip narrowing in WIDEN_ONLY mode if (Options::HandleRecur() == WIDEN_ONLY) { break; @@ -821,41 +1062,44 @@ void AbstractInterpretation::handleCycleWTO(const ICFGCycleWTO*cycle) // Perform normal narrowing in WIDEN_NARROW mode else if (Options::HandleRecur() == WIDEN_NARROW) { - // Widening's fixpoint reached in the widening phase, switch to narrowing abstractTrace[cycle_head] = prev_head_state.narrowing(cur_head_state); if (abstractTrace[cycle_head] == prev_head_state) { - // Narrowing's fixpoint reached in the narrowing phase, exit loop + // Narrowing fixpoint reached, exit loop break; } } - // When Options::HandleRecur() == TOP, skipRecursiveCall will handle recursions, - // thus should not reach this branch else { assert(false && "Recursion mode TOP should not reach here"); } } - // For nodes outside recursions, perform normal narrowing else { - // Widening's fixpoint reached in the widening phase, switch to narrowing + // For nodes outside recursions, perform normal narrowing abstractTrace[cycle_head] = prev_head_state.narrowing(cur_head_state); if (abstractTrace[cycle_head] == prev_head_state) { - // Narrowing's fixpoint reached in the narrowing phase, exit loop + // Narrowing fixpoint reached, exit loop break; } } } } - else + + // Process cycle body components + for (const ICFGWTOComp* comp : cycle->getWTOComponents()) { - // Handle the cycle head - handleWTOComponent(cycle->head()); + if (const ICFGSingletonWTO* singleton = SVFUtil::dyn_cast(comp)) + { + handleICFGNode(singleton->getICFGNode()); + } + else if (const ICFGCycleWTO* subCycle = SVFUtil::dyn_cast(comp)) + { + // Handle nested cycle recursively + handleCycleWTO(subCycle); + } } - // Handle the cycle body - handleWTOComponents(cycle->getWTOComponents()); } } From 353871db007a9bf8daf0b49efa480b901795c6ed Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Wed, 28 Jan 2026 15:26:00 +1100 Subject: [PATCH 02/32] sync with SSA Ass3 --- .../AE/Svfexe/AbstractInterpretation.h | 4 - svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 208 ++++++++---------- 2 files changed, 91 insertions(+), 121 deletions(-) diff --git a/svf/include/AE/Svfexe/AbstractInterpretation.h b/svf/include/AE/Svfexe/AbstractInterpretation.h index e06322887..d5f594e68 100644 --- a/svf/include/AE/Svfexe/AbstractInterpretation.h +++ b/svf/include/AE/Svfexe/AbstractInterpretation.h @@ -220,10 +220,6 @@ class AbstractInterpretation */ virtual void handleCycleWTO(const ICFGCycleWTO* cycle); - void handleWTOComponents(const std::list& wtoComps); - - void handleWTOComponent(const ICFGWTOComp* wtoComp); - /** * Handle a function using worklist algorithm * diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index c5ef275db..bcd98abdb 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -810,30 +810,101 @@ void AbstractInterpretation::handleFunction(const ICFGNode* funEntry) /** * @brief Handle two types of WTO components (singleton and cycle) */ -void AbstractInterpretation::handleWTOComponents(const std::list& wtoComps) +void AbstractInterpretation::handleCycleWTO(const ICFGCycleWTO* cycle) { - for (const ICFGWTOComp* wtoNode : wtoComps) - { - handleWTOComponent(wtoNode); - } -} + const ICFGNode* cycle_head = cycle->head()->getICFGNode(); + // Flag to indicate if we are in the increasing (widening) phase + bool increasing = true; + u32_t widen_delay = Options::WidenDelay(); -void AbstractInterpretation::handleWTOComponent(const ICFGWTOComp* wtoNode) -{ - if (const ICFGSingletonWTO* node = SVFUtil::dyn_cast(wtoNode)) - { - if (mergeStatesFromPredecessors(node->getICFGNode())) - handleSingletonWTO(node); - } - // Handle WTO cycles - else if (const ICFGCycleWTO* cycle = SVFUtil::dyn_cast(wtoNode)) + // Infinite loop until a fixpoint is reached + for (u32_t cur_iter = 0;; cur_iter++) { - if (mergeStatesFromPredecessors(cycle->head()->getICFGNode())) - handleCycleWTO(cycle); + // Get the abstract state before processing the cycle head + AbstractState prev_head_state; + if (hasAbsStateFromTrace(cycle_head)) + prev_head_state = abstractTrace[cycle_head]; + + // Process the cycle head node + handleICFGNode(cycle_head); + AbstractState cur_head_state = abstractTrace[cycle_head]; + + // Start widening or narrowing if cur_iter >= widen delay threshold + if (cur_iter >= widen_delay) + { + if (increasing) + { + if (isRecursiveFun(cycle_head->getFun()) && + !(Options::HandleRecur() == WIDEN_ONLY || + Options::HandleRecur() == WIDEN_NARROW)) + { + // When Options::HandleRecur() == TOP, skipRecursiveCall will handle recursions, + // thus should not reach this branch + assert(false && "Recursion mode TOP should not reach here!"); + } + + // Apply widening operator + abstractTrace[cycle_head] = prev_head_state.widening(cur_head_state); + + if (abstractTrace[cycle_head] == prev_head_state) + { + // Widening fixpoint reached, switch to narrowing phase + increasing = false; + continue; + } + } + else + { + // Narrowing phase - use different modes for nodes within recursions + if (isRecursiveFun(cycle_head->getFun())) + { + // For nodes in recursions, skip narrowing in WIDEN_ONLY mode + if (Options::HandleRecur() == WIDEN_ONLY) + { + break; + } + // Perform normal narrowing in WIDEN_NARROW mode + else if (Options::HandleRecur() == WIDEN_NARROW) + { + abstractTrace[cycle_head] = prev_head_state.narrowing(cur_head_state); + if (abstractTrace[cycle_head] == prev_head_state) + { + // Narrowing fixpoint reached, exit loop + break; + } + } + else + { + assert(false && "Recursion mode TOP should not reach here"); + } + } + else + { + // For nodes outside recursions, perform normal narrowing + abstractTrace[cycle_head] = prev_head_state.narrowing(cur_head_state); + if (abstractTrace[cycle_head] == prev_head_state) + { + // Narrowing fixpoint reached, exit loop + break; + } + } + } + } + + // Process cycle body components + for (const ICFGWTOComp* comp : cycle->getWTOComponents()) + { + if (const ICFGSingletonWTO* singleton = SVFUtil::dyn_cast(comp)) + { + handleICFGNode(singleton->getICFGNode()); + } + else if (const ICFGCycleWTO* subCycle = SVFUtil::dyn_cast(comp)) + { + // Handle nested cycle recursively + handleCycleWTO(subCycle); + } + } } - // Assert false for unknown WTO types - else - assert(false && "unknown WTO type!"); } void AbstractInterpretation::handleCallSite(const ICFGNode* node) @@ -1006,103 +1077,6 @@ void AbstractInterpretation::indirectCallFunPass(const CallICFGNode *callNode) } /// handle wto cycle (loop) using worklist-compatible widening/narrowing iteration -void AbstractInterpretation::handleCycleWTO(const ICFGCycleWTO* cycle) -{ - const ICFGNode* cycle_head = cycle->head()->getICFGNode(); - // Flag to indicate if we are in the increasing (widening) phase - bool increasing = true; - u32_t widen_delay = Options::WidenDelay(); - - // Infinite loop until a fixpoint is reached - for (u32_t cur_iter = 0;; cur_iter++) - { - // Get the abstract state before processing the cycle head - AbstractState prev_head_state; - if (hasAbsStateFromTrace(cycle_head)) - prev_head_state = abstractTrace[cycle_head]; - - // Process the cycle head node - handleICFGNode(cycle_head); - AbstractState cur_head_state = abstractTrace[cycle_head]; - - // Start widening or narrowing if cur_iter >= widen delay threshold - if (cur_iter >= widen_delay) - { - if (increasing) - { - if (isRecursiveFun(cycle_head->getFun()) && - !(Options::HandleRecur() == WIDEN_ONLY || - Options::HandleRecur() == WIDEN_NARROW)) - { - // When Options::HandleRecur() == TOP, skipRecursiveCall will handle recursions, - // thus should not reach this branch - assert(false && "Recursion mode TOP should not reach here!"); - } - - // Apply widening operator - abstractTrace[cycle_head] = prev_head_state.widening(cur_head_state); - - if (abstractTrace[cycle_head] == prev_head_state) - { - // Widening fixpoint reached, switch to narrowing phase - increasing = false; - continue; - } - } - else - { - // Narrowing phase - use different modes for nodes within recursions - if (isRecursiveFun(cycle_head->getFun())) - { - // For nodes in recursions, skip narrowing in WIDEN_ONLY mode - if (Options::HandleRecur() == WIDEN_ONLY) - { - break; - } - // Perform normal narrowing in WIDEN_NARROW mode - else if (Options::HandleRecur() == WIDEN_NARROW) - { - abstractTrace[cycle_head] = prev_head_state.narrowing(cur_head_state); - if (abstractTrace[cycle_head] == prev_head_state) - { - // Narrowing fixpoint reached, exit loop - break; - } - } - else - { - assert(false && "Recursion mode TOP should not reach here"); - } - } - else - { - // For nodes outside recursions, perform normal narrowing - abstractTrace[cycle_head] = prev_head_state.narrowing(cur_head_state); - if (abstractTrace[cycle_head] == prev_head_state) - { - // Narrowing fixpoint reached, exit loop - break; - } - } - } - } - - // Process cycle body components - for (const ICFGWTOComp* comp : cycle->getWTOComponents()) - { - if (const ICFGSingletonWTO* singleton = SVFUtil::dyn_cast(comp)) - { - handleICFGNode(singleton->getICFGNode()); - } - else if (const ICFGCycleWTO* subCycle = SVFUtil::dyn_cast(comp)) - { - // Handle nested cycle recursively - handleCycleWTO(subCycle); - } - } - } -} - void AbstractInterpretation::handleSVFStatement(const SVFStmt *stmt) { if (const AddrStmt *addr = SVFUtil::dyn_cast(stmt)) From 3847a44470e25879ed6d05b813cc8253eac6a332 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Thu, 29 Jan 2026 12:57:19 +1100 Subject: [PATCH 03/32] refactor recursion --- .../AE/Svfexe/AbstractInterpretation.h | 41 ++- svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 322 +++++++++--------- 2 files changed, 208 insertions(+), 155 deletions(-) diff --git a/svf/include/AE/Svfexe/AbstractInterpretation.h b/svf/include/AE/Svfexe/AbstractInterpretation.h index d5f594e68..79ee2c72a 100644 --- a/svf/include/AE/Svfexe/AbstractInterpretation.h +++ b/svf/include/AE/Svfexe/AbstractInterpretation.h @@ -218,7 +218,7 @@ class AbstractInterpretation * * @param cycle WTOCycle which has weak topo order of basic blocks and nested cycles */ - virtual void handleCycleWTO(const ICFGCycleWTO* cycle); + virtual void handleICFGCycle(const ICFGCycleWTO* cycle); /** * Handle a function using worklist algorithm @@ -251,6 +251,13 @@ class AbstractInterpretation */ std::vector getNextNodesOfCycle(const ICFGCycleWTO* cycle) const; + /** + * Recursively collect cycle heads from nested WTO components + * + * @param comps The list of WTO components to collect cycle heads from + */ + void collectCycleHeads(const std::list& comps); + /** * handle SVF Statement like CmpStmt, CallStmt, GepStmt, LoadStmt, StoreStmt, etc. @@ -351,6 +358,38 @@ class AbstractInterpretation virtual bool isIndirectCall(const CallICFGNode* callNode); virtual void indirectCallFunPass(const CallICFGNode* callNode); + /// Recursion Handling Decision Methods + /// All recursion mode (TOP/WIDEN_ONLY/WIDEN_NARROW) logic is centralized here + + /** + * Determines if a recursive call should be skipped (not inlined). + * - TOP mode: Always skip recursive calls + * - WIDEN_ONLY/WIDEN_NARROW: Skip only recursive callsites (calls within same SCC) + */ + bool shouldSkipRecursiveCall(const CallICFGNode* callNode, const FunObjVar* callee); + + /** + * Handles state for a skipped recursive call. + * Sets return value and stores to TOP (only meaningful in TOP mode). + */ + void handleSkippedRecursiveCall(const CallICFGNode* callNode); + + /** + * Determines if narrowing should be applied for a cycle head in a recursive function. + * - TOP mode: Should not reach here (asserts) + * - WIDEN_ONLY: Returns false (skip narrowing) + * - WIDEN_NARROW: Returns true (apply narrowing) + * For non-recursive functions, always returns true. + */ + bool shouldApplyNarrowingInRecursion(const FunObjVar* fun); + + /** + * Determines if a return edge should contribute to state merging. + * - TOP mode: Always include + * - WIDEN_ONLY/WIDEN_NARROW: Only if callsite has state + */ + bool shouldIncludeReturnEdge(const RetICFGNode* returnSite); + // there data should be shared with subclasses Map> func_map; diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index bcd98abdb..aac1765c5 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -73,14 +73,25 @@ AbstractInterpretation::~AbstractInterpretation() } /** - * @brief Compute WTO for each function partition entry + * @brief Recursively collect cycle heads from nested WTO components * - * This function first identifies function partition entries (pair: ), - * and then compute the IWTO for each pair. - * It does this by detecting call graph's strongly connected components (SCC). - * Each SCC forms a function partition, and any function that is invoked from outside its SCC - * is identified as an entry of the function partition. + * This helper function traverses the WTO component tree and builds the cycleHeadToCycle + * map, which maps each cycle head node to its corresponding ICFGCycleWTO object. + * This enables efficient O(1) lookup of cycles during analysis. */ +void AbstractInterpretation::collectCycleHeads(const std::list& comps) +{ + for (const ICFGWTOComp* comp : comps) + { + if (const ICFGCycleWTO* cycle = SVFUtil::dyn_cast(comp)) + { + cycleHeadToCycle[cycle->head()->getICFGNode()] = cycle; + // Recursively collect nested cycle heads + collectCycleHeads(cycle->getWTOComponents()); + } + } +} + void AbstractInterpretation::initWTO() { AndersenWaveDiff* ander = AndersenWaveDiff::createAndersenWaveDiff(svfir); @@ -147,20 +158,6 @@ void AbstractInterpretation::initWTO() // This maps cycle head nodes to their corresponding WTO cycles for efficient lookup for (auto& [func, wto] : funcToWTO) { - // Recursive lambda to collect cycle heads from nested WTO components - std::function&)> collectCycleHeads = - [&](const std::list& comps) - { - for (const ICFGWTOComp* comp : comps) - { - if (const ICFGCycleWTO* cycle = SVFUtil::dyn_cast(comp)) - { - cycleHeadToCycle[cycle->head()->getICFGNode()] = cycle; - // Recursively collect nested cycle heads - collectCycleHeads(cycle->getWTOComponents()); - } - } - }; collectCycleHeads(wto->getWTOComponents()); } } @@ -237,21 +234,10 @@ bool AbstractInterpretation::mergeStatesFromPredecessors(const ICFGNode * icfgNo else if (const RetCFGEdge *retCfgEdge = SVFUtil::dyn_cast(edge)) { - switch (Options::HandleRecur()) - { - case TOP: + const RetICFGNode* returnSite = SVFUtil::dyn_cast(icfgNode); + if (shouldIncludeReturnEdge(returnSite)) { workList.push_back(abstractTrace[retCfgEdge->getSrcNode()]); - break; - } - case WIDEN_ONLY: - case WIDEN_NARROW: - { - const RetICFGNode* returnSite = SVFUtil::dyn_cast(icfgNode); - const CallICFGNode* callSite = returnSite->getCallICFGNode(); - if (hasAbsStateFromTrace(callSite)) - workList.push_back(abstractTrace[retCfgEdge->getSrcNode()]); - } } } else @@ -779,7 +765,7 @@ void AbstractInterpretation::handleFunction(const ICFGNode* funEntry) if (cycleHeadToCycle.find(node) != cycleHeadToCycle.end()) { const ICFGCycleWTO* cycle = cycleHeadToCycle[node]; - handleCycleWTO(cycle); + handleICFGCycle(cycle); // Push nodes outside the cycle to the worklist std::vector cycleNextNodes = getNextNodesOfCycle(cycle); @@ -807,105 +793,6 @@ void AbstractInterpretation::handleFunction(const ICFGNode* funEntry) } } -/** - * @brief Handle two types of WTO components (singleton and cycle) - */ -void AbstractInterpretation::handleCycleWTO(const ICFGCycleWTO* cycle) -{ - const ICFGNode* cycle_head = cycle->head()->getICFGNode(); - // Flag to indicate if we are in the increasing (widening) phase - bool increasing = true; - u32_t widen_delay = Options::WidenDelay(); - - // Infinite loop until a fixpoint is reached - for (u32_t cur_iter = 0;; cur_iter++) - { - // Get the abstract state before processing the cycle head - AbstractState prev_head_state; - if (hasAbsStateFromTrace(cycle_head)) - prev_head_state = abstractTrace[cycle_head]; - - // Process the cycle head node - handleICFGNode(cycle_head); - AbstractState cur_head_state = abstractTrace[cycle_head]; - - // Start widening or narrowing if cur_iter >= widen delay threshold - if (cur_iter >= widen_delay) - { - if (increasing) - { - if (isRecursiveFun(cycle_head->getFun()) && - !(Options::HandleRecur() == WIDEN_ONLY || - Options::HandleRecur() == WIDEN_NARROW)) - { - // When Options::HandleRecur() == TOP, skipRecursiveCall will handle recursions, - // thus should not reach this branch - assert(false && "Recursion mode TOP should not reach here!"); - } - - // Apply widening operator - abstractTrace[cycle_head] = prev_head_state.widening(cur_head_state); - - if (abstractTrace[cycle_head] == prev_head_state) - { - // Widening fixpoint reached, switch to narrowing phase - increasing = false; - continue; - } - } - else - { - // Narrowing phase - use different modes for nodes within recursions - if (isRecursiveFun(cycle_head->getFun())) - { - // For nodes in recursions, skip narrowing in WIDEN_ONLY mode - if (Options::HandleRecur() == WIDEN_ONLY) - { - break; - } - // Perform normal narrowing in WIDEN_NARROW mode - else if (Options::HandleRecur() == WIDEN_NARROW) - { - abstractTrace[cycle_head] = prev_head_state.narrowing(cur_head_state); - if (abstractTrace[cycle_head] == prev_head_state) - { - // Narrowing fixpoint reached, exit loop - break; - } - } - else - { - assert(false && "Recursion mode TOP should not reach here"); - } - } - else - { - // For nodes outside recursions, perform normal narrowing - abstractTrace[cycle_head] = prev_head_state.narrowing(cur_head_state); - if (abstractTrace[cycle_head] == prev_head_state) - { - // Narrowing fixpoint reached, exit loop - break; - } - } - } - } - - // Process cycle body components - for (const ICFGWTOComp* comp : cycle->getWTOComponents()) - { - if (const ICFGSingletonWTO* singleton = SVFUtil::dyn_cast(comp)) - { - handleICFGNode(singleton->getICFGNode()); - } - else if (const ICFGCycleWTO* subCycle = SVFUtil::dyn_cast(comp)) - { - // Handle nested cycle recursively - handleCycleWTO(subCycle); - } - } - } -} void AbstractInterpretation::handleCallSite(const ICFGNode* node) { @@ -915,10 +802,6 @@ void AbstractInterpretation::handleCallSite(const ICFGNode* node) { extCallPass(callNode); } - else if (isRecursiveCall(callNode) && Options::HandleRecur() == TOP) - { - recursiveCallPass(callNode); - } else if (isDirectCall(callNode)) { directCallFunPass(callNode); @@ -990,6 +873,70 @@ bool AbstractInterpretation::isRecursiveCallSite(const CallICFGNode* callNode, nonRecursiveCallSites.end(); } +/// Recursion Handling Decision Methods +/// All recursion mode (TOP/WIDEN_ONLY/WIDEN_NARROW) logic is centralized here + +bool AbstractInterpretation::shouldSkipRecursiveCall( + const CallICFGNode* callNode, const FunObjVar* callee) +{ + if (!isRecursiveFun(callee)) + return false; + + switch (Options::HandleRecur()) + { + case TOP: + return true; // Always skip in TOP mode + case WIDEN_ONLY: + case WIDEN_NARROW: + return isRecursiveCallSite(callNode, callee); // Skip only within-SCC calls + default: + assert(false && "Unknown recursion handling mode"); + return false; + } +} + +void AbstractInterpretation::handleSkippedRecursiveCall(const CallICFGNode* callNode) +{ + // Delegate to recursiveCallPass which sets return value and stores to TOP + recursiveCallPass(callNode); +} + +bool AbstractInterpretation::shouldApplyNarrowingInRecursion(const FunObjVar* fun) +{ + if (!isRecursiveFun(fun)) + return true; // Non-recursive functions always apply narrowing + + switch (Options::HandleRecur()) + { + case TOP: + assert(false && "TOP mode should not reach cycle narrowing phase"); + return false; + case WIDEN_ONLY: + return false; // Skip narrowing for recursive functions + case WIDEN_NARROW: + return true; // Apply narrowing for recursive functions + default: + assert(false && "Unknown recursion handling mode"); + return false; + } +} + +bool AbstractInterpretation::shouldIncludeReturnEdge(const RetICFGNode* returnSite) +{ + switch (Options::HandleRecur()) + { + case TOP: + return true; // Always include in TOP mode + case WIDEN_ONLY: + case WIDEN_NARROW: + // Only include if the corresponding callsite has state + return hasAbsStateFromTrace(returnSite->getCallICFGNode()); + default: + assert(false && "Unknown recursion handling mode"); + return true; + } +} + bool AbstractInterpretation::isDirectCall(const CallICFGNode *callNode) { const FunObjVar *callfun =callNode->getCalledFunction(); @@ -1004,21 +951,16 @@ void AbstractInterpretation::directCallFunPass(const CallICFGNode *callNode) abstractTrace[callNode] = as; - const FunObjVar *calleeFun =callNode->getCalledFunction(); - if (Options::HandleRecur() == WIDEN_ONLY || Options::HandleRecur() == WIDEN_NARROW) - { - // If this CallICFGNode is a recursive callsite (i.e. this Node - // resides in a recursive function 'fun' and its callee function is - // in the same SCC with the fun), then skip it. Since the callee - // function is handled during the handling of WTO of the whole recursion. - if (isRecursiveCallSite(callNode, calleeFun)) - return; - } - else + const FunObjVar *calleeFun = callNode->getCalledFunction(); + + // Check if this recursive call should be skipped + if (shouldSkipRecursiveCall(callNode, calleeFun)) { - // When Options::HandleRecur() == TOP, skipRecursiveCall will handle recursions, - // thus should not reach this branch - assert(false && "Recursion mode TOP should not reach here!"); + // In TOP mode, set return value and stores to TOP + // In WIDEN_ONLY/WIDEN_NARROW, just skip (WTO handles it) + if (Options::HandleRecur() == TOP) + handleSkippedRecursiveCall(callNode); + return; } callSiteStack.push_back(callNode); @@ -1055,10 +997,14 @@ void AbstractInterpretation::indirectCallFunPass(const CallICFGNode *callNode) if(const FunObjVar* funObjVar = SVFUtil::dyn_cast(func_var)) { - if (Options::HandleRecur() == WIDEN_ONLY || Options::HandleRecur() == WIDEN_NARROW) + // Check if this recursive call should be skipped + if (shouldSkipRecursiveCall(callNode, funObjVar)) { - if (isRecursiveCallSite(callNode, funObjVar)) - return; + // In TOP mode, set return value and stores to TOP + // In WIDEN_ONLY/WIDEN_NARROW, just skip (WTO handles it) + if (Options::HandleRecur() == TOP) + handleSkippedRecursiveCall(callNode); + return; } const FunObjVar* callfun = funObjVar->getFunction(); @@ -1077,6 +1023,74 @@ void AbstractInterpretation::indirectCallFunPass(const CallICFGNode *callNode) } /// handle wto cycle (loop) using worklist-compatible widening/narrowing iteration +void AbstractInterpretation::handleICFGCycle(const ICFGCycleWTO* cycle) +{ + const ICFGNode* cycle_head = cycle->head()->getICFGNode(); + // Flag to indicate if we are in the increasing (widening) phase + bool increasing = true; + u32_t widen_delay = Options::WidenDelay(); + + // Infinite loop until a fixpoint is reached + for (u32_t cur_iter = 0;; cur_iter++) + { + // Get the abstract state before processing the cycle head + AbstractState prev_head_state; + if (hasAbsStateFromTrace(cycle_head)) + prev_head_state = abstractTrace[cycle_head]; + + // Process the cycle head node + handleICFGNode(cycle_head); + AbstractState cur_head_state = abstractTrace[cycle_head]; + + // Start widening or narrowing if cur_iter >= widen delay threshold + if (cur_iter >= widen_delay) + { + if (increasing) + { + // Apply widening operator + abstractTrace[cycle_head] = prev_head_state.widening(cur_head_state); + + if (abstractTrace[cycle_head] == prev_head_state) + { + // Widening fixpoint reached, switch to narrowing phase + increasing = false; + continue; + } + } + else + { + // Narrowing phase - check if narrowing should be applied + if (!shouldApplyNarrowingInRecursion(cycle_head->getFun())) + { + break; + } + + // Apply narrowing + abstractTrace[cycle_head] = prev_head_state.narrowing(cur_head_state); + if (abstractTrace[cycle_head] == prev_head_state) + { + // Narrowing fixpoint reached, exit loop + break; + } + } + } + + // Process cycle body components + for (const ICFGWTOComp* comp : cycle->getWTOComponents()) + { + if (const ICFGSingletonWTO* singleton = SVFUtil::dyn_cast(comp)) + { + handleICFGNode(singleton->getICFGNode()); + } + else if (const ICFGCycleWTO* subCycle = SVFUtil::dyn_cast(comp)) + { + // Handle nested cycle recursively + handleICFGCycle(subCycle); + } + } + } +} + void AbstractInterpretation::handleSVFStatement(const SVFStmt *stmt) { if (const AddrStmt *addr = SVFUtil::dyn_cast(stmt)) From b7be147f13a77f5cfc22b04abdeea98e0ff58373 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Sat, 31 Jan 2026 22:42:34 +1100 Subject: [PATCH 04/32] Continue to reduce the Options() handleRecur. (vibe-kanban 5f1e98b0) I think you could continue to narrow down the usage of OPtions::HandleREcur(). I mean maybe you could put the option check inside the function. For example, "// Check if this recursive call should be skipped if (shouldSkipRecursiveCall(callNode, funObjVar)) { // In TOP mode, set return value and stores to TOP // In WIDEN\_ONLY/WIDEN\_NARROW, just skip (WTO handles it) if (Options::HandleRecur() == TOP) handleSkippedRecursiveCall(callNode); return; } " maybe you should move the if check inside the function. You could try this way to reduce the Options::handleRecur as low as possible. --- .../AE/Svfexe/AbstractInterpretation.h | 15 +++----- svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 35 ++++++------------- 2 files changed, 15 insertions(+), 35 deletions(-) diff --git a/svf/include/AE/Svfexe/AbstractInterpretation.h b/svf/include/AE/Svfexe/AbstractInterpretation.h index 79ee2c72a..e0b3dbd8a 100644 --- a/svf/include/AE/Svfexe/AbstractInterpretation.h +++ b/svf/include/AE/Svfexe/AbstractInterpretation.h @@ -362,17 +362,12 @@ class AbstractInterpretation /// All recursion mode (TOP/WIDEN_ONLY/WIDEN_NARROW) logic is centralized here /** - * Determines if a recursive call should be skipped (not inlined). - * - TOP mode: Always skip recursive calls - * - WIDEN_ONLY/WIDEN_NARROW: Skip only recursive callsites (calls within same SCC) + * Attempts to handle a recursive call. Returns true if the call was handled + * (caller should return early), false if normal processing should continue. + * - TOP mode: Always handles recursive calls (sets return/stores to TOP) + * - WIDEN_ONLY/WIDEN_NARROW: Only handles recursive callsites within same SCC */ - bool shouldSkipRecursiveCall(const CallICFGNode* callNode, const FunObjVar* callee); - - /** - * Handles state for a skipped recursive call. - * Sets return value and stores to TOP (only meaningful in TOP mode). - */ - void handleSkippedRecursiveCall(const CallICFGNode* callNode); + bool handleRecursiveCall(const CallICFGNode* callNode, const FunObjVar* callee); /** * Determines if narrowing should be applied for a cycle head in a recursive function. diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index aac1765c5..cb3851720 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -876,7 +876,7 @@ bool AbstractInterpretation::isRecursiveCallSite(const CallICFGNode* callNode, /// Recursion Handling Decision Methods /// All recursion mode (TOP/WIDEN_ONLY/WIDEN_NARROW) logic is centralized here -bool AbstractInterpretation::shouldSkipRecursiveCall( +bool AbstractInterpretation::handleRecursiveCall( const CallICFGNode* callNode, const FunObjVar* callee) { if (!isRecursiveFun(callee)) @@ -885,22 +885,19 @@ bool AbstractInterpretation::shouldSkipRecursiveCall( switch (Options::HandleRecur()) { case TOP: - return true; // Always skip in TOP mode + // Always skip recursive calls, set return value and stores to TOP + recursiveCallPass(callNode); + return true; case WIDEN_ONLY: case WIDEN_NARROW: - return isRecursiveCallSite(callNode, callee); // Skip only within-SCC calls + // Only skip recursive callsites within same SCC (WTO handles the analysis) + return isRecursiveCallSite(callNode, callee); default: assert(false && "Unknown recursion handling mode"); return false; } } -void AbstractInterpretation::handleSkippedRecursiveCall(const CallICFGNode* callNode) -{ - // Delegate to recursiveCallPass which sets return value and stores to TOP - recursiveCallPass(callNode); -} - bool AbstractInterpretation::shouldApplyNarrowingInRecursion(const FunObjVar* fun) { if (!isRecursiveFun(fun)) @@ -953,15 +950,9 @@ void AbstractInterpretation::directCallFunPass(const CallICFGNode *callNode) const FunObjVar *calleeFun = callNode->getCalledFunction(); - // Check if this recursive call should be skipped - if (shouldSkipRecursiveCall(callNode, calleeFun)) - { - // In TOP mode, set return value and stores to TOP - // In WIDEN_ONLY/WIDEN_NARROW, just skip (WTO handles it) - if (Options::HandleRecur() == TOP) - handleSkippedRecursiveCall(callNode); + // Handle recursive call if applicable (returns true if handled) + if (handleRecursiveCall(callNode, calleeFun)) return; - } callSiteStack.push_back(callNode); @@ -997,15 +988,9 @@ void AbstractInterpretation::indirectCallFunPass(const CallICFGNode *callNode) if(const FunObjVar* funObjVar = SVFUtil::dyn_cast(func_var)) { - // Check if this recursive call should be skipped - if (shouldSkipRecursiveCall(callNode, funObjVar)) - { - // In TOP mode, set return value and stores to TOP - // In WIDEN_ONLY/WIDEN_NARROW, just skip (WTO handles it) - if (Options::HandleRecur() == TOP) - handleSkippedRecursiveCall(callNode); + // Handle recursive call if applicable (returns true if handled) + if (handleRecursiveCall(callNode, funObjVar)) return; - } const FunObjVar* callfun = funObjVar->getFunction(); callSiteStack.push_back(callNode); From e7f18f80487e39e161bd2762431f63b86cc8cccc Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Sat, 31 Jan 2026 23:04:07 +1100 Subject: [PATCH 05/32] Continue to reduce the Options() handleRecur. (vibe-kanban 5f1e98b0) I think you could continue to narrow down the usage of OPtions::HandleREcur(). I mean maybe you could put the option check inside the function. For example, "// Check if this recursive call should be skipped if (shouldSkipRecursiveCall(callNode, funObjVar)) { // In TOP mode, set return value and stores to TOP // In WIDEN\_ONLY/WIDEN\_NARROW, just skip (WTO handles it) if (Options::HandleRecur() == TOP) handleSkippedRecursiveCall(callNode); return; } " maybe you should move the if check inside the function. You could try this way to reduce the Options::handleRecur as low as possible. --- .../AE/Svfexe/AbstractInterpretation.h | 7 ------- svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 21 ++++--------------- 2 files changed, 4 insertions(+), 24 deletions(-) diff --git a/svf/include/AE/Svfexe/AbstractInterpretation.h b/svf/include/AE/Svfexe/AbstractInterpretation.h index e0b3dbd8a..952744eb8 100644 --- a/svf/include/AE/Svfexe/AbstractInterpretation.h +++ b/svf/include/AE/Svfexe/AbstractInterpretation.h @@ -378,13 +378,6 @@ class AbstractInterpretation */ bool shouldApplyNarrowingInRecursion(const FunObjVar* fun); - /** - * Determines if a return edge should contribute to state merging. - * - TOP mode: Always include - * - WIDEN_ONLY/WIDEN_NARROW: Only if callsite has state - */ - bool shouldIncludeReturnEdge(const RetICFGNode* returnSite); - // there data should be shared with subclasses Map> func_map; diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index cb3851720..6df08a8df 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -234,8 +234,10 @@ bool AbstractInterpretation::mergeStatesFromPredecessors(const ICFGNode * icfgNo else if (const RetCFGEdge *retCfgEdge = SVFUtil::dyn_cast(edge)) { - const RetICFGNode* returnSite = SVFUtil::dyn_cast(icfgNode); - if (shouldIncludeReturnEdge(returnSite)) + // Only include return edge if the corresponding callsite was processed + // (skipped recursive callsites in WIDEN_ONLY/WIDEN_NARROW won't have state) + const RetICFGNode* retNode = SVFUtil::dyn_cast(icfgNode); + if (hasAbsStateFromTrace(retNode->getCallICFGNode())) { workList.push_back(abstractTrace[retCfgEdge->getSrcNode()]); } @@ -918,21 +920,6 @@ bool AbstractInterpretation::shouldApplyNarrowingInRecursion(const FunObjVar* fu } } -bool AbstractInterpretation::shouldIncludeReturnEdge(const RetICFGNode* returnSite) -{ - switch (Options::HandleRecur()) - { - case TOP: - return true; // Always include in TOP mode - case WIDEN_ONLY: - case WIDEN_NARROW: - // Only include if the corresponding callsite has state - return hasAbsStateFromTrace(returnSite->getCallICFGNode()); - default: - assert(false && "Unknown recursion handling mode"); - return true; - } -} bool AbstractInterpretation::isDirectCall(const CallICFGNode *callNode) { From 500c22f9ad0ea7e201ca40810551fe00750f1af2 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Mon, 2 Feb 2026 12:05:22 +1100 Subject: [PATCH 06/32] Continue to reduce the Options() handleRecur. (vibe-kanban 5f1e98b0) I think you could continue to narrow down the usage of OPtions::HandleREcur(). I mean maybe you could put the option check inside the function. For example, "// Check if this recursive call should be skipped if (shouldSkipRecursiveCall(callNode, funObjVar)) { // In TOP mode, set return value and stores to TOP // In WIDEN\_ONLY/WIDEN\_NARROW, just skip (WTO handles it) if (Options::HandleRecur() == TOP) handleSkippedRecursiveCall(callNode); return; } " maybe you should move the if check inside the function. You could try this way to reduce the Options::handleRecur as low as possible. --- .../AE/Svfexe/AbstractInterpretation.h | 29 +++++++++++++++---- svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 17 ++++++----- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/svf/include/AE/Svfexe/AbstractInterpretation.h b/svf/include/AE/Svfexe/AbstractInterpretation.h index 952744eb8..1a94378c2 100644 --- a/svf/include/AE/Svfexe/AbstractInterpretation.h +++ b/svf/include/AE/Svfexe/AbstractInterpretation.h @@ -362,12 +362,31 @@ class AbstractInterpretation /// All recursion mode (TOP/WIDEN_ONLY/WIDEN_NARROW) logic is centralized here /** - * Attempts to handle a recursive call. Returns true if the call was handled - * (caller should return early), false if normal processing should continue. - * - TOP mode: Always handles recursive calls (sets return/stores to TOP) - * - WIDEN_ONLY/WIDEN_NARROW: Only handles recursive callsites within same SCC + * Determines whether to skip (not inline) a call to a potentially recursive function. + * Returns true if the call should be skipped, false if normal inlining should proceed. + * + * Example: + * int factorial(int n) { + * if (n <= 1) return 1; + * return n * factorial(n - 1); // inner call (recursive callsite) + * } + * int main() { + * return factorial(5); // outer call (entry into recursion) + * } + * + * Behavior by mode: + * - TOP mode: + * Both calls are skipped. Return value and stores are set to TOP. + * Result: factorial returns [-inf, +inf] + * + * - WIDEN_ONLY / WIDEN_NARROW mode: + * Outer call (main -> factorial): NOT skipped, inlined normally. + * Inner call (factorial -> factorial): Skipped, WTO cycle handles the iteration. + * The recursive function body is analyzed via handleICFGCycle() with widening + * (and narrowing for WIDEN_NARROW) to reach a fixpoint. + * Result: factorial returns [10000, +inf] (WIDEN_ONLY) or [10000, 10000] (WIDEN_NARROW) */ - bool handleRecursiveCall(const CallICFGNode* callNode, const FunObjVar* callee); + bool skipRecursiveCall(const CallICFGNode* callNode, const FunObjVar* callee); /** * Determines if narrowing should be applied for a cycle head in a recursive function. diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index 6df08a8df..6a10c6f82 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -878,21 +878,24 @@ bool AbstractInterpretation::isRecursiveCallSite(const CallICFGNode* callNode, /// Recursion Handling Decision Methods /// All recursion mode (TOP/WIDEN_ONLY/WIDEN_NARROW) logic is centralized here -bool AbstractInterpretation::handleRecursiveCall( +bool AbstractInterpretation::skipRecursiveCall( const CallICFGNode* callNode, const FunObjVar* callee) { + // Non-recursive function: never skip, always inline if (!isRecursiveFun(callee)) return false; switch (Options::HandleRecur()) { case TOP: - // Always skip recursive calls, set return value and stores to TOP + // Skip all calls to recursive functions, set return value and stores to TOP recursiveCallPass(callNode); return true; case WIDEN_ONLY: case WIDEN_NARROW: - // Only skip recursive callsites within same SCC (WTO handles the analysis) + // Skip only recursive callsites (within same SCC), not the entry call. + // Entry call is inlined; recursive callsites are skipped so that + // handleICFGCycle() can analyze the function body with widening/narrowing. return isRecursiveCallSite(callNode, callee); default: assert(false && "Unknown recursion handling mode"); @@ -937,8 +940,8 @@ void AbstractInterpretation::directCallFunPass(const CallICFGNode *callNode) const FunObjVar *calleeFun = callNode->getCalledFunction(); - // Handle recursive call if applicable (returns true if handled) - if (handleRecursiveCall(callNode, calleeFun)) + // Skip recursive call if applicable (returns true if skipped) + if (skipRecursiveCall(callNode, calleeFun)) return; callSiteStack.push_back(callNode); @@ -975,8 +978,8 @@ void AbstractInterpretation::indirectCallFunPass(const CallICFGNode *callNode) if(const FunObjVar* funObjVar = SVFUtil::dyn_cast(func_var)) { - // Handle recursive call if applicable (returns true if handled) - if (handleRecursiveCall(callNode, funObjVar)) + // Skip recursive call if applicable (returns true if skipped) + if (skipRecursiveCall(callNode, funObjVar)) return; const FunObjVar* callfun = funObjVar->getFunction(); From 94ab144b92ab0a5c07fbc78b2175add5434de19f Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Mon, 2 Feb 2026 16:25:29 +1100 Subject: [PATCH 07/32] Continue to reduce the Options() handleRecur. (vibe-kanban 5f1e98b0) I think you could continue to narrow down the usage of OPtions::HandleREcur(). I mean maybe you could put the option check inside the function. For example, "// Check if this recursive call should be skipped if (shouldSkipRecursiveCall(callNode, funObjVar)) { // In TOP mode, set return value and stores to TOP // In WIDEN\_ONLY/WIDEN\_NARROW, just skip (WTO handles it) if (Options::HandleRecur() == TOP) handleSkippedRecursiveCall(callNode); return; } " maybe you should move the if check inside the function. You could try this way to reduce the Options::handleRecur as low as possible. --- .../AE/Svfexe/AbstractInterpretation.h | 24 +++--- svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 79 +++++++++++++------ 2 files changed, 66 insertions(+), 37 deletions(-) diff --git a/svf/include/AE/Svfexe/AbstractInterpretation.h b/svf/include/AE/Svfexe/AbstractInterpretation.h index 1a94378c2..ec83155bd 100644 --- a/svf/include/AE/Svfexe/AbstractInterpretation.h +++ b/svf/include/AE/Svfexe/AbstractInterpretation.h @@ -358,13 +358,14 @@ class AbstractInterpretation virtual bool isIndirectCall(const CallICFGNode* callNode); virtual void indirectCallFunPass(const CallICFGNode* callNode); - /// Recursion Handling Decision Methods - /// All recursion mode (TOP/WIDEN_ONLY/WIDEN_NARROW) logic is centralized here - /** * Determines whether to skip (not inline) a call to a potentially recursive function. * Returns true if the call should be skipped, false if normal inlining should proceed. * + * This function is mode-independent: it only checks whether the call is a recursive + * callsite (within the same SCC). The actual handling of recursive functions is done + * uniformly via handleICFGCycle(), with mode-specific behavior inside that function. + * * Example: * int factorial(int n) { * if (n <= 1) return 1; @@ -374,17 +375,14 @@ class AbstractInterpretation * return factorial(5); // outer call (entry into recursion) * } * - * Behavior by mode: - * - TOP mode: - * Both calls are skipped. Return value and stores are set to TOP. - * Result: factorial returns [-inf, +inf] + * For all modes (TOP/WIDEN_ONLY/WIDEN_NARROW): + * - Outer call (main -> factorial): NOT skipped, inlined into handleICFGCycle() + * - Inner call (factorial -> factorial): Skipped (back-edge of the cycle) * - * - WIDEN_ONLY / WIDEN_NARROW mode: - * Outer call (main -> factorial): NOT skipped, inlined normally. - * Inner call (factorial -> factorial): Skipped, WTO cycle handles the iteration. - * The recursive function body is analyzed via handleICFGCycle() with widening - * (and narrowing for WIDEN_NARROW) to reach a fixpoint. - * Result: factorial returns [10000, +inf] (WIDEN_ONLY) or [10000, 10000] (WIDEN_NARROW) + * The difference between modes is in handleICFGCycle(): + * - TOP: Sets all values to TOP without iteration. Result: [-inf, +inf] + * - WIDEN_ONLY: Widening iteration only. Result: [10000, +inf] + * - WIDEN_NARROW: Widening + narrowing. Result: [10000, 10000] */ bool skipRecursiveCall(const CallICFGNode* callNode, const FunObjVar* callee); diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index 6a10c6f82..3b4841397 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -875,9 +875,6 @@ bool AbstractInterpretation::isRecursiveCallSite(const CallICFGNode* callNode, nonRecursiveCallSites.end(); } -/// Recursion Handling Decision Methods -/// All recursion mode (TOP/WIDEN_ONLY/WIDEN_NARROW) logic is centralized here - bool AbstractInterpretation::skipRecursiveCall( const CallICFGNode* callNode, const FunObjVar* callee) { @@ -885,33 +882,26 @@ bool AbstractInterpretation::skipRecursiveCall( if (!isRecursiveFun(callee)) return false; - switch (Options::HandleRecur()) - { - case TOP: - // Skip all calls to recursive functions, set return value and stores to TOP - recursiveCallPass(callNode); - return true; - case WIDEN_ONLY: - case WIDEN_NARROW: - // Skip only recursive callsites (within same SCC), not the entry call. - // Entry call is inlined; recursive callsites are skipped so that - // handleICFGCycle() can analyze the function body with widening/narrowing. - return isRecursiveCallSite(callNode, callee); - default: - assert(false && "Unknown recursion handling mode"); - return false; - } + // For recursive functions, skip only recursive callsites (within same SCC). + // Entry calls (from outside SCC) are not skipped - they are inlined so that + // handleICFGCycle() can analyze the function body. + // This applies uniformly to all modes (TOP/WIDEN_ONLY/WIDEN_NARROW). + return isRecursiveCallSite(callNode, callee); } bool AbstractInterpretation::shouldApplyNarrowingInRecursion(const FunObjVar* fun) { + // Non-recursive functions (regular loops): always apply narrowing if (!isRecursiveFun(fun)) - return true; // Non-recursive functions always apply narrowing + return true; + // Recursive functions: depends on mode + // Note: TOP mode handles recursive cycles separately in handleICFGCycle, + // so this should not be reached for recursive functions in TOP mode. switch (Options::HandleRecur()) { case TOP: - assert(false && "TOP mode should not reach cycle narrowing phase"); + assert(false && "TOP mode should not reach cycle narrowing phase for recursive functions"); return false; case WIDEN_ONLY: return false; // Skip narrowing for recursive functions @@ -997,15 +987,56 @@ void AbstractInterpretation::indirectCallFunPass(const CallICFGNode *callNode) } } -/// handle wto cycle (loop) using worklist-compatible widening/narrowing iteration +/// handle wto cycle (loop) using widening/narrowing iteration +/// +/// This handles both regular loops and recursive function cycles. +/// The behavior depends on Options::HandleRecur() for recursive functions: +/// +/// - TOP mode (recursive function cycle only): +/// Sets all values to TOP without iteration. This is the most conservative +/// but fastest approach. +/// +/// - WIDEN_ONLY mode: +/// Iterates with widening until fixpoint. No narrowing phase. +/// For recursive functions, this gives upper bounds (e.g., [10000, +inf]). +/// +/// - WIDEN_NARROW mode: +/// Iterates with widening, then narrows to refine the result. +/// For recursive functions, this gives precise bounds (e.g., [10000, 10000]). +/// +/// For regular (non-recursive) loops, all modes use widening + narrowing. void AbstractInterpretation::handleICFGCycle(const ICFGCycleWTO* cycle) { const ICFGNode* cycle_head = cycle->head()->getICFGNode(); - // Flag to indicate if we are in the increasing (widening) phase + + // TOP mode for recursive function cycles: set states to TOP and return immediately + if (Options::HandleRecur() == TOP && isRecursiveFun(cycle_head->getFun())) + { + // Process cycle head once to establish state + handleICFGNode(cycle_head); + + // Process cycle body once, setting all states + for (const ICFGWTOComp* comp : cycle->getWTOComponents()) + { + if (const ICFGSingletonWTO* singleton = SVFUtil::dyn_cast(comp)) + { + handleICFGNode(singleton->getICFGNode()); + } + else if (const ICFGCycleWTO* subCycle = SVFUtil::dyn_cast(comp)) + { + handleICFGCycle(subCycle); + } + } + + // Set cycle head state to TOP to represent unknown recursive result + abstractTrace[cycle_head] = abstractTrace[cycle_head].top(); + return; + } + + // WIDEN_ONLY / WIDEN_NARROW modes (and regular loops): iterate until fixpoint bool increasing = true; u32_t widen_delay = Options::WidenDelay(); - // Infinite loop until a fixpoint is reached for (u32_t cur_iter = 0;; cur_iter++) { // Get the abstract state before processing the cycle head From 809a397af306e947e4780e965449bea36ab0f2e0 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Mon, 2 Feb 2026 21:17:12 +1100 Subject: [PATCH 08/32] Continue to reduce the Options() handleRecur. (vibe-kanban 5f1e98b0) I think you could continue to narrow down the usage of OPtions::HandleREcur(). I mean maybe you could put the option check inside the function. For example, "// Check if this recursive call should be skipped if (shouldSkipRecursiveCall(callNode, funObjVar)) { // In TOP mode, set return value and stores to TOP // In WIDEN\_ONLY/WIDEN\_NARROW, just skip (WTO handles it) if (Options::HandleRecur() == TOP) handleSkippedRecursiveCall(callNode); return; } " maybe you should move the if check inside the function. You could try this way to reduce the Options::handleRecur as low as possible. --- svf/include/AE/Svfexe/AbstractInterpretation.h | 15 +++++++++------ svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 6 +++--- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/svf/include/AE/Svfexe/AbstractInterpretation.h b/svf/include/AE/Svfexe/AbstractInterpretation.h index ec83155bd..672f7922f 100644 --- a/svf/include/AE/Svfexe/AbstractInterpretation.h +++ b/svf/include/AE/Svfexe/AbstractInterpretation.h @@ -387,13 +387,16 @@ class AbstractInterpretation bool skipRecursiveCall(const CallICFGNode* callNode, const FunObjVar* callee); /** - * Determines if narrowing should be applied for a cycle head in a recursive function. - * - TOP mode: Should not reach here (asserts) - * - WIDEN_ONLY: Returns false (skip narrowing) - * - WIDEN_NARROW: Returns true (apply narrowing) - * For non-recursive functions, always returns true. + * Determines if narrowing should be applied for a cycle head. + * Called during the narrowing phase of handleICFGCycle(). + * + * For non-recursive functions (regular loops): always returns true. + * For recursive functions: depends on Options::HandleRecur(): + * - TOP mode: Should not reach here (asserts), TOP mode exits early in handleICFGCycle + * - WIDEN_ONLY: Returns false (skip narrowing) + * - WIDEN_NARROW: Returns true (apply narrowing) */ - bool shouldApplyNarrowingInRecursion(const FunObjVar* fun); + bool shouldApplyNarrowing(const FunObjVar* fun); // there data should be shared with subclasses Map> func_map; diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index 3b4841397..8aec1c422 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -889,7 +889,7 @@ bool AbstractInterpretation::skipRecursiveCall( return isRecursiveCallSite(callNode, callee); } -bool AbstractInterpretation::shouldApplyNarrowingInRecursion(const FunObjVar* fun) +bool AbstractInterpretation::shouldApplyNarrowing(const FunObjVar* fun) { // Non-recursive functions (regular loops): always apply narrowing if (!isRecursiveFun(fun)) @@ -901,7 +901,7 @@ bool AbstractInterpretation::shouldApplyNarrowingInRecursion(const FunObjVar* fu switch (Options::HandleRecur()) { case TOP: - assert(false && "TOP mode should not reach cycle narrowing phase for recursive functions"); + assert(false && "TOP mode should not reach narrowing phase for recursive functions"); return false; case WIDEN_ONLY: return false; // Skip narrowing for recursive functions @@ -1066,7 +1066,7 @@ void AbstractInterpretation::handleICFGCycle(const ICFGCycleWTO* cycle) else { // Narrowing phase - check if narrowing should be applied - if (!shouldApplyNarrowingInRecursion(cycle_head->getFun())) + if (!shouldApplyNarrowing(cycle_head->getFun())) { break; } From ec7f7ca515d156dd7181fb346704c062943dca1b Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Mon, 2 Feb 2026 21:37:19 +1100 Subject: [PATCH 09/32] Continue to reduce the Options() handleRecur. (vibe-kanban 5f1e98b0) I think you could continue to narrow down the usage of OPtions::HandleREcur(). I mean maybe you could put the option check inside the function. For example, "// Check if this recursive call should be skipped if (shouldSkipRecursiveCall(callNode, funObjVar)) { // In TOP mode, set return value and stores to TOP // In WIDEN\_ONLY/WIDEN\_NARROW, just skip (WTO handles it) if (Options::HandleRecur() == TOP) handleSkippedRecursiveCall(callNode); return; } " maybe you should move the if check inside the function. You could try this way to reduce the Options::handleRecur as low as possible. --- svf/include/AE/Svfexe/AbstractInterpretation.h | 5 +++-- svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/svf/include/AE/Svfexe/AbstractInterpretation.h b/svf/include/AE/Svfexe/AbstractInterpretation.h index 672f7922f..4fcfbefe9 100644 --- a/svf/include/AE/Svfexe/AbstractInterpretation.h +++ b/svf/include/AE/Svfexe/AbstractInterpretation.h @@ -267,11 +267,12 @@ class AbstractInterpretation virtual void handleSVFStatement(const SVFStmt* stmt); /** - * Check if this callnode is recursive call and skip it. + * Sets all store values in the recursive function to TOP. + * This is called when skipping a recursive call in TOP mode. * * @param callnode CallICFGNode which calls a recursive function */ - virtual void SkipRecursiveCall(const CallICFGNode* callnode); + virtual void setRecursiveCallStoresToTop(const CallICFGNode* callnode); /** diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index 8aec1c422..7eb987661 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -852,7 +852,7 @@ bool AbstractInterpretation::isRecursiveCall(const CallICFGNode *callNode) void AbstractInterpretation::recursiveCallPass(const CallICFGNode *callNode) { AbstractState& as = getAbsStateFromTrace(callNode); - SkipRecursiveCall(callNode); + setRecursiveCallStoresToTop(callNode); const RetICFGNode *retNode = callNode->getRetICFGNode(); if (retNode->getSVFStmts().size() > 0) { @@ -1158,7 +1158,7 @@ void AbstractInterpretation::handleSVFStatement(const SVFStmt *stmt) !getAbsStateFromTrace(stmt->getICFGNode())[IRGraph::NullPtr].isAddr()); } -void AbstractInterpretation::SkipRecursiveCall(const CallICFGNode *callNode) +void AbstractInterpretation::setRecursiveCallStoresToTop(const CallICFGNode *callNode) { AbstractState& as = getAbsStateFromTrace(callNode); const RetICFGNode *retNode = callNode->getRetICFGNode(); From 8b0605c46f99b7d5712fde1b39957cd33cb77115 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Mon, 2 Feb 2026 21:47:57 +1100 Subject: [PATCH 10/32] Continue to reduce the Options() handleRecur. (vibe-kanban 5f1e98b0) I think you could continue to narrow down the usage of OPtions::HandleREcur(). I mean maybe you could put the option check inside the function. For example, "// Check if this recursive call should be skipped if (shouldSkipRecursiveCall(callNode, funObjVar)) { // In TOP mode, set return value and stores to TOP // In WIDEN\_ONLY/WIDEN\_NARROW, just skip (WTO handles it) if (Options::HandleRecur() == TOP) handleSkippedRecursiveCall(callNode); return; } " maybe you should move the if check inside the function. You could try this way to reduce the Options::handleRecur as low as possible. --- .../AE/Svfexe/AbstractInterpretation.h | 10 ++- svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 80 ++++++++++++------- 2 files changed, 60 insertions(+), 30 deletions(-) diff --git a/svf/include/AE/Svfexe/AbstractInterpretation.h b/svf/include/AE/Svfexe/AbstractInterpretation.h index 4fcfbefe9..6aa365152 100644 --- a/svf/include/AE/Svfexe/AbstractInterpretation.h +++ b/svf/include/AE/Svfexe/AbstractInterpretation.h @@ -385,7 +385,15 @@ class AbstractInterpretation * - WIDEN_ONLY: Widening iteration only. Result: [10000, +inf] * - WIDEN_NARROW: Widening + narrowing. Result: [10000, 10000] */ - bool skipRecursiveCall(const CallICFGNode* callNode, const FunObjVar* callee); + bool skipRecursiveCall(const CallICFGNode* callNode); + + /** + * Gets the callee function for a call node. + * For direct calls, returns callNode->getCalledFunction(). + * For indirect calls, resolves the callee through pointer analysis. + * Returns nullptr if callee cannot be determined. + */ + const FunObjVar* getCallee(const CallICFGNode* callNode); /** * Determines if narrowing should be applied for a cycle head. diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index 7eb987661..a654be27d 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -875,9 +875,41 @@ bool AbstractInterpretation::isRecursiveCallSite(const CallICFGNode* callNode, nonRecursiveCallSites.end(); } -bool AbstractInterpretation::skipRecursiveCall( - const CallICFGNode* callNode, const FunObjVar* callee) +const FunObjVar* AbstractInterpretation::getCallee(const CallICFGNode* callNode) { + // Direct call: get callee directly from call node + if (const FunObjVar* callee = callNode->getCalledFunction()) + return callee; + + // Indirect call: resolve callee through pointer analysis + const auto callsiteMaps = svfir->getIndirectCallsites(); + auto it = callsiteMaps.find(callNode); + if (it == callsiteMaps.end()) + return nullptr; + + NodeID call_id = it->second; + if (!hasAbsStateFromTrace(callNode)) + return nullptr; + + AbstractState& as = getAbsStateFromTrace(callNode); + if (!as.inVarToAddrsTable(call_id)) + return nullptr; + + AbstractValue Addrs = as[call_id]; + if (Addrs.getAddrs().empty()) + return nullptr; + + NodeID addr = *Addrs.getAddrs().begin(); + SVFVar* func_var = svfir->getGNode(as.getIDFromAddr(addr)); + return SVFUtil::dyn_cast(func_var); +} + +bool AbstractInterpretation::skipRecursiveCall(const CallICFGNode* callNode) +{ + const FunObjVar* callee = getCallee(callNode); + if (!callee) + return false; + // Non-recursive function: never skip, always inline if (!isRecursiveFun(callee)) return false; @@ -928,12 +960,11 @@ void AbstractInterpretation::directCallFunPass(const CallICFGNode *callNode) abstractTrace[callNode] = as; - const FunObjVar *calleeFun = callNode->getCalledFunction(); - // Skip recursive call if applicable (returns true if skipped) - if (skipRecursiveCall(callNode, calleeFun)) + if (skipRecursiveCall(callNode)) return; + const FunObjVar *calleeFun = callNode->getCalledFunction(); callSiteStack.push_back(callNode); // Use worklist-based function handling instead of recursive WTO component handling @@ -956,35 +987,26 @@ bool AbstractInterpretation::isIndirectCall(const CallICFGNode *callNode) void AbstractInterpretation::indirectCallFunPass(const CallICFGNode *callNode) { AbstractState& as = getAbsStateFromTrace(callNode); - const auto callsiteMaps = svfir->getIndirectCallsites(); - NodeID call_id = callsiteMaps.at(callNode); - if (!as.inVarToAddrsTable(call_id)) - { + + // Skip recursive call if applicable (returns true if skipped) + if (skipRecursiveCall(callNode)) return; - } - AbstractValue Addrs = as[call_id]; - NodeID addr = *Addrs.getAddrs().begin(); - SVFVar *func_var = svfir->getGNode(as.getIDFromAddr(addr)); - if(const FunObjVar* funObjVar = SVFUtil::dyn_cast(func_var)) - { - // Skip recursive call if applicable (returns true if skipped) - if (skipRecursiveCall(callNode, funObjVar)) - return; + const FunObjVar* callee = getCallee(callNode); + if (!callee) + return; - const FunObjVar* callfun = funObjVar->getFunction(); - callSiteStack.push_back(callNode); - abstractTrace[callNode] = as; + callSiteStack.push_back(callNode); + abstractTrace[callNode] = as; - // Use worklist-based function handling instead of recursive WTO component handling - const ICFGNode* calleeEntry = icfg->getFunEntryICFGNode(callfun); - handleFunction(calleeEntry); + // Use worklist-based function handling instead of recursive WTO component handling + const ICFGNode* calleeEntry = icfg->getFunEntryICFGNode(callee); + handleFunction(calleeEntry); - callSiteStack.pop_back(); - // handle Ret node - const RetICFGNode* retNode = callNode->getRetICFGNode(); - abstractTrace[retNode] = abstractTrace[callNode]; - } + callSiteStack.pop_back(); + // handle Ret node + const RetICFGNode* retNode = callNode->getRetICFGNode(); + abstractTrace[retNode] = abstractTrace[callNode]; } /// handle wto cycle (loop) using widening/narrowing iteration From e6942d51c5762f827da99eb6e3a63ca29504628a Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Mon, 2 Feb 2026 22:01:48 +1100 Subject: [PATCH 11/32] Continue to reduce the Options() handleRecur. (vibe-kanban 5f1e98b0) I think you could continue to narrow down the usage of OPtions::HandleREcur(). I mean maybe you could put the option check inside the function. For example, "// Check if this recursive call should be skipped if (shouldSkipRecursiveCall(callNode, funObjVar)) { // In TOP mode, set return value and stores to TOP // In WIDEN\_ONLY/WIDEN\_NARROW, just skip (WTO handles it) if (Options::HandleRecur() == TOP) handleSkippedRecursiveCall(callNode); return; } " maybe you should move the if check inside the function. You could try this way to reduce the Options::handleRecur as low as possible. --- svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 23 +++++--------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index a654be27d..7a10a504b 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -1031,27 +1031,16 @@ void AbstractInterpretation::handleICFGCycle(const ICFGCycleWTO* cycle) { const ICFGNode* cycle_head = cycle->head()->getICFGNode(); - // TOP mode for recursive function cycles: set states to TOP and return immediately + // TOP mode for recursive function cycles: use recursiveCallPass to set + // all stores and return value to TOP, maintaining original semantics if (Options::HandleRecur() == TOP && isRecursiveFun(cycle_head->getFun())) { - // Process cycle head once to establish state - handleICFGNode(cycle_head); - - // Process cycle body once, setting all states - for (const ICFGWTOComp* comp : cycle->getWTOComponents()) + // Get the call node from callSiteStack (the call that entered this function) + if (!callSiteStack.empty()) { - if (const ICFGSingletonWTO* singleton = SVFUtil::dyn_cast(comp)) - { - handleICFGNode(singleton->getICFGNode()); - } - else if (const ICFGCycleWTO* subCycle = SVFUtil::dyn_cast(comp)) - { - handleICFGCycle(subCycle); - } + const CallICFGNode* callNode = callSiteStack.back(); + recursiveCallPass(callNode); } - - // Set cycle head state to TOP to represent unknown recursive result - abstractTrace[cycle_head] = abstractTrace[cycle_head].top(); return; } From 9c72e10b96ff6aab392510c2b617fdfcfd5c9689 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Mon, 2 Feb 2026 22:27:12 +1100 Subject: [PATCH 12/32] Continue to reduce the Options() handleRecur. (vibe-kanban 5f1e98b0) I think you could continue to narrow down the usage of OPtions::HandleREcur(). I mean maybe you could put the option check inside the function. For example, "// Check if this recursive call should be skipped if (shouldSkipRecursiveCall(callNode, funObjVar)) { // In TOP mode, set return value and stores to TOP // In WIDEN\_ONLY/WIDEN\_NARROW, just skip (WTO handles it) if (Options::HandleRecur() == TOP) handleSkippedRecursiveCall(callNode); return; } " maybe you should move the if check inside the function. You could try this way to reduce the Options::handleRecur as low as possible. --- svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 46 +++++++++++++++----- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index 7a10a504b..d62e11e0c 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -1009,24 +1009,46 @@ void AbstractInterpretation::indirectCallFunPass(const CallICFGNode *callNode) abstractTrace[retNode] = abstractTrace[callNode]; } -/// handle wto cycle (loop) using widening/narrowing iteration +/// Handle WTO cycle (loop or recursive function) using widening/narrowing iteration. /// -/// This handles both regular loops and recursive function cycles. -/// The behavior depends on Options::HandleRecur() for recursive functions: +/// Widening is applied at the cycle head to ensure termination of the analysis. +/// The cycle head's abstract state is iteratively updated until a fixpoint is reached. /// -/// - TOP mode (recursive function cycle only): -/// Sets all values to TOP without iteration. This is the most conservative -/// but fastest approach. +/// == What is being widened == +/// The abstract state at the cycle head node, which includes: +/// - Variable values (intervals) that may change across loop iterations +/// - For example, a loop counter `i` starting at 0 and incrementing each iteration +/// +/// == Regular loops (non-recursive functions) == +/// All modes (TOP/WIDEN_ONLY/WIDEN_NARROW) behave the same for regular loops: +/// 1. Widening phase: Iterate until the cycle head state stabilizes +/// Example: for(i=0; i<100; i++) -> i widens to [0, +inf] +/// 2. Narrowing phase: Refine the over-approximation from widening +/// Example: [0, +inf] narrows to [0, 100] using loop condition +/// +/// == Recursive function cycles == +/// Behavior depends on Options::HandleRecur(): +/// +/// - TOP mode: +/// Does not iterate. Calls recursiveCallPass() to set all stores and +/// return value to TOP immediately. This is the most conservative but fastest. +/// Example: +/// int factorial(int n) { return n <= 1 ? 1 : n * factorial(n-1); } +/// factorial(5) -> returns [-inf, +inf] /// /// - WIDEN_ONLY mode: -/// Iterates with widening until fixpoint. No narrowing phase. -/// For recursive functions, this gives upper bounds (e.g., [10000, +inf]). +/// Widening phase only, no narrowing for recursive functions. +/// The recursive function body is analyzed with widening until fixpoint. +/// Example: +/// int factorial(int n) { return n <= 1 ? 1 : n * factorial(n-1); } +/// factorial(5) -> returns [10000, +inf] (widened upper bound) /// /// - WIDEN_NARROW mode: -/// Iterates with widening, then narrows to refine the result. -/// For recursive functions, this gives precise bounds (e.g., [10000, 10000]). -/// -/// For regular (non-recursive) loops, all modes use widening + narrowing. +/// Both widening and narrowing phases for recursive functions. +/// After widening reaches fixpoint, narrowing refines the result. +/// Example: +/// int factorial(int n) { return n <= 1 ? 1 : n * factorial(n-1); } +/// factorial(5) -> returns [10000, 10000] (precise after narrowing) void AbstractInterpretation::handleICFGCycle(const ICFGCycleWTO* cycle) { const ICFGNode* cycle_head = cycle->head()->getICFGNode(); From e0d7674fb095ea0ac301bf3ac6381613dfdd39f3 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Mon, 2 Feb 2026 22:37:20 +1100 Subject: [PATCH 13/32] Continue to reduce the Options() handleRecur. (vibe-kanban 5f1e98b0) I think you could continue to narrow down the usage of OPtions::HandleREcur(). I mean maybe you could put the option check inside the function. For example, "// Check if this recursive call should be skipped if (shouldSkipRecursiveCall(callNode, funObjVar)) { // In TOP mode, set return value and stores to TOP // In WIDEN\_ONLY/WIDEN\_NARROW, just skip (WTO handles it) if (Options::HandleRecur() == TOP) handleSkippedRecursiveCall(callNode); return; } " maybe you should move the if check inside the function. You could try this way to reduce the Options::handleRecur as low as possible. --- .../AE/Svfexe/AbstractInterpretation.h | 50 ------------------- svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 13 +++-- 2 files changed, 10 insertions(+), 53 deletions(-) diff --git a/svf/include/AE/Svfexe/AbstractInterpretation.h b/svf/include/AE/Svfexe/AbstractInterpretation.h index 6aa365152..a7c9bf075 100644 --- a/svf/include/AE/Svfexe/AbstractInterpretation.h +++ b/svf/include/AE/Svfexe/AbstractInterpretation.h @@ -266,12 +266,6 @@ class AbstractInterpretation */ virtual void handleSVFStatement(const SVFStmt* stmt); - /** - * Sets all store values in the recursive function to TOP. - * This is called when skipping a recursive call in TOP mode. - * - * @param callnode CallICFGNode which calls a recursive function - */ virtual void setRecursiveCallStoresToTop(const CallICFGNode* callnode); @@ -359,52 +353,8 @@ class AbstractInterpretation virtual bool isIndirectCall(const CallICFGNode* callNode); virtual void indirectCallFunPass(const CallICFGNode* callNode); - /** - * Determines whether to skip (not inline) a call to a potentially recursive function. - * Returns true if the call should be skipped, false if normal inlining should proceed. - * - * This function is mode-independent: it only checks whether the call is a recursive - * callsite (within the same SCC). The actual handling of recursive functions is done - * uniformly via handleICFGCycle(), with mode-specific behavior inside that function. - * - * Example: - * int factorial(int n) { - * if (n <= 1) return 1; - * return n * factorial(n - 1); // inner call (recursive callsite) - * } - * int main() { - * return factorial(5); // outer call (entry into recursion) - * } - * - * For all modes (TOP/WIDEN_ONLY/WIDEN_NARROW): - * - Outer call (main -> factorial): NOT skipped, inlined into handleICFGCycle() - * - Inner call (factorial -> factorial): Skipped (back-edge of the cycle) - * - * The difference between modes is in handleICFGCycle(): - * - TOP: Sets all values to TOP without iteration. Result: [-inf, +inf] - * - WIDEN_ONLY: Widening iteration only. Result: [10000, +inf] - * - WIDEN_NARROW: Widening + narrowing. Result: [10000, 10000] - */ bool skipRecursiveCall(const CallICFGNode* callNode); - - /** - * Gets the callee function for a call node. - * For direct calls, returns callNode->getCalledFunction(). - * For indirect calls, resolves the callee through pointer analysis. - * Returns nullptr if callee cannot be determined. - */ const FunObjVar* getCallee(const CallICFGNode* callNode); - - /** - * Determines if narrowing should be applied for a cycle head. - * Called during the narrowing phase of handleICFGCycle(). - * - * For non-recursive functions (regular loops): always returns true. - * For recursive functions: depends on Options::HandleRecur(): - * - TOP mode: Should not reach here (asserts), TOP mode exits early in handleICFGCycle - * - WIDEN_ONLY: Returns false (skip narrowing) - * - WIDEN_NARROW: Returns true (apply narrowing) - */ bool shouldApplyNarrowing(const FunObjVar* fun); // there data should be shared with subclasses diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index d62e11e0c..42988c699 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -835,11 +835,13 @@ void AbstractInterpretation::extCallPass(const CallICFGNode *callNode) callSiteStack.pop_back(); } +/// Check if a function is recursive (part of a call graph SCC) bool AbstractInterpretation::isRecursiveFun(const FunObjVar* fun) { return recursiveFuns.find(fun) != recursiveFuns.end(); } +/// Check if a call node calls a recursive function bool AbstractInterpretation::isRecursiveCall(const CallICFGNode *callNode) { const FunObjVar *callfun = callNode->getCalledFunction(); @@ -849,6 +851,7 @@ bool AbstractInterpretation::isRecursiveCall(const CallICFGNode *callNode) return isRecursiveFun(callfun); } +/// Handle recursive call in TOP mode: set all stores and return value to TOP void AbstractInterpretation::recursiveCallPass(const CallICFGNode *callNode) { AbstractState& as = getAbsStateFromTrace(callNode); @@ -868,6 +871,7 @@ void AbstractInterpretation::recursiveCallPass(const CallICFGNode *callNode) abstractTrace[retNode] = as; } +/// Check if a call is a recursive callsite (within same SCC, not entry call from outside) bool AbstractInterpretation::isRecursiveCallSite(const CallICFGNode* callNode, const FunObjVar* callee) { @@ -875,6 +879,7 @@ bool AbstractInterpretation::isRecursiveCallSite(const CallICFGNode* callNode, nonRecursiveCallSites.end(); } +/// Get callee function: directly for direct calls, via pointer analysis for indirect calls const FunObjVar* AbstractInterpretation::getCallee(const CallICFGNode* callNode) { // Direct call: get callee directly from call node @@ -904,6 +909,7 @@ const FunObjVar* AbstractInterpretation::getCallee(const CallICFGNode* callNode) return SVFUtil::dyn_cast(func_var); } +/// Skip recursive callsites (within SCC); entry calls from outside SCC are not skipped bool AbstractInterpretation::skipRecursiveCall(const CallICFGNode* callNode) { const FunObjVar* callee = getCallee(callNode); @@ -921,15 +927,15 @@ bool AbstractInterpretation::skipRecursiveCall(const CallICFGNode* callNode) return isRecursiveCallSite(callNode, callee); } +/// Check if narrowing should be applied: always for regular loops, mode-dependent for recursion bool AbstractInterpretation::shouldApplyNarrowing(const FunObjVar* fun) { // Non-recursive functions (regular loops): always apply narrowing if (!isRecursiveFun(fun)) return true; - // Recursive functions: depends on mode - // Note: TOP mode handles recursive cycles separately in handleICFGCycle, - // so this should not be reached for recursive functions in TOP mode. + // Recursive functions: WIDEN_NARROW applies narrowing, WIDEN_ONLY does not + // TOP mode exits early in handleICFGCycle, so should not reach here switch (Options::HandleRecur()) { case TOP: @@ -1191,6 +1197,7 @@ void AbstractInterpretation::handleSVFStatement(const SVFStmt *stmt) !getAbsStateFromTrace(stmt->getICFGNode())[IRGraph::NullPtr].isAddr()); } +/// Set all store values in a recursive function to TOP (used in TOP mode) void AbstractInterpretation::setRecursiveCallStoresToTop(const CallICFGNode *callNode) { AbstractState& as = getAbsStateFromTrace(callNode); From bf793f885fce4f23618cffb991ceb83f58d930f9 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Tue, 3 Feb 2026 15:40:31 +1100 Subject: [PATCH 14/32] rename handleICFGCycle --- svf/include/AE/Svfexe/AbstractInterpretation.h | 2 +- svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/svf/include/AE/Svfexe/AbstractInterpretation.h b/svf/include/AE/Svfexe/AbstractInterpretation.h index a7c9bf075..783489543 100644 --- a/svf/include/AE/Svfexe/AbstractInterpretation.h +++ b/svf/include/AE/Svfexe/AbstractInterpretation.h @@ -218,7 +218,7 @@ class AbstractInterpretation * * @param cycle WTOCycle which has weak topo order of basic blocks and nested cycles */ - virtual void handleICFGCycle(const ICFGCycleWTO* cycle); + virtual void HandleLoopOrRecursion(const ICFGCycleWTO* cycle); /** * Handle a function using worklist algorithm diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index 42988c699..208b4af7e 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -767,7 +767,7 @@ void AbstractInterpretation::handleFunction(const ICFGNode* funEntry) if (cycleHeadToCycle.find(node) != cycleHeadToCycle.end()) { const ICFGCycleWTO* cycle = cycleHeadToCycle[node]; - handleICFGCycle(cycle); + HandleLoopOrRecursion(cycle); // Push nodes outside the cycle to the worklist std::vector cycleNextNodes = getNextNodesOfCycle(cycle); @@ -922,7 +922,7 @@ bool AbstractInterpretation::skipRecursiveCall(const CallICFGNode* callNode) // For recursive functions, skip only recursive callsites (within same SCC). // Entry calls (from outside SCC) are not skipped - they are inlined so that - // handleICFGCycle() can analyze the function body. + // HandleLoopOrRecursion() can analyze the function body. // This applies uniformly to all modes (TOP/WIDEN_ONLY/WIDEN_NARROW). return isRecursiveCallSite(callNode, callee); } @@ -935,7 +935,7 @@ bool AbstractInterpretation::shouldApplyNarrowing(const FunObjVar* fun) return true; // Recursive functions: WIDEN_NARROW applies narrowing, WIDEN_ONLY does not - // TOP mode exits early in handleICFGCycle, so should not reach here + // TOP mode exits early in HandleLoopOrRecursion, so should not reach here switch (Options::HandleRecur()) { case TOP: @@ -1055,7 +1055,7 @@ void AbstractInterpretation::indirectCallFunPass(const CallICFGNode *callNode) /// Example: /// int factorial(int n) { return n <= 1 ? 1 : n * factorial(n-1); } /// factorial(5) -> returns [10000, 10000] (precise after narrowing) -void AbstractInterpretation::handleICFGCycle(const ICFGCycleWTO* cycle) +void AbstractInterpretation::HandleLoopOrRecursion(const ICFGCycleWTO* cycle) { const ICFGNode* cycle_head = cycle->head()->getICFGNode(); @@ -1130,7 +1130,7 @@ void AbstractInterpretation::handleICFGCycle(const ICFGCycleWTO* cycle) else if (const ICFGCycleWTO* subCycle = SVFUtil::dyn_cast(comp)) { // Handle nested cycle recursively - handleICFGCycle(subCycle); + HandleLoopOrRecursion(subCycle); } } } From 09e86d58f1353bbca7a9c3d39ce31b3307be17fb Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Tue, 3 Feb 2026 15:43:50 +1100 Subject: [PATCH 15/32] Continue to reduce the Options() handleRecur. (vibe-kanban 5f1e98b0) I think you could continue to narrow down the usage of OPtions::HandleREcur(). I mean maybe you could put the option check inside the function. For example, "// Check if this recursive call should be skipped if (shouldSkipRecursiveCall(callNode, funObjVar)) { // In TOP mode, set return value and stores to TOP // In WIDEN\_ONLY/WIDEN\_NARROW, just skip (WTO handles it) if (Options::HandleRecur() == TOP) handleSkippedRecursiveCall(callNode); return; } " maybe you should move the if check inside the function. You could try this way to reduce the Options::handleRecur as low as possible. --- .../AE/Svfexe/AbstractInterpretation.h | 5 +- svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 58 ++----------------- 2 files changed, 7 insertions(+), 56 deletions(-) diff --git a/svf/include/AE/Svfexe/AbstractInterpretation.h b/svf/include/AE/Svfexe/AbstractInterpretation.h index 783489543..988dd7c05 100644 --- a/svf/include/AE/Svfexe/AbstractInterpretation.h +++ b/svf/include/AE/Svfexe/AbstractInterpretation.h @@ -348,10 +348,7 @@ class AbstractInterpretation virtual bool isRecursiveCall(const CallICFGNode* callNode); virtual void recursiveCallPass(const CallICFGNode *callNode); virtual bool isRecursiveCallSite(const CallICFGNode* callNode, const FunObjVar *); - virtual bool isDirectCall(const CallICFGNode* callNode); - virtual void directCallFunPass(const CallICFGNode* callNode); - virtual bool isIndirectCall(const CallICFGNode* callNode); - virtual void indirectCallFunPass(const CallICFGNode* callNode); + virtual void callFunPass(const CallICFGNode* callNode); bool skipRecursiveCall(const CallICFGNode* callNode); const FunObjVar* getCallee(const CallICFGNode* callNode); diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index 208b4af7e..5353f16cc 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -804,16 +804,11 @@ void AbstractInterpretation::handleCallSite(const ICFGNode* node) { extCallPass(callNode); } - else if (isDirectCall(callNode)) - { - directCallFunPass(callNode); - } - else if (isIndirectCall(callNode)) + else { - indirectCallFunPass(callNode); + // Handle both direct and indirect calls uniformly + callFunPass(callNode); } - else - assert(false && "implement this part"); } else assert (false && "it is not call node"); @@ -950,51 +945,13 @@ bool AbstractInterpretation::shouldApplyNarrowing(const FunObjVar* fun) return false; } } - - -bool AbstractInterpretation::isDirectCall(const CallICFGNode *callNode) -{ - const FunObjVar *callfun =callNode->getCalledFunction(); - if (!callfun) - return false; - else - return !callfun->isDeclaration(); -} -void AbstractInterpretation::directCallFunPass(const CallICFGNode *callNode) +/// Handle direct or indirect call: get callee, process function body, set return state +void AbstractInterpretation::callFunPass(const CallICFGNode *callNode) { AbstractState& as = getAbsStateFromTrace(callNode); - abstractTrace[callNode] = as; - // Skip recursive call if applicable (returns true if skipped) - if (skipRecursiveCall(callNode)) - return; - - const FunObjVar *calleeFun = callNode->getCalledFunction(); - callSiteStack.push_back(callNode); - - // Use worklist-based function handling instead of recursive WTO component handling - const ICFGNode* calleeEntry = icfg->getFunEntryICFGNode(calleeFun); - handleFunction(calleeEntry); - - callSiteStack.pop_back(); - // handle Ret node - const RetICFGNode *retNode = callNode->getRetICFGNode(); - // resume ES to callnode - abstractTrace[retNode] = abstractTrace[callNode]; -} - -bool AbstractInterpretation::isIndirectCall(const CallICFGNode *callNode) -{ - const auto callsiteMaps = svfir->getIndirectCallsites(); - return callsiteMaps.find(callNode) != callsiteMaps.end(); -} - -void AbstractInterpretation::indirectCallFunPass(const CallICFGNode *callNode) -{ - AbstractState& as = getAbsStateFromTrace(callNode); - - // Skip recursive call if applicable (returns true if skipped) + // Skip recursive callsites (within SCC); entry calls are not skipped if (skipRecursiveCall(callNode)) return; @@ -1003,14 +960,11 @@ void AbstractInterpretation::indirectCallFunPass(const CallICFGNode *callNode) return; callSiteStack.push_back(callNode); - abstractTrace[callNode] = as; - // Use worklist-based function handling instead of recursive WTO component handling const ICFGNode* calleeEntry = icfg->getFunEntryICFGNode(callee); handleFunction(calleeEntry); callSiteStack.pop_back(); - // handle Ret node const RetICFGNode* retNode = callNode->getRetICFGNode(); abstractTrace[retNode] = abstractTrace[callNode]; } From 76c2374a2eb6967dae09fa4d7640bde7451de624 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Wed, 4 Feb 2026 12:03:53 +1100 Subject: [PATCH 16/32] rename two functions in AbstractInterpretation (vibe-kanban 313b27a9) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 你方便把 setRecursiveCallStoresToTop改名setTopToObjInRecursion 然后把callFunPass 改名HandleFunCall吗? 改名就行 --- svf/include/AE/Svfexe/AbstractInterpretation.h | 4 ++-- svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/svf/include/AE/Svfexe/AbstractInterpretation.h b/svf/include/AE/Svfexe/AbstractInterpretation.h index 988dd7c05..55c12b116 100644 --- a/svf/include/AE/Svfexe/AbstractInterpretation.h +++ b/svf/include/AE/Svfexe/AbstractInterpretation.h @@ -266,7 +266,7 @@ class AbstractInterpretation */ virtual void handleSVFStatement(const SVFStmt* stmt); - virtual void setRecursiveCallStoresToTop(const CallICFGNode* callnode); + virtual void setTopToObjInRecursion(const CallICFGNode* callnode); /** @@ -348,7 +348,7 @@ class AbstractInterpretation virtual bool isRecursiveCall(const CallICFGNode* callNode); virtual void recursiveCallPass(const CallICFGNode *callNode); virtual bool isRecursiveCallSite(const CallICFGNode* callNode, const FunObjVar *); - virtual void callFunPass(const CallICFGNode* callNode); + virtual void HandleFunCall(const CallICFGNode* callNode); bool skipRecursiveCall(const CallICFGNode* callNode); const FunObjVar* getCallee(const CallICFGNode* callNode); diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index 5353f16cc..2fa53183c 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -807,7 +807,7 @@ void AbstractInterpretation::handleCallSite(const ICFGNode* node) else { // Handle both direct and indirect calls uniformly - callFunPass(callNode); + HandleFunCall(callNode); } } else @@ -850,7 +850,7 @@ bool AbstractInterpretation::isRecursiveCall(const CallICFGNode *callNode) void AbstractInterpretation::recursiveCallPass(const CallICFGNode *callNode) { AbstractState& as = getAbsStateFromTrace(callNode); - setRecursiveCallStoresToTop(callNode); + setTopToObjInRecursion(callNode); const RetICFGNode *retNode = callNode->getRetICFGNode(); if (retNode->getSVFStmts().size() > 0) { @@ -946,7 +946,7 @@ bool AbstractInterpretation::shouldApplyNarrowing(const FunObjVar* fun) } } /// Handle direct or indirect call: get callee, process function body, set return state -void AbstractInterpretation::callFunPass(const CallICFGNode *callNode) +void AbstractInterpretation::HandleFunCall(const CallICFGNode *callNode) { AbstractState& as = getAbsStateFromTrace(callNode); abstractTrace[callNode] = as; @@ -1152,7 +1152,7 @@ void AbstractInterpretation::handleSVFStatement(const SVFStmt *stmt) } /// Set all store values in a recursive function to TOP (used in TOP mode) -void AbstractInterpretation::setRecursiveCallStoresToTop(const CallICFGNode *callNode) +void AbstractInterpretation::setTopToObjInRecursion(const CallICFGNode *callNode) { AbstractState& as = getAbsStateFromTrace(callNode); const RetICFGNode *retNode = callNode->getRetICFGNode(); From 7fce76870f6459b92ef045df509878095eb26311 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Wed, 4 Feb 2026 14:52:09 +1100 Subject: [PATCH 17/32] rename two functions in AbstractInterpretation (vibe-kanban 313b27a9) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 你方便把 setRecursiveCallStoresToTop改名setTopToObjInRecursion 然后把callFunPass 改名HandleFunCall吗? 改名就行 --- svf/include/AE/Svfexe/AbstractInterpretation.h | 4 ++-- svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/svf/include/AE/Svfexe/AbstractInterpretation.h b/svf/include/AE/Svfexe/AbstractInterpretation.h index 55c12b116..f17b09130 100644 --- a/svf/include/AE/Svfexe/AbstractInterpretation.h +++ b/svf/include/AE/Svfexe/AbstractInterpretation.h @@ -343,12 +343,12 @@ class AbstractInterpretation // helper functions in handleCallSite virtual bool isExtCall(const CallICFGNode* callNode); - virtual void extCallPass(const CallICFGNode* callNode); + virtual void handleExtCall(const CallICFGNode* callNode); virtual bool isRecursiveFun(const FunObjVar* fun); virtual bool isRecursiveCall(const CallICFGNode* callNode); virtual void recursiveCallPass(const CallICFGNode *callNode); virtual bool isRecursiveCallSite(const CallICFGNode* callNode, const FunObjVar *); - virtual void HandleFunCall(const CallICFGNode* callNode); + virtual void handleFunCall(const CallICFGNode* callNode); bool skipRecursiveCall(const CallICFGNode* callNode); const FunObjVar* getCallee(const CallICFGNode* callNode); diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index 2fa53183c..3a4ee86bd 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -802,12 +802,12 @@ void AbstractInterpretation::handleCallSite(const ICFGNode* node) { if (isExtCall(callNode)) { - extCallPass(callNode); + handleExtCall(callNode); } else { // Handle both direct and indirect calls uniformly - HandleFunCall(callNode); + handleFunCall(callNode); } } else @@ -819,7 +819,7 @@ bool AbstractInterpretation::isExtCall(const CallICFGNode *callNode) return SVFUtil::isExtCall(callNode->getCalledFunction()); } -void AbstractInterpretation::extCallPass(const CallICFGNode *callNode) +void AbstractInterpretation::handleExtCall(const CallICFGNode *callNode) { callSiteStack.push_back(callNode); utils->handleExtAPI(callNode); @@ -946,7 +946,7 @@ bool AbstractInterpretation::shouldApplyNarrowing(const FunObjVar* fun) } } /// Handle direct or indirect call: get callee, process function body, set return state -void AbstractInterpretation::HandleFunCall(const CallICFGNode *callNode) +void AbstractInterpretation::handleFunCall(const CallICFGNode *callNode) { AbstractState& as = getAbsStateFromTrace(callNode); abstractTrace[callNode] = as; From d3b4faee463f2f67462611b167fe46dcb160b917 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Wed, 4 Feb 2026 14:53:25 +1100 Subject: [PATCH 18/32] rename two functions in AbstractInterpretation (vibe-kanban 313b27a9) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 你方便把 setRecursiveCallStoresToTop改名setTopToObjInRecursion 然后把callFunPass 改名HandleFunCall吗? 改名就行 --- svf/include/AE/Svfexe/AbstractInterpretation.h | 2 +- svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/svf/include/AE/Svfexe/AbstractInterpretation.h b/svf/include/AE/Svfexe/AbstractInterpretation.h index f17b09130..aea2e9d90 100644 --- a/svf/include/AE/Svfexe/AbstractInterpretation.h +++ b/svf/include/AE/Svfexe/AbstractInterpretation.h @@ -218,7 +218,7 @@ class AbstractInterpretation * * @param cycle WTOCycle which has weak topo order of basic blocks and nested cycles */ - virtual void HandleLoopOrRecursion(const ICFGCycleWTO* cycle); + virtual void handleLoopOrRecursion(const ICFGCycleWTO* cycle); /** * Handle a function using worklist algorithm diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index 3a4ee86bd..5e413c9f2 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -767,7 +767,7 @@ void AbstractInterpretation::handleFunction(const ICFGNode* funEntry) if (cycleHeadToCycle.find(node) != cycleHeadToCycle.end()) { const ICFGCycleWTO* cycle = cycleHeadToCycle[node]; - HandleLoopOrRecursion(cycle); + handleLoopOrRecursion(cycle); // Push nodes outside the cycle to the worklist std::vector cycleNextNodes = getNextNodesOfCycle(cycle); @@ -917,7 +917,7 @@ bool AbstractInterpretation::skipRecursiveCall(const CallICFGNode* callNode) // For recursive functions, skip only recursive callsites (within same SCC). // Entry calls (from outside SCC) are not skipped - they are inlined so that - // HandleLoopOrRecursion() can analyze the function body. + // handleLoopOrRecursion() can analyze the function body. // This applies uniformly to all modes (TOP/WIDEN_ONLY/WIDEN_NARROW). return isRecursiveCallSite(callNode, callee); } @@ -930,7 +930,7 @@ bool AbstractInterpretation::shouldApplyNarrowing(const FunObjVar* fun) return true; // Recursive functions: WIDEN_NARROW applies narrowing, WIDEN_ONLY does not - // TOP mode exits early in HandleLoopOrRecursion, so should not reach here + // TOP mode exits early in handleLoopOrRecursion, so should not reach here switch (Options::HandleRecur()) { case TOP: @@ -1009,7 +1009,7 @@ void AbstractInterpretation::handleFunCall(const CallICFGNode *callNode) /// Example: /// int factorial(int n) { return n <= 1 ? 1 : n * factorial(n-1); } /// factorial(5) -> returns [10000, 10000] (precise after narrowing) -void AbstractInterpretation::HandleLoopOrRecursion(const ICFGCycleWTO* cycle) +void AbstractInterpretation::handleLoopOrRecursion(const ICFGCycleWTO* cycle) { const ICFGNode* cycle_head = cycle->head()->getICFGNode(); @@ -1084,7 +1084,7 @@ void AbstractInterpretation::HandleLoopOrRecursion(const ICFGCycleWTO* cycle) else if (const ICFGCycleWTO* subCycle = SVFUtil::dyn_cast(comp)) { // Handle nested cycle recursively - HandleLoopOrRecursion(subCycle); + handleLoopOrRecursion(subCycle); } } } From 4c87a382273d7d1bcf92c4700167b79f32378b75 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Fri, 6 Feb 2026 21:51:10 +1100 Subject: [PATCH 19/32] Add multi-entry whole-program analysis for library code - Add collectEntryFunctions() to find functions without callers - Add analyseFromAllEntries() for analyzing from all entry points - Implement flow-sensitive join for same function called multiple times - Add ICFG and function coverage statistics in AEStat - Add allAnalyzedNodes set to track analyzed nodes across entry points - Fix bottom interval assertion errors in AbsExtAPI (handleMemcpy/handleMemset) When no main function exists, the analysis automatically starts from all entry points (functions with no callers). Each entry point is analyzed independently with fresh state. Coverage statistics now correctly track all analyzed nodes across multiple entry points. Co-Authored-By: Claude --- .../AE/Svfexe/AbstractInterpretation.h | 10 + svf/lib/AE/Svfexe/AbsExtAPI.cpp | 19 ++ svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 201 +++++++++++++++--- 3 files changed, 202 insertions(+), 28 deletions(-) diff --git a/svf/include/AE/Svfexe/AbstractInterpretation.h b/svf/include/AE/Svfexe/AbstractInterpretation.h index aea2e9d90..6c3e4e974 100644 --- a/svf/include/AE/Svfexe/AbstractInterpretation.h +++ b/svf/include/AE/Svfexe/AbstractInterpretation.h @@ -144,6 +144,15 @@ class AbstractInterpretation /// Program entry void analyse(); + /// Analyze all entry points (functions without callers) + void analyseFromAllEntries(); + + /// Get all entry point functions (functions without callers) + std::vector collectEntryFunctions(); + + /// Clear abstract trace for fresh analysis from new entry + void clearAbstractTrace(); + static AbstractInterpretation& getAEInstance() { static AbstractInterpretation instance; @@ -358,6 +367,7 @@ class AbstractInterpretation Map> func_map; Map abstractTrace; // abstract states immediately after nodes + Set allAnalyzedNodes; // All nodes ever analyzed (across all entry points) std::string moduleName; std::vector> detectors; diff --git a/svf/lib/AE/Svfexe/AbsExtAPI.cpp b/svf/lib/AE/Svfexe/AbsExtAPI.cpp index 1d90ad602..6e6d3f4bc 100644 --- a/svf/lib/AE/Svfexe/AbsExtAPI.cpp +++ b/svf/lib/AE/Svfexe/AbsExtAPI.cpp @@ -498,6 +498,9 @@ void AbsExtAPI::handleStrcpy(const CallICFGNode *call) const SVFVar* arg1Val = call->getArgument(1); IntervalValue strLen = getStrlen(as, arg1Val); // no need to -1, since it has \0 as the last byte + // Skip if strLen is bottom or unbounded + if (strLen.isBottom() || strLen.lb().is_minus_infinity()) + return; handleMemcpy(as, arg0Val, arg1Val, strLen, strLen.lb().getIntNumeral()); } @@ -592,6 +595,9 @@ void AbsExtAPI::handleStrcat(const SVF::CallICFGNode *call) IntervalValue strLen0 = getStrlen(as, arg0Val); IntervalValue strLen1 = getStrlen(as, arg1Val); IntervalValue totalLen = strLen0 + strLen1; + // Skip if strLen0 is bottom or unbounded + if (strLen0.isBottom() || strLen0.lb().is_minus_infinity()) + return; handleMemcpy(as, arg0Val, arg1Val, strLen1, strLen0.lb().getIntNumeral()); // do memcpy } @@ -603,6 +609,9 @@ void AbsExtAPI::handleStrcat(const SVF::CallICFGNode *call) IntervalValue arg2Num = as[arg2Val->getId()].getInterval(); IntervalValue strLen0 = getStrlen(as, arg0Val); IntervalValue totalLen = strLen0 + arg2Num; + // Skip if strLen0 is bottom or unbounded + if (strLen0.isBottom() || strLen0.lb().is_minus_infinity()) + return; handleMemcpy(as, arg0Val, arg1Val, arg2Num, strLen0.lb().getIntNumeral()); // do memcpy } @@ -640,6 +649,11 @@ void AbsExtAPI::handleMemcpy(AbstractState& as, const SVF::SVFVar *dst, const SV { assert(false && "we cannot support this type"); } + // Handle bottom or unbounded interval - skip memcpy in these cases + if (len.isBottom() || len.lb().is_minus_infinity()) + { + return; + } u32_t size = std::min((u32_t)Options::MaxFieldLimit(), (u32_t) len.lb().getIntNumeral()); u32_t range_val = size / elemSize; if (as.inVarToAddrsTable(srcId) && as.inVarToAddrsTable(dstId)) @@ -672,6 +686,11 @@ void AbsExtAPI::handleMemcpy(AbstractState& as, const SVF::SVFVar *dst, const SV void AbsExtAPI::handleMemset(AbstractState& as, const SVF::SVFVar *dst, IntervalValue elem, IntervalValue len) { + // Handle bottom or unbounded interval - skip memset in these cases + if (len.isBottom() || len.lb().is_minus_infinity()) + { + return; + } u32_t dstId = dst->getId(); u32_t size = std::min((u32_t)Options::MaxFieldLimit(), (u32_t) len.lb().getIntNumeral()); u32_t elemSize = 1; diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index 5e413c9f2..224de0ade 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -162,7 +162,46 @@ void AbstractInterpretation::initWTO() } } -/// Program entry +/// Collect all entry point functions (functions without callers) +std::vector AbstractInterpretation::collectEntryFunctions() +{ + std::vector entryFunctions; + const CallGraph* callGraph = svfir->getCallGraph(); + + for (auto it = callGraph->begin(); it != callGraph->end(); ++it) + { + const CallGraphNode* cgNode = it->second; + const FunObjVar* fun = cgNode->getFunction(); + + // Skip declarations + if (fun->isDeclaration()) + continue; + + // Check if function has no callers (entry point) + if (cgNode->getInEdges().empty()) + { + entryFunctions.push_back(fun); + } + } + + // If main exists, put it first for priority + auto mainIt = std::find_if(entryFunctions.begin(), entryFunctions.end(), + [](const FunObjVar* f) { return f->getName() == "main"; }); + if (mainIt != entryFunctions.end() && mainIt != entryFunctions.begin()) + { + std::iter_swap(entryFunctions.begin(), mainIt); + } + + return entryFunctions; +} + +/// Clear abstract trace for fresh analysis from new entry +void AbstractInterpretation::clearAbstractTrace() +{ + abstractTrace.clear(); +} + +/// Program entry - analyze from main if exists, otherwise analyze from all entry points void AbstractInterpretation::analyse() { initWTO(); @@ -176,6 +215,43 @@ void AbstractInterpretation::analyse() const ICFGNode* mainEntry = icfg->getFunEntryICFGNode(cgn->getFunction()); handleFunction(mainEntry); } + else + { + // No main function found, analyze from all entry points (library code) + SVFUtil::outs() << "No main function found, analyzing from all entry points...\n"; + analyseFromAllEntries(); + } +} + +/// Analyze all entry points (functions without callers) - for whole-program analysis without main +void AbstractInterpretation::analyseFromAllEntries() +{ + initWTO(); + + // Collect all entry point functions + std::vector entryFunctions = collectEntryFunctions(); + + if (entryFunctions.empty()) + { + SVFUtil::errs() << "Warning: No entry functions found for analysis\n"; + return; + } + + // Analyze from each entry point independently (Scenario 2: different entries -> fresh start) + for (const FunObjVar* entryFun : entryFunctions) + { + // Clear abstract trace for fresh analysis from this entry + clearAbstractTrace(); + + // Handle global node for each entry (global state is shared across entries) + handleGlobalNode(); + getAbsStateFromTrace( + icfg->getGlobalICFGNode())[PAG::getPAG()->getBlkPtr()] = IntervalValue::top(); + + // Analyze from this entry function + const ICFGNode* funEntry = icfg->getFunEntryICFGNode(entryFun); + handleFunction(funEntry); + } } /// handle global node @@ -602,37 +678,58 @@ bool AbstractInterpretation::handleICFGNode(const ICFGNode* node) bool isFunEntry = SVFUtil::isa(node); if (isFunEntry) { - // Try to merge from predecessors first (handles call edges) - if (!mergeStatesFromPredecessors(node)) + // For function entries, we need flow-sensitive join: + // When the same function is called multiple times from the same entry, + // we join the input states (Scenario 1: x=1 -> foo(), x=2 -> foo() => foo sees [1,2]) + + AbstractState inputState; + bool hasInput = false; + + // Try to get state from ICFG predecessors (call edges) + // mergeStatesFromPredecessors handles CallCFGEdge + if (mergeStatesFromPredecessors(node)) { - // No predecessors with state - initialize from caller or global - if (!callSiteStack.empty()) + inputState = abstractTrace[node]; + hasInput = true; + } + else if (!callSiteStack.empty()) + { + // No ICFG predecessors with state - get from current call site stack + const CallICFGNode* caller = callSiteStack.back(); + if (hasAbsStateFromTrace(caller)) { - // Get state from the most recent call site - const CallICFGNode* caller = callSiteStack.back(); - if (hasAbsStateFromTrace(caller)) - { - abstractTrace[node] = abstractTrace[caller]; - } - else - { - abstractTrace[node] = AbstractState(); - } + inputState = abstractTrace[caller]; + hasInput = true; + } + } + else + { + // Entry function (like main) - inherit from global node + const ICFGNode* globalNode = icfg->getGlobalICFGNode(); + if (hasAbsStateFromTrace(globalNode)) + { + inputState = abstractTrace[globalNode]; + hasInput = true; + } + } + + if (hasInput) + { + // Flow-sensitive join: if function entry already has state, join with new input + if (hadPrevState) + { + prevState.joinWith(inputState); + abstractTrace[node] = prevState; } else { - // This is the main function entry, inherit from global node - const ICFGNode* globalNode = icfg->getGlobalICFGNode(); - if (hasAbsStateFromTrace(globalNode)) - { - abstractTrace[node] = abstractTrace[globalNode]; - } - else - { - abstractTrace[node] = AbstractState(); - } + abstractTrace[node] = inputState; } } + else + { + abstractTrace[node] = AbstractState(); + } } else { @@ -661,6 +758,9 @@ bool AbstractInterpretation::handleICFGNode(const ICFGNode* node) detector->detect(getAbsStateFromTrace(node), node); stat->countStateSize(); + // Track this node as analyzed (for coverage statistics across all entry points) + allAnalyzedNodes.insert(node); + // Check if state changed (for fixpoint detection) // For entry nodes on first visit, always return true to process successors if (isFunEntry && !hadPrevState) @@ -1229,15 +1329,39 @@ void AEStat::finializeStat() generalNumMap["ES_Loc_Addr_AVG_Num"] /= count; } generalNumMap["SVF_STMT_NUM"] = count; - generalNumMap["ICFG_Node_Num"] = _ae->svfir->getICFG()->nodeNum; + + u32_t totalICFGNodes = _ae->svfir->getICFG()->nodeNum; + generalNumMap["ICFG_Node_Num"] = totalICFGNodes; + + // Calculate coverage: use allAnalyzedNodes which tracks all nodes across all entry points + u32_t analyzedNodes = _ae->allAnalyzedNodes.size(); + generalNumMap["Analyzed_ICFG_Node_Num"] = analyzedNodes; + + // Coverage percentage (stored as integer percentage * 100 for precision) + if (totalICFGNodes > 0) + { + double coveragePercent = (double)analyzedNodes / (double)totalICFGNodes * 100.0; + generalNumMap["ICFG_Coverage_Percent"] = (u32_t)(coveragePercent * 100); // Store as percentage * 100 + } + else + { + generalNumMap["ICFG_Coverage_Percent"] = 0; + } + u32_t callSiteNum = 0; u32_t extCallSiteNum = 0; Set funs; + Set analyzedFuns; for (const auto &it: *_ae->svfir->getICFG()) { if (it.second->getFun()) { funs.insert(it.second->getFun()); + // Check if this node was analyzed (across all entry points) + if (_ae->allAnalyzedNodes.find(it.second) != _ae->allAnalyzedNodes.end()) + { + analyzedFuns.insert(it.second->getFun()); + } } if (const CallICFGNode *callNode = dyn_cast(it.second)) { @@ -1252,6 +1376,19 @@ void AEStat::finializeStat() } } generalNumMap["Func_Num"] = funs.size(); + generalNumMap["Analyzed_Func_Num"] = analyzedFuns.size(); + + // Function coverage percentage + if (funs.size() > 0) + { + double funcCoveragePercent = (double)analyzedFuns.size() / (double)funs.size() * 100.0; + generalNumMap["Func_Coverage_Percent"] = (u32_t)(funcCoveragePercent * 100); // Store as percentage * 100 + } + else + { + generalNumMap["Func_Coverage_Percent"] = 0; + } + generalNumMap["EXT_CallSite_Num"] = extCallSiteNum; generalNumMap["NonEXT_CallSite_Num"] = callSiteNum; timeStatMap["Total_Time(sec)"] = (double)(endTime - startTime) / TIMEINTERVAL; @@ -1280,8 +1417,16 @@ void AEStat::performStat() unsigned field_width = 30; for (NUMStatMap::iterator it = generalNumMap.begin(), eit = generalNumMap.end(); it != eit; ++it) { - // format out put with width 20 space - std::cout << std::setw(field_width) << it->first << it->second << "\n"; + // Special handling for percentage fields (stored as percentage * 100) + if (it->first == "ICFG_Coverage_Percent" || it->first == "Func_Coverage_Percent") + { + double percent = (double)it->second / 100.0; + std::cout << std::setw(field_width) << it->first << std::fixed << std::setprecision(2) << percent << "%\n"; + } + else + { + std::cout << std::setw(field_width) << it->first << it->second << "\n"; + } } SVFUtil::outs() << "-------------------------------------------------------\n"; for (TIMEStatMap::iterator it = timeStatMap.begin(), eit = timeStatMap.end(); it != eit; ++it) From 7444f967813bc1a042098c24ab53615ce250f8c3 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Fri, 6 Feb 2026 22:53:09 +1100 Subject: [PATCH 20/32] Add -ae-multientry option for multi-entry analysis - Add new command-line option -ae-multientry (default: false) - When false (default): analyze from main() only, preserving original behavior for Test-Suite test cases - When true: analyze from all entry points (functions without callers), useful for library code without main function - If no main function exists and -ae-multientry is not set, automatically falls back to multi-entry analysis Co-Authored-By: Claude Opus 4.5 --- svf/include/Util/Options.h | 4 ++++ svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 10 ++++++++++ svf/lib/Util/Options.cpp | 6 ++++++ 3 files changed, 20 insertions(+) diff --git a/svf/include/Util/Options.h b/svf/include/Util/Options.h index 0fa17910d..ca992a0a1 100644 --- a/svf/include/Util/Options.h +++ b/svf/include/Util/Options.h @@ -267,6 +267,10 @@ class Options // float precision for symbolic abstraction static const Option AEPrecision; + + /// If true, analyze from all entry points (functions without callers) + /// instead of only from main. Useful for library code without main function. + static const Option AEMultiEntry; }; } // namespace SVF diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index 224de0ade..7c9236172 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -209,6 +209,16 @@ void AbstractInterpretation::analyse() handleGlobalNode(); getAbsStateFromTrace( icfg->getGlobalICFGNode())[PAG::getPAG()->getBlkPtr()] = IntervalValue::top(); + + // If -ae-multientry is set, always use multi-entry analysis + if (Options::AEMultiEntry()) + { + SVFUtil::outs() << "Multi-entry analysis enabled, analyzing from all entry points...\n"; + analyseFromAllEntries(); + return; + } + + // Default behavior: start from main if exists if (const CallGraphNode* cgn = svfir->getCallGraph()->getCallGraphNode("main")) { // Use worklist-based function handling instead of recursive WTO component handling diff --git a/svf/lib/Util/Options.cpp b/svf/lib/Util/Options.cpp index a783ce4e9..7c4f01cb7 100644 --- a/svf/lib/Util/Options.cpp +++ b/svf/lib/Util/Options.cpp @@ -832,4 +832,10 @@ const Option Options::AEPrecision( 0 ); +const Option Options::AEMultiEntry( + "ae-multientry", + "Analyze from all entry points (functions without callers) instead of only main. Useful for library code.", + false +); + } // namespace SVF. From 2aa167cbcfb2e999653c656f1c91457991a4bbbe Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Fri, 6 Feb 2026 23:33:59 +1100 Subject: [PATCH 21/32] Fix handleICFGNode regression in function entry state handling Revert the flow-sensitive join logic in handleICFGNode that was incorrectly breaking the original function entry state initialization. The previous change introduced unnecessary complexity that caused incorrect state propagation when functions are called. The multi-entry analysis feature still works correctly because each entry point is analyzed independently with clearAbstractTrace() called before each entry, so flow-sensitive join at function entries is not needed. This fixes 7 out of 8 failing test cases: - BASIC_ptr_call2-0.c.bc - LOOP_for_call-0.c.bc - CWE121_Stack_Based_Buffer_Overflow__CWE129_fgets_01.c.bc - CWE121_Stack_Based_Buffer_Overflow__CWE129_fgets_01.c.bc-widen-narrow - CWE126_Buffer_Overread__CWE129_fgets_01.c.bc - CWE126_Buffer_Overread__CWE129_fgets_01.c.bc-widen-narrow - demo.c.bc-widen-narrow The remaining failure (INTERVAL_test_10-0.c.bc) is a pre-existing bug unrelated to the multi-entry changes. Co-Authored-By: Claude Opus 4.5 --- svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 69 +++++++------------- 1 file changed, 24 insertions(+), 45 deletions(-) diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index 7c9236172..0a55189cd 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -688,58 +688,37 @@ bool AbstractInterpretation::handleICFGNode(const ICFGNode* node) bool isFunEntry = SVFUtil::isa(node); if (isFunEntry) { - // For function entries, we need flow-sensitive join: - // When the same function is called multiple times from the same entry, - // we join the input states (Scenario 1: x=1 -> foo(), x=2 -> foo() => foo sees [1,2]) - - AbstractState inputState; - bool hasInput = false; - - // Try to get state from ICFG predecessors (call edges) - // mergeStatesFromPredecessors handles CallCFGEdge - if (mergeStatesFromPredecessors(node)) - { - inputState = abstractTrace[node]; - hasInput = true; - } - else if (!callSiteStack.empty()) - { - // No ICFG predecessors with state - get from current call site stack - const CallICFGNode* caller = callSiteStack.back(); - if (hasAbsStateFromTrace(caller)) - { - inputState = abstractTrace[caller]; - hasInput = true; - } - } - else - { - // Entry function (like main) - inherit from global node - const ICFGNode* globalNode = icfg->getGlobalICFGNode(); - if (hasAbsStateFromTrace(globalNode)) - { - inputState = abstractTrace[globalNode]; - hasInput = true; - } - } - - if (hasInput) + // Try to merge from predecessors first (handles call edges) + if (!mergeStatesFromPredecessors(node)) { - // Flow-sensitive join: if function entry already has state, join with new input - if (hadPrevState) + // No predecessors with state - initialize from caller or global + if (!callSiteStack.empty()) { - prevState.joinWith(inputState); - abstractTrace[node] = prevState; + // Get state from the most recent call site + const CallICFGNode* caller = callSiteStack.back(); + if (hasAbsStateFromTrace(caller)) + { + abstractTrace[node] = abstractTrace[caller]; + } + else + { + abstractTrace[node] = AbstractState(); + } } else { - abstractTrace[node] = inputState; + // This is the main function entry, inherit from global node + const ICFGNode* globalNode = icfg->getGlobalICFGNode(); + if (hasAbsStateFromTrace(globalNode)) + { + abstractTrace[node] = abstractTrace[globalNode]; + } + else + { + abstractTrace[node] = AbstractState(); + } } } - else - { - abstractTrace[node] = AbstractState(); - } } else { From afb1b8f166c26e5dfa79c16b2eb07e3c6d0b1797 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Sat, 7 Feb 2026 09:51:48 +1100 Subject: [PATCH 22/32] Fix assertion errors in AE for multi-entry analysis 1. Fix BufOverflowDetector assertion (AEDetector.cpp:482) - When a variable is not an address type in multi-entry analysis, conservatively return true (assume safe) instead of asserting 2. Fix undefined compare predicate assertion (AbstractInterpretation.cpp) - Add support for FCMP_ORD and FCMP_UNO floating-point comparisons - These predicates check for NaN conditions, conservatively return [0,1] Co-Authored-By: Claude Opus 4.5 --- svf/lib/AE/Svfexe/AEDetector.cpp | 7 ++++++- svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/svf/lib/AE/Svfexe/AEDetector.cpp b/svf/lib/AE/Svfexe/AEDetector.cpp index ae9d0bb3c..1882b1665 100644 --- a/svf/lib/AE/Svfexe/AEDetector.cpp +++ b/svf/lib/AE/Svfexe/AEDetector.cpp @@ -479,7 +479,12 @@ bool BufOverflowDetector::canSafelyAccessMemory(AbstractState& as, const SVF::SV SVFIR* svfir = PAG::getPAG(); NodeID value_id = value->getId(); - assert(as[value_id].isAddr()); + // In multi-entry analysis, some variables may not be initialized as addresses + if (!as[value_id].isAddr()) + { + // Conservatively assume safe when we don't have address information + return true; + } for (const auto& addr : as[value_id].getAddrs()) { NodeID objId = as.getIDFromAddr(addr); diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index 0a55189cd..e97669285 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -1739,6 +1739,13 @@ void AbstractInterpretation::updateStateOnCmp(const CmpStmt *cmp) case CmpStmt::FCMP_TRUE: resVal = IntervalValue(1, 1); break; + case CmpStmt::FCMP_ORD: + case CmpStmt::FCMP_UNO: + // FCMP_ORD: true if both operands are not NaN + // FCMP_UNO: true if either operand is NaN + // Conservatively return [0, 1] since we don't track NaN + resVal = IntervalValue(0, 1); + break; default: assert(false && "undefined compare: "); } @@ -1853,6 +1860,13 @@ void AbstractInterpretation::updateStateOnCmp(const CmpStmt *cmp) case CmpStmt::FCMP_TRUE: resVal = IntervalValue(1, 1); break; + case CmpStmt::FCMP_ORD: + case CmpStmt::FCMP_UNO: + // FCMP_ORD: true if both operands are not NaN + // FCMP_UNO: true if either operand is NaN + // Conservatively return [0, 1] since we don't track NaN + resVal = IntervalValue(0, 1); + break; default: assert(false && "undefined compare: "); } From bbb2c39592e1840e5c36d4661a23425dcb4aa088 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Sun, 8 Feb 2026 20:11:00 +1100 Subject: [PATCH 23/32] Some Rename and Refactor --- .../AE/Svfexe/AbstractInterpretation.h | 5 ++- svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 45 +++++++++++-------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/svf/include/AE/Svfexe/AbstractInterpretation.h b/svf/include/AE/Svfexe/AbstractInterpretation.h index 6c3e4e974..dc85535b8 100644 --- a/svf/include/AE/Svfexe/AbstractInterpretation.h +++ b/svf/include/AE/Svfexe/AbstractInterpretation.h @@ -36,6 +36,7 @@ #include "Util/SVFBugReport.h" #include "Util/SVFStat.h" #include "Graphs/SCC.h" +#include namespace SVF { @@ -145,10 +146,10 @@ class AbstractInterpretation void analyse(); /// Analyze all entry points (functions without callers) - void analyseFromAllEntries(); + void analyzeFromAllProgEntries(); /// Get all entry point functions (functions without callers) - std::vector collectEntryFunctions(); + std::deque collectProgEntryFuns(); /// Clear abstract trace for fresh analysis from new entry void clearAbstractTrace(); diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index e97669285..2ade8e759 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -34,6 +34,7 @@ #include "Graphs/CallGraph.h" #include "WPA/Andersen.h" #include +#include using namespace SVF; using namespace SVFUtil; @@ -163,9 +164,10 @@ void AbstractInterpretation::initWTO() } /// Collect all entry point functions (functions without callers) -std::vector AbstractInterpretation::collectEntryFunctions() +/// Uses a deque to allow efficient insertion at front for prioritizing main() +std::deque AbstractInterpretation::collectProgEntryFuns() { - std::vector entryFunctions; + std::deque entryFunctions; const CallGraph* callGraph = svfir->getCallGraph(); for (auto it = callGraph->begin(); it != callGraph->end(); ++it) @@ -180,18 +182,18 @@ std::vector AbstractInterpretation::collectEntryFunctions() // Check if function has no callers (entry point) if (cgNode->getInEdges().empty()) { - entryFunctions.push_back(fun); + // If main exists, put it first for priority using deque's push_front + if (fun->getName() == "main") + { + entryFunctions.push_front(fun); + } + else + { + entryFunctions.push_back(fun); + } } } - // If main exists, put it first for priority - auto mainIt = std::find_if(entryFunctions.begin(), entryFunctions.end(), - [](const FunObjVar* f) { return f->getName() == "main"; }); - if (mainIt != entryFunctions.end() && mainIt != entryFunctions.begin()) - { - std::iter_swap(entryFunctions.begin(), mainIt); - } - return entryFunctions; } @@ -207,14 +209,12 @@ void AbstractInterpretation::analyse() initWTO(); // handle Global ICFGNode of SVFModule handleGlobalNode(); - getAbsStateFromTrace( - icfg->getGlobalICFGNode())[PAG::getPAG()->getBlkPtr()] = IntervalValue::top(); // If -ae-multientry is set, always use multi-entry analysis if (Options::AEMultiEntry()) { SVFUtil::outs() << "Multi-entry analysis enabled, analyzing from all entry points...\n"; - analyseFromAllEntries(); + analyzeFromAllProgEntries(); return; } @@ -229,17 +229,17 @@ void AbstractInterpretation::analyse() { // No main function found, analyze from all entry points (library code) SVFUtil::outs() << "No main function found, analyzing from all entry points...\n"; - analyseFromAllEntries(); + analyzeFromAllProgEntries(); } } /// Analyze all entry points (functions without callers) - for whole-program analysis without main -void AbstractInterpretation::analyseFromAllEntries() +void AbstractInterpretation::analyzeFromAllProgEntries() { initWTO(); // Collect all entry point functions - std::vector entryFunctions = collectEntryFunctions(); + std::deque entryFunctions = collectProgEntryFuns(); if (entryFunctions.empty()) { @@ -255,8 +255,6 @@ void AbstractInterpretation::analyseFromAllEntries() // Handle global node for each entry (global state is shared across entries) handleGlobalNode(); - getAbsStateFromTrace( - icfg->getGlobalICFGNode())[PAG::getPAG()->getBlkPtr()] = IntervalValue::top(); // Analyze from this entry function const ICFGNode* funEntry = icfg->getFunEntryICFGNode(entryFun); @@ -265,16 +263,25 @@ void AbstractInterpretation::analyseFromAllEntries() } /// handle global node +/// Initializes the abstract state for the global ICFG node and processes all global statements. +/// This includes setting up the null pointer and black hole pointer (blkPtr) to top value, +/// which represents unknown/uninitialized memory that can point to any location. void AbstractInterpretation::handleGlobalNode() { const ICFGNode* node = icfg->getGlobalICFGNode(); abstractTrace[node] = AbstractState(); abstractTrace[node][IRGraph::NullPtr] = AddressValue(); + // Global Node, we just need to handle addr, load, store, copy and gep for (const SVFStmt *stmt: node->getSVFStmts()) { handleSVFStatement(stmt); } + + // Set black hole pointer to top value - this represents unknown/uninitialized + // memory locations that may point anywhere. This is essential for soundness + // when analyzing code where pointers may not be fully initialized. + abstractTrace[node][PAG::getPAG()->getBlkPtr()] = IntervalValue::top(); } /// get execution state by merging states of predecessor blocks From d70d6faafb5efebeb86a93908ac253c646869a43 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Sun, 8 Feb 2026 20:38:39 +1100 Subject: [PATCH 24/32] Read the comments in PullRequest (vibe-kanban 78898480) https://github.com/SVF-tools/SVF/pull/1789 Could you read comments in this PR? --- svf/include/Util/Options.h | 7 +- svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 95 ++++++++++++++------ svf/lib/Util/Options.cpp | 8 +- 3 files changed, 78 insertions(+), 32 deletions(-) diff --git a/svf/include/Util/Options.h b/svf/include/Util/Options.h index ca992a0a1..3920e2cbd 100644 --- a/svf/include/Util/Options.h +++ b/svf/include/Util/Options.h @@ -268,9 +268,10 @@ class Options // float precision for symbolic abstraction static const Option AEPrecision; - /// If true, analyze from all entry points (functions without callers) - /// instead of only from main. Useful for library code without main function. - static const Option AEMultiEntry; + /// Comma-separated list of function names to use as analysis entry points. + /// If empty (default), all functions without callers are used as entry points. + /// Example: -ae-entry-funcs="main,init,setup" + static const Option AEEntryFuncs; }; } // namespace SVF diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index 2ade8e759..845772d44 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -35,6 +35,7 @@ #include "WPA/Andersen.h" #include #include +#include using namespace SVF; using namespace SVFUtil; @@ -163,13 +164,47 @@ void AbstractInterpretation::initWTO() } } -/// Collect all entry point functions (functions without callers) +/// Parse comma-separated function names from the -ae-entry-funcs option +static Set parseEntryFuncNames() +{ + Set funcNames; + const std::string& entryFuncsStr = Options::AEEntryFuncs(); + + if (entryFuncsStr.empty()) + return funcNames; + + std::stringstream ss(entryFuncsStr); + std::string funcName; + while (std::getline(ss, funcName, ',')) + { + // Trim whitespace from function name + size_t start = funcName.find_first_not_of(" \t"); + size_t end = funcName.find_last_not_of(" \t"); + if (start != std::string::npos && end != std::string::npos) + { + funcNames.insert(funcName.substr(start, end - start + 1)); + } + else if (!funcName.empty()) + { + funcNames.insert(funcName); + } + } + return funcNames; +} + +/// Collect entry point functions for analysis. +/// If -ae-entry-funcs is specified, only those functions are used. +/// Otherwise, all functions without callers are collected as entry points. /// Uses a deque to allow efficient insertion at front for prioritizing main() std::deque AbstractInterpretation::collectProgEntryFuns() { std::deque entryFunctions; const CallGraph* callGraph = svfir->getCallGraph(); + // Check if user specified explicit entry functions + Set specifiedFuncs = parseEntryFuncNames(); + bool hasSpecifiedFuncs = !specifiedFuncs.empty(); + for (auto it = callGraph->begin(); it != callGraph->end(); ++it) { const CallGraphNode* cgNode = it->second; @@ -179,8 +214,20 @@ std::deque AbstractInterpretation::collectProgEntryFuns() if (fun->isDeclaration()) continue; - // Check if function has no callers (entry point) - if (cgNode->getInEdges().empty()) + bool shouldInclude = false; + + if (hasSpecifiedFuncs) + { + // Use only functions specified by -ae-entry-funcs + shouldInclude = specifiedFuncs.count(fun->getName()) > 0; + } + else + { + // Default: use functions without callers (entry points) + shouldInclude = cgNode->getInEdges().empty(); + } + + if (shouldInclude) { // If main exists, put it first for priority using deque's push_front if (fun->getName() == "main") @@ -194,6 +241,23 @@ std::deque AbstractInterpretation::collectProgEntryFuns() } } + // Warn if specified functions were not found + if (hasSpecifiedFuncs) + { + Set foundFuncs; + for (const FunObjVar* fun : entryFunctions) + { + foundFuncs.insert(fun->getName()); + } + for (const std::string& name : specifiedFuncs) + { + if (foundFuncs.count(name) == 0) + { + SVFUtil::errs() << "Warning: Specified entry function '" << name << "' not found\n"; + } + } + } + return entryFunctions; } @@ -203,34 +267,15 @@ void AbstractInterpretation::clearAbstractTrace() abstractTrace.clear(); } -/// Program entry - analyze from main if exists, otherwise analyze from all entry points +/// Program entry - analyze from all entry points (multi-entry analysis is the default) void AbstractInterpretation::analyse() { initWTO(); // handle Global ICFGNode of SVFModule handleGlobalNode(); - // If -ae-multientry is set, always use multi-entry analysis - if (Options::AEMultiEntry()) - { - SVFUtil::outs() << "Multi-entry analysis enabled, analyzing from all entry points...\n"; - analyzeFromAllProgEntries(); - return; - } - - // Default behavior: start from main if exists - if (const CallGraphNode* cgn = svfir->getCallGraph()->getCallGraphNode("main")) - { - // Use worklist-based function handling instead of recursive WTO component handling - const ICFGNode* mainEntry = icfg->getFunEntryICFGNode(cgn->getFunction()); - handleFunction(mainEntry); - } - else - { - // No main function found, analyze from all entry points (library code) - SVFUtil::outs() << "No main function found, analyzing from all entry points...\n"; - analyzeFromAllProgEntries(); - } + // Always use multi-entry analysis from all entry points + analyzeFromAllProgEntries(); } /// Analyze all entry points (functions without callers) - for whole-program analysis without main diff --git a/svf/lib/Util/Options.cpp b/svf/lib/Util/Options.cpp index 7c4f01cb7..804403bda 100644 --- a/svf/lib/Util/Options.cpp +++ b/svf/lib/Util/Options.cpp @@ -832,10 +832,10 @@ const Option Options::AEPrecision( 0 ); -const Option Options::AEMultiEntry( - "ae-multientry", - "Analyze from all entry points (functions without callers) instead of only main. Useful for library code.", - false +const Option Options::AEEntryFuncs( + "ae-entry-funcs", + "Comma-separated list of function names to use as analysis entry points. If empty, all functions without callers are used.", + "" ); } // namespace SVF. From 4d364ff7ae76683811631d1953b0cb53c4b9c17f Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Sun, 8 Feb 2026 22:20:54 +1100 Subject: [PATCH 25/32] Read the comments in PullRequest (vibe-kanban 78898480) https://github.com/SVF-tools/SVF/pull/1789 Could you read comments in this PR? --- svf/lib/AE/Svfexe/AEDetector.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/svf/lib/AE/Svfexe/AEDetector.cpp b/svf/lib/AE/Svfexe/AEDetector.cpp index 1882b1665..71467c81b 100644 --- a/svf/lib/AE/Svfexe/AEDetector.cpp +++ b/svf/lib/AE/Svfexe/AEDetector.cpp @@ -479,11 +479,23 @@ bool BufOverflowDetector::canSafelyAccessMemory(AbstractState& as, const SVF::SV SVFIR* svfir = PAG::getPAG(); NodeID value_id = value->getId(); - // In multi-entry analysis, some variables may not be initialized as addresses + // Lazy initialization for uninitialized pointer parameters in multi-entry analysis. + // When analyzing a function as an entry point (e.g., not called from main), + // pointer parameters may not have been initialized via AddrStmt. + // + // Example: + // void process_buffer(char* buf, int len) { + // buf[0] = 'a'; // accessing buf + // } + // When analyzing process_buffer as an entry point, 'buf' is a function parameter + // with no AddrStmt, so it has no address information in the abstract state. + // We lazily initialize it to point to the black hole object (BlkPtr), representing + // an unknown but valid memory location. This allows the analysis to continue + // while being conservatively sound. if (!as[value_id].isAddr()) { - // Conservatively assume safe when we don't have address information - return true; + NodeID blkPtrId = svfir->getBlkPtr(); + as[value_id] = AddressValue(AbstractState::getVirtualMemAddress(blkPtrId)); } for (const auto& addr : as[value_id].getAddrs()) { From c0b4582d1a10c18ea50e8fac1f9d1c5338907f23 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Tue, 10 Feb 2026 14:04:55 +1100 Subject: [PATCH 26/32] Read the comments in PullRequest (vibe-kanban 78898480) https://github.com/SVF-tools/SVF/pull/1789 Could you read comments in this PR? --- svf/include/Util/Options.h | 5 -- svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 69 +------------------- svf/lib/Util/Options.cpp | 6 -- 3 files changed, 3 insertions(+), 77 deletions(-) diff --git a/svf/include/Util/Options.h b/svf/include/Util/Options.h index 3920e2cbd..0fa17910d 100644 --- a/svf/include/Util/Options.h +++ b/svf/include/Util/Options.h @@ -267,11 +267,6 @@ class Options // float precision for symbolic abstraction static const Option AEPrecision; - - /// Comma-separated list of function names to use as analysis entry points. - /// If empty (default), all functions without callers are used as entry points. - /// Example: -ae-entry-funcs="main,init,setup" - static const Option AEEntryFuncs; }; } // namespace SVF diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index 845772d44..69e39a2bf 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -35,7 +35,6 @@ #include "WPA/Andersen.h" #include #include -#include using namespace SVF; using namespace SVFUtil; @@ -164,47 +163,14 @@ void AbstractInterpretation::initWTO() } } -/// Parse comma-separated function names from the -ae-entry-funcs option -static Set parseEntryFuncNames() -{ - Set funcNames; - const std::string& entryFuncsStr = Options::AEEntryFuncs(); - - if (entryFuncsStr.empty()) - return funcNames; - - std::stringstream ss(entryFuncsStr); - std::string funcName; - while (std::getline(ss, funcName, ',')) - { - // Trim whitespace from function name - size_t start = funcName.find_first_not_of(" \t"); - size_t end = funcName.find_last_not_of(" \t"); - if (start != std::string::npos && end != std::string::npos) - { - funcNames.insert(funcName.substr(start, end - start + 1)); - } - else if (!funcName.empty()) - { - funcNames.insert(funcName); - } - } - return funcNames; -} - /// Collect entry point functions for analysis. -/// If -ae-entry-funcs is specified, only those functions are used. -/// Otherwise, all functions without callers are collected as entry points. +/// Entry points are functions without callers (no incoming edges in CallGraph). /// Uses a deque to allow efficient insertion at front for prioritizing main() std::deque AbstractInterpretation::collectProgEntryFuns() { std::deque entryFunctions; const CallGraph* callGraph = svfir->getCallGraph(); - // Check if user specified explicit entry functions - Set specifiedFuncs = parseEntryFuncNames(); - bool hasSpecifiedFuncs = !specifiedFuncs.empty(); - for (auto it = callGraph->begin(); it != callGraph->end(); ++it) { const CallGraphNode* cgNode = it->second; @@ -214,20 +180,8 @@ std::deque AbstractInterpretation::collectProgEntryFuns() if (fun->isDeclaration()) continue; - bool shouldInclude = false; - - if (hasSpecifiedFuncs) - { - // Use only functions specified by -ae-entry-funcs - shouldInclude = specifiedFuncs.count(fun->getName()) > 0; - } - else - { - // Default: use functions without callers (entry points) - shouldInclude = cgNode->getInEdges().empty(); - } - - if (shouldInclude) + // Entry points are functions without callers (no incoming edges) + if (cgNode->getInEdges().empty()) { // If main exists, put it first for priority using deque's push_front if (fun->getName() == "main") @@ -241,23 +195,6 @@ std::deque AbstractInterpretation::collectProgEntryFuns() } } - // Warn if specified functions were not found - if (hasSpecifiedFuncs) - { - Set foundFuncs; - for (const FunObjVar* fun : entryFunctions) - { - foundFuncs.insert(fun->getName()); - } - for (const std::string& name : specifiedFuncs) - { - if (foundFuncs.count(name) == 0) - { - SVFUtil::errs() << "Warning: Specified entry function '" << name << "' not found\n"; - } - } - } - return entryFunctions; } diff --git a/svf/lib/Util/Options.cpp b/svf/lib/Util/Options.cpp index 804403bda..a783ce4e9 100644 --- a/svf/lib/Util/Options.cpp +++ b/svf/lib/Util/Options.cpp @@ -832,10 +832,4 @@ const Option Options::AEPrecision( 0 ); -const Option Options::AEEntryFuncs( - "ae-entry-funcs", - "Comma-separated list of function names to use as analysis entry points. If empty, all functions without callers are used.", - "" -); - } // namespace SVF. From 1bdb795e5e325299a4f061540654d674a5774aaf Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Tue, 10 Feb 2026 14:30:26 +1100 Subject: [PATCH 27/32] Read the comments in PullRequest (vibe-kanban 78898480) https://github.com/SVF-tools/SVF/pull/1789 Could you read comments in this PR? --- svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index 69e39a2bf..aff91d2ab 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -218,8 +218,6 @@ void AbstractInterpretation::analyse() /// Analyze all entry points (functions without callers) - for whole-program analysis without main void AbstractInterpretation::analyzeFromAllProgEntries() { - initWTO(); - // Collect all entry point functions std::deque entryFunctions = collectProgEntryFuns(); From f5d899415c74fbffe380232d05adbea4795c455f Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Fri, 13 Feb 2026 13:09:25 +1100 Subject: [PATCH 28/32] 1) use andersen pts to do function ptr 2) refactor blackhole for big programs 3)share states btw multiple entries --- .../AE/Svfexe/AbstractInterpretation.h | 2 + svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 88 +++++++++++++------ 2 files changed, 65 insertions(+), 25 deletions(-) diff --git a/svf/include/AE/Svfexe/AbstractInterpretation.h b/svf/include/AE/Svfexe/AbstractInterpretation.h index dc85535b8..88dde96f7 100644 --- a/svf/include/AE/Svfexe/AbstractInterpretation.h +++ b/svf/include/AE/Svfexe/AbstractInterpretation.h @@ -36,6 +36,7 @@ #include "Util/SVFBugReport.h" #include "Util/SVFStat.h" #include "Graphs/SCC.h" +#include "Graphs/CallGraph.h" #include namespace SVF @@ -332,6 +333,7 @@ class AbstractInterpretation AEAPI* api{nullptr}; ICFG* icfg; + CallGraph* callGraph; AEStat* stat; std::vector callSiteStack; diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index aff91d2ab..c96fdb34d 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -62,6 +62,7 @@ void AbstractInterpretation::runOnModule(ICFG *_icfg) } AbstractInterpretation::AbstractInterpretation() + : callGraph(nullptr) { stat = new AEStat(this); } @@ -99,7 +100,7 @@ void AbstractInterpretation::initWTO() // Detect if the call graph has cycles by finding its strongly connected components (SCC) Andersen::CallGraphSCC* callGraphScc = ander->getCallGraphSCC(); callGraphScc->find(); - CallGraph* callGraph = ander->getCallGraph(); + callGraph = ander->getCallGraph(); // Iterate through the call graph for (auto it = callGraph->begin(); it != callGraph->end(); it++) @@ -215,7 +216,9 @@ void AbstractInterpretation::analyse() analyzeFromAllProgEntries(); } -/// Analyze all entry points (functions without callers) - for whole-program analysis without main +/// Analyze all entry points (functions without callers) - for whole-program analysis. +/// Abstract state is shared across entry points so that functions analyzed from +/// earlier entries are not re-analyzed from scratch. void AbstractInterpretation::analyzeFromAllProgEntries() { // Collect all entry point functions @@ -227,16 +230,14 @@ void AbstractInterpretation::analyzeFromAllProgEntries() return; } - // Analyze from each entry point independently (Scenario 2: different entries -> fresh start) + // Analyze from each entry point, sharing abstract state across entries. + // We do NOT clear abstractTrace between entries: the abstract states computed + // from earlier entries (e.g., library init functions, helper functions) should + // be visible to later entries. This is essential for whole-program analysis + // where different entry points may call shared utility functions — clearing + // the trace would discard already-computed summaries and lose precision. for (const FunObjVar* entryFun : entryFunctions) { - // Clear abstract trace for fresh analysis from this entry - clearAbstractTrace(); - - // Handle global node for each entry (global state is shared across entries) - handleGlobalNode(); - - // Analyze from this entry function const ICFGNode* funEntry = icfg->getFunEntryICFGNode(entryFun); handleFunction(funEntry); } @@ -244,8 +245,9 @@ void AbstractInterpretation::analyzeFromAllProgEntries() /// handle global node /// Initializes the abstract state for the global ICFG node and processes all global statements. -/// This includes setting up the null pointer and black hole pointer (blkPtr) to top value, -/// which represents unknown/uninitialized memory that can point to any location. +/// This includes setting up the null pointer and black hole pointer (blkPtr). +/// BlkPtr is initialized to point to the InvalidMem (BlackHole) object, representing +/// an unknown memory location that cannot be statically resolved. void AbstractInterpretation::handleGlobalNode() { const ICFGNode* node = icfg->getGlobalICFGNode(); @@ -258,10 +260,20 @@ void AbstractInterpretation::handleGlobalNode() handleSVFStatement(stmt); } - // Set black hole pointer to top value - this represents unknown/uninitialized - // memory locations that may point anywhere. This is essential for soundness - // when analyzing code where pointers may not be fully initialized. - abstractTrace[node][PAG::getPAG()->getBlkPtr()] = IntervalValue::top(); + // BlkPtr represents a pointer whose target is statically unknown (e.g., from + // int2ptr casts, external function returns, or unmodeled instructions like + // AtomicCmpXchg). It should be an address pointing to the InvalidMem object + // (BlackHole, ID=2), NOT an interval top. + // + // History: this was originally set to IntervalValue::top() as a quick fix when + // the analysis crashed on programs containing uninitialized BlkPtr. However, + // BlkPtr is semantically a *pointer* (address domain), not a numeric value + // (interval domain). Setting it to interval top broke cross-domain consistency: + // the interval domain and address domain gave contradictory information for the + // same variable. The correct representation is an AddressValue containing the + // BlackHole/InvalidMem virtual address, which means "points to unknown memory". + abstractTrace[node][PAG::getPAG()->getBlkPtr()] = + AddressValue(InvalidMemAddr); } /// get execution state by merging states of predecessor blocks @@ -1021,7 +1033,15 @@ bool AbstractInterpretation::shouldApplyNarrowing(const FunObjVar* fun) return false; } } -/// Handle direct or indirect call: get callee, process function body, set return state +/// Handle direct or indirect call: get callee(s), process function body, set return state. +/// +/// For direct calls, the callee is known statically. +/// For indirect calls, the previous implementation resolved callees from the abstract +/// state's address domain, which only picked the first address and missed other targets. +/// Since the abstract state's address domain is not an over-approximation for function +/// pointers (it may be uninitialized or incomplete), we now use Andersen's pointer +/// analysis results from the pre-computed call graph, which soundly resolves all +/// possible indirect call targets. void AbstractInterpretation::handleFunCall(const CallICFGNode *callNode) { AbstractState& as = getAbsStateFromTrace(callNode); @@ -1031,16 +1051,34 @@ void AbstractInterpretation::handleFunCall(const CallICFGNode *callNode) if (skipRecursiveCall(callNode)) return; - const FunObjVar* callee = getCallee(callNode); - if (!callee) + // Direct call: callee is known + if (const FunObjVar* callee = callNode->getCalledFunction()) + { + callSiteStack.push_back(callNode); + const ICFGNode* calleeEntry = icfg->getFunEntryICFGNode(callee); + handleFunction(calleeEntry); + callSiteStack.pop_back(); + const RetICFGNode* retNode = callNode->getRetICFGNode(); + abstractTrace[retNode] = abstractTrace[callNode]; return; + } - callSiteStack.push_back(callNode); - - const ICFGNode* calleeEntry = icfg->getFunEntryICFGNode(callee); - handleFunction(calleeEntry); - - callSiteStack.pop_back(); + // Indirect call: use Andersen's call graph to get all resolved callees. + // The call graph was built during initWTO() by running Andersen's pointer analysis, + // which over-approximates the set of possible targets for each indirect callsite. + if (callGraph->hasIndCSCallees(callNode)) + { + const auto& callees = callGraph->getIndCSCallees(callNode); + callSiteStack.push_back(callNode); + for (const FunObjVar* callee : callees) + { + if (callee->isDeclaration()) + continue; + const ICFGNode* calleeEntry = icfg->getFunEntryICFGNode(callee); + handleFunction(calleeEntry); + } + callSiteStack.pop_back(); + } const RetICFGNode* retNode = callNode->getRetICFGNode(); abstractTrace[retNode] = abstractTrace[callNode]; } From 586a522c2e1e6415d7d33c4f202caa99e0a4d436 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Fri, 13 Feb 2026 13:35:32 +1100 Subject: [PATCH 29/32] revoke and remove some functions --- svf/include/AE/Svfexe/AbstractInterpretation.h | 2 -- svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 13 +------------ 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/svf/include/AE/Svfexe/AbstractInterpretation.h b/svf/include/AE/Svfexe/AbstractInterpretation.h index 88dde96f7..9d3dcf791 100644 --- a/svf/include/AE/Svfexe/AbstractInterpretation.h +++ b/svf/include/AE/Svfexe/AbstractInterpretation.h @@ -152,8 +152,6 @@ class AbstractInterpretation /// Get all entry point functions (functions without callers) std::deque collectProgEntryFuns(); - /// Clear abstract trace for fresh analysis from new entry - void clearAbstractTrace(); static AbstractInterpretation& getAEInstance() { diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index c96fdb34d..230c85f69 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -199,11 +199,6 @@ std::deque AbstractInterpretation::collectProgEntryFuns() return entryFunctions; } -/// Clear abstract trace for fresh analysis from new entry -void AbstractInterpretation::clearAbstractTrace() -{ - abstractTrace.clear(); -} /// Program entry - analyze from all entry points (multi-entry analysis is the default) void AbstractInterpretation::analyse() @@ -226,16 +221,10 @@ void AbstractInterpretation::analyzeFromAllProgEntries() if (entryFunctions.empty()) { - SVFUtil::errs() << "Warning: No entry functions found for analysis\n"; + assert(false && "No entry functions found for analysis"); return; } - // Analyze from each entry point, sharing abstract state across entries. - // We do NOT clear abstractTrace between entries: the abstract states computed - // from earlier entries (e.g., library init functions, helper functions) should - // be visible to later entries. This is essential for whole-program analysis - // where different entry points may call shared utility functions — clearing - // the trace would discard already-computed summaries and lose precision. for (const FunObjVar* entryFun : entryFunctions) { const ICFGNode* funEntry = icfg->getFunEntryICFGNode(entryFun); From ade5b61a6bf502ac4b20848ccb360a796d06c817 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Fri, 13 Feb 2026 23:25:33 +1100 Subject: [PATCH 30/32] refactor some bug fixes in String handler --- svf/include/AE/Svfexe/AbsExtAPI.h | 44 +--- svf/lib/AE/Svfexe/AbsExtAPI.cpp | 363 +++++++++++++----------------- 2 files changed, 164 insertions(+), 243 deletions(-) diff --git a/svf/include/AE/Svfexe/AbsExtAPI.h b/svf/include/AE/Svfexe/AbsExtAPI.h index b97da7ba5..858ebd752 100644 --- a/svf/include/AE/Svfexe/AbsExtAPI.h +++ b/svf/include/AE/Svfexe/AbsExtAPI.h @@ -74,43 +74,23 @@ class AbsExtAPI */ void handleExtAPI(const CallICFGNode *call); - /** - * @brief Handles the strcpy API call. - * @param call Pointer to the call ICFG node. - */ - void handleStrcpy(const CallICFGNode *call); + // --- Shared primitives used by string/memory handlers --- - /** - * @brief Calculates the length of a string. - * @param as Reference to the abstract state. - * @param strValue Pointer to the SVF variable representing the string. - * @return The interval value representing the string length. - */ + /// Get the byte size of each element for a pointer/array variable. + u32_t getElementSize(AbstractState& as, const SVFVar* var); + + /// Check if an interval length is usable (not bottom, not unbounded). + static bool isValidLength(const IntervalValue& len); + + /// Calculate the length of a null-terminated string in abstract state. IntervalValue getStrlen(AbstractState& as, const SVF::SVFVar *strValue); - /** - * @brief Handles the strcat API call. - * @param call Pointer to the call ICFG node. - */ - void handleStrcat(const SVF::CallICFGNode *call); + // --- String/memory operation handlers --- - /** - * @brief Handles the memcpy API call. - * @param as Reference to the abstract state. - * @param dst Pointer to the destination SVF variable. - * @param src Pointer to the source SVF variable. - * @param len The interval value representing the length to copy. - * @param start_idx The starting index for copying. - */ + void handleStrcpy(const CallICFGNode *call); + void handleStrcat(const CallICFGNode *call); + void handleStrncat(const CallICFGNode *call); void handleMemcpy(AbstractState& as, const SVF::SVFVar *dst, const SVF::SVFVar *src, IntervalValue len, u32_t start_idx); - - /** - * @brief Handles the memset API call. - * @param as Reference to the abstract state. - * @param dst Pointer to the destination SVF variable. - * @param elem The interval value representing the element to set. - * @param len The interval value representing the length to set. - */ void handleMemset(AbstractState& as, const SVFVar* dst, IntervalValue elem, IntervalValue len); /** diff --git a/svf/lib/AE/Svfexe/AbsExtAPI.cpp b/svf/lib/AE/Svfexe/AbsExtAPI.cpp index 6e6d3f4bc..a6400a88e 100644 --- a/svf/lib/AE/Svfexe/AbsExtAPI.cpp +++ b/svf/lib/AE/Svfexe/AbsExtAPI.cpp @@ -272,60 +272,19 @@ void AbsExtAPI::initExtFunMap() auto sse_strlen = [&](const CallICFGNode *callNode) { - // check the arg size if (callNode->arg_size() < 1) return; - const SVFVar* strValue = callNode->getArgument(0); AbstractState& as = getAbsStateFromTrace(callNode); - NodeID value_id = strValue->getId(); u32_t lhsId = callNode->getRetICFGNode()->getActualRet()->getId(); - u32_t dst_size = 0; - for (const auto& addr : as[value_id].getAddrs()) - { - NodeID objId = as.getIDFromAddr(addr); - if (svfir->getBaseObject(objId)->isConstantByteSize()) - { - dst_size = svfir->getBaseObject(objId)->getByteSizeOfObj(); - } - else - { - const ICFGNode* addrNode = svfir->getBaseObject(objId)->getICFGNode(); - for (const SVFStmt* stmt2: addrNode->getSVFStmts()) - { - if (const AddrStmt* addrStmt = SVFUtil::dyn_cast(stmt2)) - { - dst_size = as.getAllocaInstByteSize(addrStmt); - } - } - } - } - u32_t len = 0; - NodeID dstid = strValue->getId(); - if (as.inVarToAddrsTable(dstid)) - { - for (u32_t index = 0; index < dst_size; index++) - { - AbstractValue expr0 = - as.getGepObjAddrs(dstid, IntervalValue(index)); - AbstractValue val; - for (const auto &addr: expr0.getAddrs()) - { - val.join_with(as.load(addr)); - } - if (val.getInterval().is_numeral() && (char) val.getInterval().getIntNumeral() == '\0') - { - break; - } - ++len; - } - } - if (len == 0) - { - as[lhsId] = IntervalValue((s64_t)0, (s64_t)Options::MaxFieldLimit()); - } + // strlen/wcslen return the number of characters (not bytes). + // getStrlen returns byte-scaled length (len * elemSize) for use + // by memcpy/strcpy. Here we need the raw character count, so + // divide back by elemSize. + IntervalValue byteLen = getStrlen(as, callNode->getArgument(0)); + u32_t elemSize = getElementSize(as, callNode->getArgument(0)); + if (byteLen.is_numeral() && elemSize > 1) + as[lhsId] = IntervalValue(byteLen.getIntNumeral() / (s64_t)elemSize); else - { - as[lhsId] = IntervalValue(len); - } + as[lhsId] = byteLen; }; func_map["strlen"] = sse_strlen; func_map["wcslen"] = sse_strlen; @@ -480,7 +439,13 @@ void AbsExtAPI::handleExtAPI(const CallICFGNode *call) } else if (extType == STRCAT) { - handleStrcat(call); + // Both strcat and strncat are annotated as STRCAT. + // Distinguish by name: strncat/wcsncat contain "ncat". + const std::string& name = fun->getName(); + if (name.find("ncat") != std::string::npos) + handleStrncat(call); + else + handleStrcat(call); } else { @@ -489,24 +454,50 @@ void AbsExtAPI::handleExtAPI(const CallICFGNode *call) return; } -void AbsExtAPI::handleStrcpy(const CallICFGNode *call) +// ===----------------------------------------------------------------------===// +// Shared primitives for string/memory handlers +// ===----------------------------------------------------------------------===// + +/// Get the byte size of each element for a pointer/array variable. +/// Shared by handleMemcpy, handleMemset, and getStrlen to avoid duplication. +u32_t AbsExtAPI::getElementSize(AbstractState& as, const SVFVar* var) { - // strcpy, __strcpy_chk, stpcpy , wcscpy, __wcscpy_chk - // get the dst and src - AbstractState& as = getAbsStateFromTrace(call); - const SVFVar* arg0Val = call->getArgument(0); - const SVFVar* arg1Val = call->getArgument(1); - IntervalValue strLen = getStrlen(as, arg1Val); - // no need to -1, since it has \0 as the last byte - // Skip if strLen is bottom or unbounded - if (strLen.isBottom() || strLen.lb().is_minus_infinity()) - return; - handleMemcpy(as, arg0Val, arg1Val, strLen, strLen.lb().getIntNumeral()); + if (var->getType()->isArrayTy()) + { + return SVFUtil::dyn_cast(var->getType()) + ->getTypeOfElement()->getByteSize(); + } + if (var->getType()->isPointerTy()) + { + if (const SVFType* elemType = as.getPointeeElement(var->getId())) + { + if (elemType->isArrayTy()) + return SVFUtil::dyn_cast(elemType) + ->getTypeOfElement()->getByteSize(); + return elemType->getByteSize(); + } + return 1; + } + assert(false && "unsupported type for element size"); + return 1; +} + +/// Check if an interval length is usable for memory operations. +/// Returns false for bottom (no information) or unbounded lower bound +/// (cannot determine a concrete start for iteration). +bool AbsExtAPI::isValidLength(const IntervalValue& len) +{ + return !len.isBottom() && !len.lb().is_minus_infinity(); } +/// Calculate the length of a null-terminated string in abstract state. +/// Scans memory from the base of strValue looking for a '\0' byte. +/// Returns an IntervalValue: exact length if '\0' found, otherwise [0, MaxFieldLimit]. IntervalValue AbsExtAPI::getStrlen(AbstractState& as, const SVF::SVFVar *strValue) { NodeID value_id = strValue->getId(); + + // Step 1: determine the buffer size (in bytes) backing this pointer u32_t dst_size = 0; for (const auto& addr : as[value_id].getAddrs()) { @@ -527,8 +518,9 @@ IntervalValue AbsExtAPI::getStrlen(AbstractState& as, const SVF::SVFVar *strValu } } } + + // Step 2: scan for '\0' terminator u32_t len = 0; - u32_t elemSize = 1; if (as.inVarToAddrsTable(value_id)) { for (u32_t index = 0; index < dst_size; index++) @@ -540,204 +532,153 @@ IntervalValue AbsExtAPI::getStrlen(AbstractState& as, const SVF::SVFVar *strValu { val.join_with(as.load(addr)); } - if (val.getInterval().is_numeral() && (char) val.getInterval().getIntNumeral() == '\0') + if (val.getInterval().is_numeral() && + (char) val.getInterval().getIntNumeral() == '\0') { break; } ++len; } - if (strValue->getType()->isArrayTy()) - { - elemSize = SVFUtil::dyn_cast(strValue->getType())->getTypeOfElement()->getByteSize(); - } - else if (strValue->getType()->isPointerTy()) - { - if (const SVFType* elemType = as.getPointeeElement(value_id)) - { - if (elemType->isArrayTy()) - elemSize = SVFUtil::dyn_cast(elemType)->getTypeOfElement()->getByteSize(); - else - elemSize = elemType->getByteSize(); - } - else - { - elemSize = 1; - } - } - else - { - assert(false && "we cannot support this type"); - } } + + // Step 3: scale by element size and return + u32_t elemSize = getElementSize(as, strValue); if (len == 0) - { return IntervalValue((s64_t)0, (s64_t)Options::MaxFieldLimit()); - } - else - { - return IntervalValue(len * elemSize); - } + return IntervalValue(len * elemSize); } +// ===----------------------------------------------------------------------===// +// String/memory operation handlers +// ===----------------------------------------------------------------------===// -void AbsExtAPI::handleStrcat(const SVF::CallICFGNode *call) +/// strcpy(dst, src): copy all of src (including '\0') into dst. +/// Covers: strcpy, __strcpy_chk, stpcpy, wcscpy, __wcscpy_chk +void AbsExtAPI::handleStrcpy(const CallICFGNode *call) { - // __strcat_chk, strcat, __wcscat_chk, wcscat, __strncat_chk, strncat, __wcsncat_chk, wcsncat - // to check it is strcat group or strncat group AbstractState& as = getAbsStateFromTrace(call); - const FunObjVar *fun = call->getCalledFunction(); - const std::vector strcatGroup = {"__strcat_chk", "strcat", "__wcscat_chk", "wcscat"}; - const std::vector strncatGroup = {"__strncat_chk", "strncat", "__wcsncat_chk", "wcsncat"}; - if (std::find(strcatGroup.begin(), strcatGroup.end(), fun->getName()) != strcatGroup.end()) - { - const SVFVar* arg0Val = call->getArgument(0); - const SVFVar* arg1Val = call->getArgument(1); - IntervalValue strLen0 = getStrlen(as, arg0Val); - IntervalValue strLen1 = getStrlen(as, arg1Val); - IntervalValue totalLen = strLen0 + strLen1; - // Skip if strLen0 is bottom or unbounded - if (strLen0.isBottom() || strLen0.lb().is_minus_infinity()) - return; - handleMemcpy(as, arg0Val, arg1Val, strLen1, strLen0.lb().getIntNumeral()); - // do memcpy - } - else if (std::find(strncatGroup.begin(), strncatGroup.end(), fun->getName()) != strncatGroup.end()) - { - const SVFVar* arg0Val = call->getArgument(0); - const SVFVar* arg1Val = call->getArgument(1); - const SVFVar* arg2Val = call->getArgument(2); - IntervalValue arg2Num = as[arg2Val->getId()].getInterval(); - IntervalValue strLen0 = getStrlen(as, arg0Val); - IntervalValue totalLen = strLen0 + arg2Num; - // Skip if strLen0 is bottom or unbounded - if (strLen0.isBottom() || strLen0.lb().is_minus_infinity()) - return; - handleMemcpy(as, arg0Val, arg1Val, arg2Num, strLen0.lb().getIntNumeral()); - // do memcpy - } - else - { - assert(false && "unknown strcat function, please add it to strcatGroup or strncatGroup"); - } + const SVFVar* dst = call->getArgument(0); + const SVFVar* src = call->getArgument(1); + IntervalValue srcLen = getStrlen(as, src); + // no need to -1, since srcLen includes up to (but not past) '\0' + if (!isValidLength(srcLen)) return; + handleMemcpy(as, dst, src, srcLen, 0); +} + +/// strcat(dst, src): append all of src after the end of dst. +/// Covers: strcat, __strcat_chk, wcscat, __wcscat_chk +void AbsExtAPI::handleStrcat(const CallICFGNode *call) +{ + AbstractState& as = getAbsStateFromTrace(call); + const SVFVar* dst = call->getArgument(0); + const SVFVar* src = call->getArgument(1); + IntervalValue dstLen = getStrlen(as, dst); + IntervalValue srcLen = getStrlen(as, src); + if (!isValidLength(dstLen)) return; + handleMemcpy(as, dst, src, srcLen, dstLen.lb().getIntNumeral()); } -void AbsExtAPI::handleMemcpy(AbstractState& as, const SVF::SVFVar *dst, const SVF::SVFVar *src, IntervalValue len, u32_t start_idx) +/// strncat(dst, src, n): append at most n bytes of src after the end of dst. +/// Covers: strncat, __strncat_chk, wcsncat, __wcsncat_chk +void AbsExtAPI::handleStrncat(const CallICFGNode *call) { - u32_t dstId = dst->getId(); // pts(dstId) = {objid} objbar objtypeinfo->getType(). + AbstractState& as = getAbsStateFromTrace(call); + const SVFVar* dst = call->getArgument(0); + const SVFVar* src = call->getArgument(1); + IntervalValue n = as[call->getArgument(2)->getId()].getInterval(); + IntervalValue dstLen = getStrlen(as, dst); + if (!isValidLength(dstLen)) return; + handleMemcpy(as, dst, src, n, dstLen.lb().getIntNumeral()); +} + +/// Core memcpy: copy `len` bytes from src to dst starting at dst[start_idx]. +void AbsExtAPI::handleMemcpy(AbstractState& as, const SVF::SVFVar *dst, + const SVF::SVFVar *src, IntervalValue len, + u32_t start_idx) +{ + if (!isValidLength(len)) return; + + u32_t dstId = dst->getId(); u32_t srcId = src->getId(); - u32_t elemSize = 1; - if (dst->getType()->isArrayTy()) - { - elemSize = SVFUtil::dyn_cast(dst->getType())->getTypeOfElement()->getByteSize(); - } - // memcpy(i32*, i32*, 40) - else if (dst->getType()->isPointerTy()) - { - if (const SVFType* elemType = as.getPointeeElement(dstId)) - { - if (elemType->isArrayTy()) - elemSize = SVFUtil::dyn_cast(elemType)->getTypeOfElement()->getByteSize(); - else - elemSize = elemType->getByteSize(); - } - else - { - elemSize = 1; - } - } - else - { - assert(false && "we cannot support this type"); - } - // Handle bottom or unbounded interval - skip memcpy in these cases - if (len.isBottom() || len.lb().is_minus_infinity()) - { - return; - } - u32_t size = std::min((u32_t)Options::MaxFieldLimit(), (u32_t) len.lb().getIntNumeral()); + u32_t elemSize = getElementSize(as, dst); + u32_t size = std::min((u32_t)Options::MaxFieldLimit(), + (u32_t)len.lb().getIntNumeral()); u32_t range_val = size / elemSize; - if (as.inVarToAddrsTable(srcId) && as.inVarToAddrsTable(dstId)) + + if (!as.inVarToAddrsTable(srcId) || !as.inVarToAddrsTable(dstId)) + return; + + for (u32_t index = 0; index < range_val; index++) { - for (u32_t index = 0; index < range_val; index++) + AbstractValue expr_src = + as.getGepObjAddrs(srcId, IntervalValue(index)); + AbstractValue expr_dst = + as.getGepObjAddrs(dstId, IntervalValue(index + start_idx)); + for (const auto &dstAddr: expr_dst.getAddrs()) { - // dead loop for string and break if there's a \0. If no \0, it will throw err. - AbstractValue expr_src = - as.getGepObjAddrs(srcId, IntervalValue(index)); - AbstractValue expr_dst = - as.getGepObjAddrs(dstId, IntervalValue(index + start_idx)); - for (const auto &dst: expr_dst.getAddrs()) + for (const auto &srcAddr: expr_src.getAddrs()) { - for (const auto &src: expr_src.getAddrs()) + u32_t objId = as.getIDFromAddr(srcAddr); + if (as.inAddrToValTable(objId) || as.inAddrToAddrsTable(objId)) { - u32_t objId = as.getIDFromAddr(src); - if (as.inAddrToValTable(objId)) - { - as.store(dst, as.load(src)); - } - else if (as.inAddrToAddrsTable(objId)) - { - as.store(dst, as.load(src)); - } + as.store(dstAddr, as.load(srcAddr)); } } } } } -void AbsExtAPI::handleMemset(AbstractState& as, const SVF::SVFVar *dst, IntervalValue elem, IntervalValue len) +/// Core memset: fill dst with `elem` for `len` bytes. +/// Note: elemSize here uses the pointee type's full size (not array element size) +/// to match how LLVM memset/wmemset intrinsics measure `len`. For a pointer to +/// wchar_t[100], elemSize = sizeof(wchar_t[100]), so range_val reflects the +/// number of top-level GEP fields, not individual array elements. +void AbsExtAPI::handleMemset(AbstractState& as, const SVF::SVFVar *dst, + IntervalValue elem, IntervalValue len) { - // Handle bottom or unbounded interval - skip memset in these cases - if (len.isBottom() || len.lb().is_minus_infinity()) - { - return; - } + if (!isValidLength(len)) return; + u32_t dstId = dst->getId(); - u32_t size = std::min((u32_t)Options::MaxFieldLimit(), (u32_t) len.lb().getIntNumeral()); u32_t elemSize = 1; if (dst->getType()->isArrayTy()) { - elemSize = SVFUtil::dyn_cast(dst->getType())->getTypeOfElement()->getByteSize(); + elemSize = SVFUtil::dyn_cast(dst->getType()) + ->getTypeOfElement()->getByteSize(); } else if (dst->getType()->isPointerTy()) { if (const SVFType* elemType = as.getPointeeElement(dstId)) - { elemSize = elemType->getByteSize(); - } else - { elemSize = 1; - } } else { - assert(false && "we cannot support this type"); + assert(false && "unsupported type for element size"); } - + u32_t size = std::min((u32_t)Options::MaxFieldLimit(), + (u32_t)len.lb().getIntNumeral()); u32_t range_val = size / elemSize; + for (u32_t index = 0; index < range_val; index++) { - // dead loop for string and break if there's a \0. If no \0, it will throw err. - if (as.inVarToAddrsTable(dstId)) + if (!as.inVarToAddrsTable(dstId)) + break; + AbstractValue lhs_gep = as.getGepObjAddrs(dstId, IntervalValue(index)); + for (const auto &addr: lhs_gep.getAddrs()) { - AbstractValue lhs_gep = as.getGepObjAddrs(dstId, IntervalValue(index)); - for (const auto &addr: lhs_gep.getAddrs()) + u32_t objId = as.getIDFromAddr(addr); + if (as.inAddrToValTable(objId)) { - u32_t objId = as.getIDFromAddr(addr); - if (as.inAddrToValTable(objId)) - { - AbstractValue tmp = as.load(addr); - tmp.join_with(elem); - as.store(addr, tmp); - } - else - { - as.store(addr, elem); - } + AbstractValue tmp = as.load(addr); + tmp.join_with(elem); + as.store(addr, tmp); + } + else + { + as.store(addr, elem); } } - else - break; } } From 1ef5e40f7be10b7cfa3de8b80578ea5f3d47da80 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Sat, 14 Feb 2026 21:51:58 +1100 Subject: [PATCH 31/32] fix for comments --- svf/lib/AE/Svfexe/AEDetector.cpp | 3 +-- svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 10 ++++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/svf/lib/AE/Svfexe/AEDetector.cpp b/svf/lib/AE/Svfexe/AEDetector.cpp index 71467c81b..91431cc90 100644 --- a/svf/lib/AE/Svfexe/AEDetector.cpp +++ b/svf/lib/AE/Svfexe/AEDetector.cpp @@ -494,8 +494,7 @@ bool BufOverflowDetector::canSafelyAccessMemory(AbstractState& as, const SVF::SV // while being conservatively sound. if (!as[value_id].isAddr()) { - NodeID blkPtrId = svfir->getBlkPtr(); - as[value_id] = AddressValue(AbstractState::getVirtualMemAddress(blkPtrId)); + as[value_id] = AddressValue(InvalidMemAddr); } for (const auto& addr : as[value_id].getAddrs()) { diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index 230c85f69..97bd353c4 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -62,8 +62,9 @@ void AbstractInterpretation::runOnModule(ICFG *_icfg) } AbstractInterpretation::AbstractInterpretation() - : callGraph(nullptr) { + AndersenWaveDiff* ander = AndersenWaveDiff::createAndersenWaveDiff(svfir); + callGraph = ander->getCallGraph(); stat = new AEStat(this); } /// Destructor @@ -96,11 +97,9 @@ void AbstractInterpretation::collectCycleHeads(const std::listgetCallGraphSCC(); callGraphScc->find(); - callGraph = ander->getCallGraph(); // Iterate through the call graph for (auto it = callGraph->begin(); it != callGraph->end(); it++) @@ -204,8 +203,6 @@ std::deque AbstractInterpretation::collectProgEntryFuns() void AbstractInterpretation::analyse() { initWTO(); - // handle Global ICFGNode of SVFModule - handleGlobalNode(); // Always use multi-entry analysis from all entry points analyzeFromAllProgEntries(); @@ -224,7 +221,8 @@ void AbstractInterpretation::analyzeFromAllProgEntries() assert(false && "No entry functions found for analysis"); return; } - + // handle Global ICFGNode of SVFModule + handleGlobalNode(); for (const FunObjVar* entryFun : entryFunctions) { const ICFGNode* funEntry = icfg->getFunEntryICFGNode(entryFun); From aa8675417d95d294c2724336cf4c7f2fe1f99a82 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Sat, 14 Feb 2026 21:58:40 +1100 Subject: [PATCH 32/32] fix ci --- svf/include/AE/Svfexe/AbstractInterpretation.h | 1 + svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/svf/include/AE/Svfexe/AbstractInterpretation.h b/svf/include/AE/Svfexe/AbstractInterpretation.h index 9d3dcf791..02bd46ca2 100644 --- a/svf/include/AE/Svfexe/AbstractInterpretation.h +++ b/svf/include/AE/Svfexe/AbstractInterpretation.h @@ -332,6 +332,7 @@ class AbstractInterpretation ICFG* icfg; CallGraph* callGraph; + CallGraphSCC* callGraphScc; AEStat* stat; std::vector callSiteStack; diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index 97bd353c4..b490217c0 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -65,6 +65,9 @@ AbstractInterpretation::AbstractInterpretation() { AndersenWaveDiff* ander = AndersenWaveDiff::createAndersenWaveDiff(svfir); callGraph = ander->getCallGraph(); + // Detect if the call graph has cycles by finding its strongly connected components (SCC) + callGraphScc = ander->getCallGraphSCC(); + callGraphScc->find(); stat = new AEStat(this); } /// Destructor @@ -97,10 +100,6 @@ void AbstractInterpretation::collectCycleHeads(const std::listgetCallGraphSCC(); - callGraphScc->find(); - // Iterate through the call graph for (auto it = callGraph->begin(); it != callGraph->end(); it++) {