Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6846eb2
refactor AE, use worklist algorithm(naive)
Jan 26, 2026
353871d
sync with SSA Ass3
Jan 28, 2026
3847a44
refactor recursion
Jan 29, 2026
b7be147
Continue to reduce the Options() handleRecur. (vibe-kanban 5f1e98b0)
Jan 31, 2026
e7f18f8
Continue to reduce the Options() handleRecur. (vibe-kanban 5f1e98b0)
Jan 31, 2026
500c22f
Continue to reduce the Options() handleRecur. (vibe-kanban 5f1e98b0)
Feb 2, 2026
94ab144
Continue to reduce the Options() handleRecur. (vibe-kanban 5f1e98b0)
Feb 2, 2026
809a397
Continue to reduce the Options() handleRecur. (vibe-kanban 5f1e98b0)
Feb 2, 2026
ec7f7ca
Continue to reduce the Options() handleRecur. (vibe-kanban 5f1e98b0)
Feb 2, 2026
8b0605c
Continue to reduce the Options() handleRecur. (vibe-kanban 5f1e98b0)
Feb 2, 2026
e6942d5
Continue to reduce the Options() handleRecur. (vibe-kanban 5f1e98b0)
Feb 2, 2026
9c72e10
Continue to reduce the Options() handleRecur. (vibe-kanban 5f1e98b0)
Feb 2, 2026
e0d7674
Continue to reduce the Options() handleRecur. (vibe-kanban 5f1e98b0)
Feb 2, 2026
bf793f8
rename handleICFGCycle
Feb 3, 2026
09e86d5
Continue to reduce the Options() handleRecur. (vibe-kanban 5f1e98b0)
Feb 3, 2026
76c2374
rename two functions in AbstractInterpretation (vibe-kanban 313b27a9)
Feb 4, 2026
7fce768
rename two functions in AbstractInterpretation (vibe-kanban 313b27a9)
Feb 4, 2026
d3b4fae
rename two functions in AbstractInterpretation (vibe-kanban 313b27a9)
Feb 4, 2026
4c87a38
Add multi-entry whole-program analysis for library code
Feb 6, 2026
7444f96
Add -ae-multientry option for multi-entry analysis
Feb 6, 2026
2aa167c
Fix handleICFGNode regression in function entry state handling
Feb 6, 2026
afb1b8f
Fix assertion errors in AE for multi-entry analysis
Feb 6, 2026
17c751c
fix merge conflict
Feb 7, 2026
bbb2c39
Some Rename and Refactor
Feb 8, 2026
d70d6fa
Read the comments in PullRequest (vibe-kanban 78898480)
Feb 8, 2026
4d364ff
Read the comments in PullRequest (vibe-kanban 78898480)
Feb 8, 2026
c0b4582
Read the comments in PullRequest (vibe-kanban 78898480)
Feb 10, 2026
1bdb795
Read the comments in PullRequest (vibe-kanban 78898480)
Feb 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions svf/include/AE/Svfexe/AbstractInterpretation.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include "Util/SVFBugReport.h"
#include "Util/SVFStat.h"
#include "Graphs/SCC.h"
#include <deque>

namespace SVF
{
Expand Down Expand Up @@ -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<const FunObjVar*> collectProgEntryFuns();

/// Clear abstract trace for fresh analysis from new entry
void clearAbstractTrace();

static AbstractInterpretation& getAEInstance()
{
static AbstractInterpretation instance;
Expand Down Expand Up @@ -358,6 +368,7 @@ class AbstractInterpretation
Map<std::string, std::function<void(const CallICFGNode*)>> func_map;

Map<const ICFGNode*, AbstractState> abstractTrace; // abstract states immediately after nodes
Set<const ICFGNode*> allAnalyzedNodes; // All nodes ever analyzed (across all entry points)
std::string moduleName;

std::vector<std::unique_ptr<AEDetector>> detectors;
Expand Down
19 changes: 18 additions & 1 deletion svf/lib/AE/Svfexe/AEDetector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
19 changes: 19 additions & 0 deletions svf/lib/AE/Svfexe/AbsExtAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we share as much code as possible for string handling functions you have

if (strLen.isBottom() || strLen.lb().is_minus_infinity())
return;
handleMemcpy(as, arg0Val, arg1Val, strLen, strLen.lb().getIntNumeral());
}

Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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;
Expand Down
155 changes: 145 additions & 10 deletions svf/lib/AE/Svfexe/AbstractInterpretation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "Graphs/CallGraph.h"
#include "WPA/Andersen.h"
#include <cmath>
#include <deque>

using namespace SVF;
using namespace SVFUtil;
Expand Down Expand Up @@ -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<const FunObjVar*> AbstractInterpretation::collectProgEntryFuns()
{
std::deque<const FunObjVar*> entryFunctions;
const CallGraph* callGraph = svfir->getCallGraph();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to use andersen's call graph if we have.


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<const FunObjVar*> 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";
Copy link
Collaborator

@yuleisui yuleisui Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should always have at least one entry function (no caller function). May be an assert is better.

return;
}

// Analyze from each entry point independently (Scenario 2: different entries -> fresh start)
for (const FunObjVar* entryFun : entryFunctions)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think handle global should be done before entry function?

Also it would be good to add each entry icfgnode of entry function into the worklist for later abstract interpretation?

{
// Clear abstract trace for fresh analysis from this entry
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handle global node can be done outside this for loop?

It is unclear why we need to clear abstract trace here? The abstract states that for an ICFGNode A should be merged if A has two callers (if both callers are entry functions)?

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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to double-check handleCallsite whether andersen's call graph shall be used?

}
}

/// 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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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<const FunObjVar *> funs;
Set<const FunObjVar *> 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<CallICFGNode>(it.second))
{
Expand All @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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: ");
}
Expand Down Expand Up @@ -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: ");
}
Expand Down
Loading