diff --git a/svf/include/AE/Svfexe/AbstractInterpretation.h b/svf/include/AE/Svfexe/AbstractInterpretation.h index aea2e9d90..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 { @@ -144,6 +145,15 @@ class AbstractInterpretation /// Program entry void analyse(); + /// Analyze all entry points (functions without callers) + void analyzeFromAllProgEntries(); + + /// 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() { static AbstractInterpretation instance; @@ -358,6 +368,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/AEDetector.cpp b/svf/lib/AE/Svfexe/AEDetector.cpp index ae9d0bb3c..71467c81b 100644 --- a/svf/lib/AE/Svfexe/AEDetector.cpp +++ b/svf/lib/AE/Svfexe/AEDetector.cpp @@ -479,7 +479,24 @@ bool BufOverflowDetector::canSafelyAccessMemory(AbstractState& as, const SVF::SV SVFIR* svfir = PAG::getPAG(); NodeID value_id = value->getId(); - assert(as[value_id].isAddr()); + // 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()) + { + NodeID blkPtrId = svfir->getBlkPtr(); + as[value_id] = AddressValue(AbstractState::getVirtualMemAddress(blkPtrId)); + } for (const auto& addr : as[value_id].getAddrs()) { NodeID objId = as.getIDFromAddr(addr); 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..aff91d2ab 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; @@ -162,33 +163,105 @@ void AbstractInterpretation::initWTO() } } -/// Program entry +/// Collect entry point functions for analysis. +/// 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(); + + 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; + + // 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") + { + entryFunctions.push_front(fun); + } + else + { + entryFunctions.push_back(fun); + } + } + } + + 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() { initWTO(); // handle Global ICFGNode of SVFModule handleGlobalNode(); - getAbsStateFromTrace( - icfg->getGlobalICFGNode())[PAG::getPAG()->getBlkPtr()] = IntervalValue::top(); - if (const CallGraphNode* cgn = svfir->getCallGraph()->getCallGraphNode("main")) + + // Always use multi-entry analysis from all entry points + analyzeFromAllProgEntries(); +} + +/// Analyze all entry points (functions without callers) - for whole-program analysis without main +void AbstractInterpretation::analyzeFromAllProgEntries() +{ + // Collect all entry point functions + std::deque entryFunctions = collectProgEntryFuns(); + + if (entryFunctions.empty()) { - // Use worklist-based function handling instead of recursive WTO component handling - const ICFGNode* mainEntry = icfg->getFunEntryICFGNode(cgn->getFunction()); - handleFunction(mainEntry); + 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(); + + // Analyze from this entry function + const ICFGNode* funEntry = icfg->getFunEntryICFGNode(entryFun); + handleFunction(funEntry); } } /// 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 @@ -661,6 +734,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 +1305,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 +1352,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 +1393,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) @@ -1605,6 +1726,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: "); } @@ -1719,6 +1847,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: "); }