Skip to content

Commit e676d5f

Browse files
bjjwwangclaude
andcommitted
Add sparse abstract interpretation with def-use driven state propagation
Introduces sparse mode (-sparse flag) for Abstract Interpretation that propagates only needed variable states at each ICFG node, instead of copying full abstract states from predecessors. Key components: - SparseDefUse: maps address-taken ObjVars to their StoreStmt def sites - getValVars/getObjVars/getSVFVars: collect variables needed at each node - getAbstractValue(ValVar/ObjVar/SVFVar): fetch values from SSA def sites - getAbstractState(vector<ValVar*/ObjVar*/SVFVar*>): build sparse states - sparseStatePropagate: orchestrates sparse fetch before node handling - Widen set: PreAnalysis computes variables defined in each loop body, ensuring all loop-modified variables participate in widening/narrowing - Fix IntervalValue operator<< signed overflow (1<<31 in 32-bit int) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent baf619b commit e676d5f

14 files changed

Lines changed: 845 additions & 86 deletions

svf/include/AE/Core/AbstractState.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,9 +208,15 @@ class AbstractState
208208
}
209209

210210
/// get abstract value of variable
211+
/// Returns a default (bottom) AbstractValue if varId is not in the map,
212+
/// which is safe for sparse abstract states that only store needed vars.
211213
inline virtual const AbstractValue &operator[](u32_t varId) const
212214
{
213-
return _varToAbsVal.at(varId);
215+
auto it = _varToAbsVal.find(varId);
216+
if (it != _varToAbsVal.end())
217+
return it->second;
218+
static AbstractValue defaultVal;
219+
return defaultVal;
214220
}
215221

216222
/// whether the variable is in varToAddrs table

svf/include/AE/Core/IntervalValue.h

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -898,23 +898,22 @@ inline IntervalValue operator<<(const IntervalValue &lhs, const IntervalValue &r
898898
if (shift.isBottom())
899899
return IntervalValue::bottom();
900900
BoundedInt lb = 0;
901-
// If the shift is greater than 32, the result is always 0
902-
if ((s32_t) shift.lb().getNumeral() >= 32 || shift.lb().is_infinity())
901+
if ((s64_t) shift.lb().getNumeral() >= 63 || shift.lb().is_infinity())
903902
{
904903
lb = IntervalValue::minus_infinity();
905904
}
906905
else
907906
{
908-
lb = (1 << (s32_t) shift.lb().getNumeral());
907+
lb = ((s64_t)1 << (s64_t) shift.lb().getNumeral());
909908
}
910909
BoundedInt ub = 0;
911-
if (shift.ub().is_infinity())
910+
if ((s64_t) shift.ub().getNumeral() >= 63 || shift.ub().is_infinity())
912911
{
913912
ub = IntervalValue::plus_infinity();
914913
}
915914
else
916915
{
917-
ub = (1 << (s32_t) shift.ub().getNumeral());
916+
ub = ((s64_t)1 << (s64_t) shift.ub().getNumeral());
918917
}
919918
IntervalValue coeff(lb, ub);
920919
return lhs * coeff;

svf/include/AE/Svfexe/AEDetector.h

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,15 @@ class AEDetector
7777
*/
7878
virtual void detect(AbstractState& as, const ICFGNode* node) = 0;
7979

80+
/**
81+
* @brief Returns additional variable IDs needed by this detector at the given node.
82+
* Used by sparse propagation to ensure the detector's needed vars are fetched.
83+
*/
84+
virtual Set<NodeID> getNeededVarsForSparse(const ICFGNode* node)
85+
{
86+
return {};
87+
}
88+
8089
/**
8190
* @brief Pure virtual function for handling stub external API calls. (e.g. UNSAFE_BUFACCESS)
8291
* @param call Pointer to the ext call ICFG node.
@@ -177,14 +186,18 @@ class BufOverflowDetector : public AEDetector
177186
* @param as Reference to the abstract state.
178187
* @param node Pointer to the ICFG node.
179188
*/
180-
void detect(AbstractState& as, const ICFGNode*);
189+
void detect(AbstractState& as, const ICFGNode*) override;
181190

191+
/**
192+
* @brief Returns additional variable IDs needed by the buffer overflow detector.
193+
*/
194+
Set<NodeID> getNeededVarsForSparse(const ICFGNode* node) override;
182195

183196
/**
184197
* @brief Handles external API calls related to buffer overflow detection.
185198
* @param call Pointer to the call ICFG node.
186199
*/
187-
void handleStubFunctions(const CallICFGNode*);
200+
void handleStubFunctions(const CallICFGNode*) override;
188201

189202
/**
190203
* @brief Adds an offset to a GEP object.
@@ -268,7 +281,7 @@ class BufOverflowDetector : public AEDetector
268281
/**
269282
* @brief Reports all detected buffer overflow bugs.
270283
*/
271-
void reportBug()
284+
void reportBug() override
272285
{
273286
if (!nodeToBugInfo.empty())
274287
{
@@ -348,13 +361,18 @@ class NullptrDerefDetector : public AEDetector
348361
* @param as Reference to the abstract state.
349362
* @param node Pointer to the ICFG node.
350363
*/
351-
void detect(AbstractState& as, const ICFGNode* node);
364+
void detect(AbstractState& as, const ICFGNode* node) override;
365+
366+
/**
367+
* @brief Returns additional variable IDs needed by the nullptr deref detector.
368+
*/
369+
Set<NodeID> getNeededVarsForSparse(const ICFGNode* node) override;
352370

353371
/**
354372
* @brief Handles external API calls related to nullptr dereferences.
355373
* @param call Pointer to the call ICFG node.
356374
*/
357-
void handleStubFunctions(const CallICFGNode* call);
375+
void handleStubFunctions(const CallICFGNode* call) override;
358376

359377
/**
360378
* @brief Checks if an Abstract Value is uninitialized.
@@ -401,7 +419,7 @@ class NullptrDerefDetector : public AEDetector
401419
/**
402420
* @brief Reports all detected nullptr dereference bugs.
403421
*/
404-
void reportBug()
422+
void reportBug() override
405423
{
406424
if (!nodeToBugInfo.empty())
407425
{

svf/include/AE/Svfexe/AbsExtAPI.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ class AbsExtAPI
7474
*/
7575
void handleExtAPI(const CallICFGNode *call);
7676

77+
/**
78+
* @brief Returns additional variable IDs needed by external API handling at the given node.
79+
* Used by sparse propagation to ensure ext API's needed vars are fetched.
80+
*/
81+
static Set<NodeID> getNeededVarsForSparse(const ICFGNode* node);
82+
7783
// --- Shared primitives used by string/memory handlers ---
7884

7985
/// Get the byte size of each element for a pointer/array variable.
@@ -106,7 +112,7 @@ class AbsExtAPI
106112
* @return Reference to the abstract state.
107113
* @throws Assertion if no trace exists for the node.
108114
*/
109-
AbstractState& getAbsStateFromTrace(const ICFGNode* node);
115+
AbstractState& getAbstractState(const ICFGNode* node);
110116

111117
protected:
112118
SVFIR* svfir; ///< Pointer to the SVF intermediate representation.

svf/include/AE/Svfexe/AbstractInterpretation.h

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "AE/Core/ICFGWTO.h"
3434
#include "AE/Svfexe/AEDetector.h"
3535
#include "AE/Svfexe/PreAnalysis.h"
36+
#include "AE/Svfexe/SparseDefUse.h"
3637
#include "AE/Svfexe/AbsExtAPI.h"
3738
#include "Util/SVFBugReport.h"
3839
#include "Util/SVFStat.h"
@@ -165,23 +166,11 @@ class AbstractInterpretation
165166

166167
Set<const CallICFGNode*> checkpoints; // for CI check
167168

168-
/**
169-
* @brief Retrieves the abstract state from the trace for a given ICFG node.
170-
* @param node Pointer to the ICFG node.
171-
* @return Reference to the abstract state.
172-
* @throws Assertion if no trace exists for the node.
173-
*/
174-
AbstractState& getAbsStateFromTrace(const ICFGNode* node)
169+
/// Get abstract state for an ICFG node
170+
AbstractState& getAbstractState(const ICFGNode* node)
175171
{
176-
if (abstractTrace.count(node) == 0)
177-
{
178-
assert(false && "No preAbsTrace for this node");
179-
abort();
180-
}
181-
else
182-
{
183-
return abstractTrace[node];
184-
}
172+
assert(abstractTrace.count(node) && "No abstract state for this node");
173+
return abstractTrace[node];
185174
}
186175

187176
private:
@@ -196,6 +185,47 @@ class AbstractInterpretation
196185
*/
197186
bool mergeStatesFromPredecessors(const ICFGNode * icfgNode);
198187

188+
/// Get the definition ICFG node of a top-level variable (sparse mode only)
189+
const ICFGNode* getDefICFGNode(const ValVar* var)
190+
{
191+
assert(Options::SparseAE() && "getDefICFGNode is only for sparse mode");
192+
return var->getICFGNode();
193+
}
194+
195+
/// Get the definition ICFG nodes of an address-taken variable (sparse mode only)
196+
const std::vector<const ICFGNode*>& getDefICFGNodes(const ObjVar* var)
197+
{
198+
assert(Options::SparseAE() && sparseDefUse && "getDefICFGNodes is only for sparse mode");
199+
return sparseDefUse->getDefICFGNodes(var->getId());
200+
}
201+
202+
/// Get top-level ValVars accessed at an ICFG node (sparse mode only)
203+
std::vector<const ValVar*> getValVars(const ICFGNode* node);
204+
205+
/// Get address-taken ObjVars accessed at an ICFG node via points-to (sparse mode only)
206+
std::vector<const ObjVar*> getObjVars(const ICFGNode* node);
207+
208+
/// Get all SVFVars (ValVars + ObjVars) accessed at an ICFG node (sparse mode only)
209+
std::vector<const SVFVar*> getSVFVars(const ICFGNode* node);
210+
211+
/// Retrieve abstract value at the definition site of a top-level variable
212+
AbstractValue getAbstractValue(const ValVar* var);
213+
214+
/// Retrieve abstract value at the definition sites of an address-taken variable (join all defs)
215+
AbstractValue getAbstractValue(const ObjVar* var);
216+
217+
/// Retrieve abstract value at the definition site(s) of an SVF variable (dispatches to ValVar/ObjVar)
218+
AbstractValue getAbstractValue(const SVFVar* var);
219+
220+
/// Build an AbstractState from definition sites of multiple top-level variables
221+
AbstractState getAbstractState(const std::vector<const ValVar*>& vars);
222+
223+
/// Build an AbstractState from definition sites of multiple address-taken variables
224+
AbstractState getAbstractState(const std::vector<const ObjVar*>& vars);
225+
226+
/// Build an AbstractState from definition sites of multiple SVF variables
227+
AbstractState getAbstractState(const std::vector<const SVFVar*>& vars);
228+
199229
/**
200230
* Check if execution state exist at the branch edge
201231
*
@@ -240,6 +270,9 @@ class AbstractInterpretation
240270
*/
241271
bool handleICFGNode(const ICFGNode* node);
242272

273+
/// Sparse state propagation: build minimal state from def sites
274+
bool sparseStatePropagate(const ICFGNode* icfgNode);
275+
243276
/**
244277
* Get the next nodes of a node within the same function
245278
*
@@ -323,9 +356,10 @@ class AbstractInterpretation
323356
AEStat* stat;
324357

325358
PreAnalysis* preAnalysis{nullptr};
359+
SparseDefUse* sparseDefUse{nullptr};
326360

327361

328-
bool hasAbsStateFromTrace(const ICFGNode* node)
362+
bool hasAbstractState(const ICFGNode* node)
329363
{
330364
return abstractTrace.count(node) != 0;
331365
}

svf/include/AE/Svfexe/PreAnalysis.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,20 +69,46 @@ class PreAnalysis
6969
/// Build WTO for each function using call graph SCC
7070
void initWTO();
7171

72+
/// Get points-to set for a variable from Andersen's analysis
73+
const PointsTo& getPts(NodeID id) const;
74+
7275
/// Accessors for WTO data
7376
const Map<const FunObjVar*, const ICFGWTO*>& getFuncToWTO() const
7477
{
7578
return funcToWTO;
7679
}
7780

81+
/// Build widen sets for all WTO cycles (call after initWTO)
82+
void buildWidenSets();
83+
84+
/// Get the set of variable IDs that need widening at a cycle head
85+
/// Includes both top-level ValVars and address-taken ObjVars defined in the loop body
86+
const Set<NodeID>& getWidenVars(const ICFGNode* cycleHead) const
87+
{
88+
auto it = cycleHeadToWidenVars.find(cycleHead);
89+
if (it != cycleHeadToWidenVars.end())
90+
return it->second;
91+
return emptyWidenSet;
92+
}
93+
7894
private:
95+
/// Collect all ICFG nodes in a WTO cycle (head + body, including nested cycles)
96+
void collectCycleNodes(const ICFGCycleWTO* cycle, std::vector<const ICFGNode*>& nodes);
97+
98+
/// Collect defined variables at an ICFG node (top-level defs + address-taken via pts)
99+
void collectDefsAtNode(const ICFGNode* node, Set<NodeID>& defs);
100+
101+
/// Build widen set for a single WTO cycle (recursive for nested cycles)
102+
void buildWidenSetForCycle(const ICFGCycleWTO* cycle);
79103
SVFIR* svfir;
80104
ICFG* icfg;
81105
AndersenWaveDiff* pta;
82106
CallGraph* callGraph;
83107
CallGraphSCC* callGraphSCC;
84108

85109
Map<const FunObjVar*, const ICFGWTO*> funcToWTO;
110+
Map<const ICFGNode*, Set<NodeID>> cycleHeadToWidenVars;
111+
static const Set<NodeID> emptyWidenSet;
86112
};
87113

88114
} // End namespace SVF
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//===- SparseDefUse.h -- Sparse Def-Use Table for Abstract Interpretation-//
2+
//
3+
// SVF: Static Value-Flow Analysis
4+
//
5+
// Copyright (C) <2013-> <Yulei Sui>
6+
//
7+
8+
// This program is free software: you can redistribute it and/or modify
9+
// it under the terms of the GNU Affero General Public License as published by
10+
// the Free Software Foundation, either version 3 of the License, or
11+
// (at your option) any later version.
12+
13+
// This program is distributed in the hope that it will be useful,
14+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
// GNU Affero General Public License for more details.
17+
18+
// You should have received a copy of the GNU Affero General Public License
19+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
20+
//
21+
//===----------------------------------------------------------------------===//
22+
23+
/*
24+
* SparseDefUse.h
25+
*
26+
* Created on: Feb 9, 2026
27+
* Author: Jiawei Wang
28+
*
29+
* Simple def-use table for address-taken objects.
30+
* Maps each ObjVar to the ICFG nodes that store to it (via StoreStmt).
31+
* Top-level variables don't need this table since ValVar::getICFGNode()
32+
* gives their single definition point in SSA form.
33+
*/
34+
35+
#ifndef INCLUDE_AE_SVFEXE_SPARSEDEFUSE_H_
36+
#define INCLUDE_AE_SVFEXE_SPARSEDEFUSE_H_
37+
38+
#include "SVFIR/SVFIR.h"
39+
#include "Graphs/ICFG.h"
40+
#include "MemoryModel/PointerAnalysis.h"
41+
42+
namespace SVF
43+
{
44+
45+
class SparseDefUse
46+
{
47+
public:
48+
SparseDefUse(PointerAnalysis* pta)
49+
: pta(pta) {}
50+
51+
~SparseDefUse() = default;
52+
53+
/// Build the table by scanning all ICFG nodes for StoreStmts
54+
void build(ICFG* icfg);
55+
56+
/// Get the ICFG nodes that may define an ObjVar (via store)
57+
const std::vector<const ICFGNode*>& getDefICFGNodes(NodeID objId) const
58+
{
59+
auto it = objToDefNodes.find(objId);
60+
if (it != objToDefNodes.end())
61+
return it->second;
62+
return emptyVec;
63+
}
64+
65+
private:
66+
PointerAnalysis* pta;
67+
Map<NodeID, std::vector<const ICFGNode*>> objToDefNodes;
68+
static const std::vector<const ICFGNode*> emptyVec;
69+
};
70+
71+
} // End namespace SVF
72+
73+
#endif /* INCLUDE_AE_SVFEXE_SPARSEDEFUSE_H_ */

svf/include/Util/Options.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,8 @@ class Options
243243
static const Option<u32_t> WidenDelay;
244244
/// recursion handling mode, Default: TOP
245245
static const OptionMap<u32_t> HandleRecur;
246+
/// Enable sparse state propagation using Use-Def table
247+
static const Option<bool> SparseAE;
246248
/// the max time consumptions (seconds). Default: 4 hours 14400s
247249
static const Option<u32_t> Timeout;
248250
/// bug info output file, Default: output.db

0 commit comments

Comments
 (0)