Skip to content
Open
Show file tree
Hide file tree
Changes from 23 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
10 changes: 10 additions & 0 deletions svf/include/AE/Svfexe/AbstractInterpretation.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,15 @@ class AbstractInterpretation
/// Program entry
void analyse();

/// Analyze all entry points (functions without callers)
void analyseFromAllEntries();
Copy link
Collaborator

Choose a reason for hiding this comment

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

analyzeFromAllProgEntries


/// Get all entry point functions (functions without callers)
std::vector<const FunObjVar*> collectEntryFunctions();
Copy link
Collaborator

Choose a reason for hiding this comment

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

collectProgEntryFuns


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

static AbstractInterpretation& getAEInstance()
{
static AbstractInterpretation instance;
Expand Down Expand Up @@ -358,6 +367,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
4 changes: 4 additions & 0 deletions svf/include/Util/Options.h
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,10 @@ class Options

// float precision for symbolic abstraction
static const Option<u32_t> 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<bool> AEMultiEntry;
};
} // namespace SVF

Expand Down
7 changes: 6 additions & 1 deletion svf/lib/AE/Svfexe/AEDetector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you add an example here?

Shall we also intitalize them?

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);
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
156 changes: 152 additions & 4 deletions svf/lib/AE/Svfexe/AbstractInterpretation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,20 +162,106 @@ void AbstractInterpretation::initWTO()
}
}

/// Program entry
/// Collect all entry point functions (functions without callers)
std::vector<const FunObjVar*> AbstractInterpretation::collectEntryFunctions()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Better to use a deque if you can both push from back and push to front (for main). Then no std::find_if is needed later.

{
std::vector<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;

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

Choose a reason for hiding this comment

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

This should be by default.

{
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
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<const FunObjVar*> entryFunctions = collectEntryFunctions();

if (entryFunctions.empty())
{
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();
getAbsStateFromTrace(
icfg->getGlobalICFGNode())[PAG::getPAG()->getBlkPtr()] = IntervalValue::top();
Copy link
Collaborator

@yuleisui yuleisui Feb 7, 2026

Choose a reason for hiding this comment

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

should this be moved to handleGlobalNode? Please also make a note to explain why this assignment is done here.


// 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
Expand Down Expand Up @@ -661,6 +747,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 +1318,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 +1365,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 +1406,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 +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: ");
}
Expand Down Expand Up @@ -1719,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: ");
}
Expand Down
6 changes: 6 additions & 0 deletions svf/lib/Util/Options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -832,4 +832,10 @@ const Option<u32_t> Options::AEPrecision(
0
);

const Option<bool> Options::AEMultiEntry(
"ae-multientry",
"Analyze from all entry points (functions without callers) instead of only main. Useful for library code.",
false
);

} // namespace SVF.
Loading