Static Value-Flow Analysis
Loading...
Searching...
No Matches
SparseAbstractInterpretation.cpp
Go to the documentation of this file.
1//===- SparseAbstractInterpretation.cpp -- Sparse Abstract Execution----//
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
24#include "AE/Svfexe/AEWTO.h"
25#include "SVFIR/SVFIR.h"
26#include "Graphs/SVFG.h"
27#include "MSSA/SVFGBuilder.h"
28#include "WPA/Andersen.h"
29
30using namespace SVF;
31
32// FullSparse input assembly is kept together below. Shared SSA def-site
33// access and WTO gather/scatter retain their existing implementations.
34
35// =====================================================================
36// Full-sparse — class lifecycle + SVFG construction.
37// =====================================================================
38
40
42{
43 svfgBuilder = std::make_unique<SVFGBuilder>(true);
45}
46
53
55 const AbstractState& src, const NodeBS& objs)
56{
57 for (NodeID obj : objs)
58 {
60 const auto value = src.getLocToVal().find(src.getIDFromAddr(addr));
61 if (value != src.getLocToVal().end())
62 {
63 if (dst.getLocToVal().count(dst.getIDFromAddr(addr)))
64 dst.load(addr).join_with(value->second);
65 else
66 dst.store(addr, value->second);
67 }
68 }
69}
70
72 const ICFGNode* node)
73{
74 // An empty SVFG input is not proof of unreachability. Reuse the existing
75 // control gate and mergeFreedAddrs, not a new solver.
76 const bool reachable =
78 if (reachable)
79 {
81 for (const VFGNode* target : node->getVFGNodes())
82 {
83 for (auto it = target->InEdgeBegin(); it != target->InEdgeEnd(); ++it)
84 {
85 const VFGEdge* edge = *it;
86 const ICFGNode* source = edge->getSrcNode()->getICFGNode();
87 if (source == node)
88 {
89 // Dependencies inside one ICFG node are handled by its
90 // statement execution, not by reading its old output.
91 }
92 else if (!source || !hasAbsState(source))
93 {
94 // This direct predecessor has no available output yet.
95 }
96 else
97 {
98 // Step 1. Read this predecessor's current edge values.
101 // Step 2. JOIN the contributions of feasible, refined paths.
103 // Step 3. Merge this contribution, never the whole source state.
105 }
106 }
107 }
108 updateAbsState(node, merged);
109 }
110 return reachable;
111}
112
114 const VFGEdge* edge, NodeBS& objs)
115{
117 objs.clear();
118 if (const auto* indirect = SVFUtil::dyn_cast<IndirectSVFGEdge>(edge))
119 {
120 for (NodeID obj : indirect->getPointsTo())
122 const ICFGNode* source = edge->getSrcNode()->getICFGNode();
124 }
125 return input;
126}
127
128// Step 2: filter one SVFG input D -> N along ICFG paths.
129// D/N label the source/target ICFG nodes; B labels a branch node.
130// Snippets below are simplified C. Interval annotations describe the already
131// computed AE values, not assignments executed by this search. For narrowing
132// examples, x is a memory Obj loaded through a pointer with one known target.
134 const VFGEdge* edge, const AbstractState& input)
135{
136 const ICFGNode* source = edge->getSrcNode()->getICFGNode();
137 const ICFGNode* target = edge->getDstNode()->getICFGNode();
138 NodeBS objs;
139 if (const auto* indirect = SVFUtil::dyn_cast<IndirectSVFGEdge>(edge))
140 {
141 for (NodeID obj : indirect->getPointsTo())
143 }
145 if (input.getLocToVal().empty() && objs.empty())
146 {
147 // No values or queried Objs. An indirect edge still identifies its
148 // base/sub Objs even when D has not produced their values yet.
149 // this branch will return empty result
150 }
151 else if (!edge->isIntraVFGEdge() || source->getFun() != target->getFun() ||
152 !source->getFun())
153 {
154 // Call input:
155 // f(&x); // D: ActualIn in caller
156 // void f(int *p) { ... } // N: FormalIn at callee entry
157 // Call output:
158 // void f(int *p) { *p = 7; } // D: FormalOut at callee exit
159 // f(&x); // N: ActualOut at caller return point
160 // Recursive input (same function, but a non-intra SVFG edge):
161 // void f(int *p) { f(p); } // D: call ActualIn -> N: entry FormalIn
162 // Global initialization:
163 // int g = 7; // D: global node, no owning function
164 // void f() { use(g); } // N
165 // All retain D's input; no cross-procedural feasibility is proved.
166 // In one word, if edge is interprocedural, we reckon it feasible, no filter applies.
167 result = input;
168 }
169 else
170 {
171 // int x = input(); // D: x=[0,10]
172 // ...
173 // if (x > 5) { // B
174 // ...
175 // use(x); // N
176 // }
177 // Same function: search D -> B -> ... -> N, carrying x=[6,10]
178 // on B's true edge. More branches give D -> B1 -> ... -> Bk -> N.
179 // incoming's key is the iterated ICFGNode in worklist
180 // incoming's value is initialized as AbstractState pulled from SVFG predecessor
181 // the AbstractState only change some interval values at branches (branch refinement)
185 worklist.push(source);
186 while (!worklist.empty())
187 {
188 const ICFGNode* current = worklist.pop();
189 if (current == target)
190 {
191 // int x = read_int(); // D: x=[-10,10]
192 // if (x > 0) { // B: true edge carries x=[1,10]
193 // use(x); // N: current == target
194 // }
195 // JOIN incoming[N] into this D -> N query's result. Stop at
196 // N: do not inspect or follow any of N's outgoing edges.
197 result.joinWith(incoming[current]);
198 }
199 else
200 {
201 // int a[2] = {0,1} // D: a[0] = [0,0], a[1] = [1,1]
202 // a[1] = 2 // S: a[1] = [2,2]
203 // use(a[1]) // N: if an input carries D's old a[1],
204 // its D -> S -> N path reuses S's result when strongly updated.
205 // An input from S already has a[1]=2; S itself is not replayed.
206
207 AbstractState output = incoming[current];
208 if (current != source && hasAbsState(current))
209 {
210 const AbstractState& state = getAbsState(current);
211 for (const SVFStmt* stmt : current->getSVFStmts())
212 {
213 if (const auto* store = SVFUtil::dyn_cast<StoreStmt>(stmt))
214 {
215 const AddressValue& targets =
216 getAbsValue(store->getLHSVar(), current).getAddrs();
218 for (NodeID obj : objs)
219 {
221 if (targets.contains(addr) &&
222 state.getIDFromAddr(addr) == obj && state.getLocToVal().count(obj))
223 {
224 // D: a[9]=9; p=a+9; S: *p=10; N: use(a[9]).
225 // Static GEP mapping may leave D -> N, not S -> N.
226 // At S, AE identifies the carried sub-Obj a[9].
227 // Reuse S's computed value; do not execute S again.
228 if (strongUpdate)
229 output.store(addr, state.load(addr));
230 else
231 output.load(addr).join_with(state.load(addr));
232 }
233 }
234 }
235 }
236 }
237 std::vector<std::pair<const ICFGNode*, const IntraCFGEdge*>> successors;
238 if (const auto* call = SVFUtil::dyn_cast<CallICFGNode>(current))
239 {
240 const RetICFGNode* retNode = call->getRetICFGNode();
241 if (call->getCalledFunction() &&
242 SVFUtil::isExtCall(call->getCalledFunction()))
243 {
244 // D: x=0; C: a model writes x=5; N: use(x).
245 // Read the queried Objs from C's already computed
246 // output, including x=5 even when input contains x=0.
247 // C may also be D, e.g. memset(a, 7, 1) -> use(a[0]);
248 // read its model output rather than skipping the call.
249 // This is a projection of C's output, not proof that
250 // C wrote every present Obj. A joined output may be
251 // less precise than this query's path-specific values.
252 if (hasAbsState(current))
253 {
254 const AbstractState& state = getAbsState(current);
255 for (NodeID obj : objs)
256 {
258 if (state.getIDFromAddr(addr) == obj && state.getLocToVal().count(obj))
259 {
260 output.store(addr, state.load(addr));
261 }
262 }
263 }
264 }
265 else
266 {
267 // Ordinary and recursive calls use the same Obj return
268 // inputs. void f(int *p) { *p=7; } f(&x); returns x=7
269 // as an Obj value. Do not execute the callee again.
270 // INTERVAL_test_16-0.c: main starts i in [0,5].
271 // void foo(int* i) {
272 // *i = *i + 1; // D: i=[1,10] at fixpoint
273 // if (*i < 10) {
274 // foo(i); // C: carried i=[1,9]
275 // // R: callee output i=[10,10]
276 // }
277 // } // N: merge both branches and return
278 // Unlike a loop exit, C -> R does not test i again.
279 // The callee's exit condition is already reflected in
280 // its Obj output. Read that output before continuing
281 // at R, without searching or executing the callee body.
284 // R may still contain a copy of C's pre-call state.
285 // Read R's indirect SVFG predecessors, not State[R].
286 for (const VFGNode* retSVFGNode : retNode->getVFGNodes())
287 {
288 for (const VFGEdge* retEdge : retSVFGNode->getInEdges())
289 {
290 if (const auto* indirect = SVFUtil::dyn_cast<IndirectSVFGEdge>(retEdge))
291 {
293 for (NodeID obj : indirect->getPointsTo())
296 const ICFGNode* calleeOutputNode = retEdge->getSrcNode()->getICFGNode();
299 }
300 }
301 }
302 // Change only queried Objs identified by the return
303 // edges; all other carried values stay unchanged.
304 for (NodeID obj : objs)
305 {
306 if (calleeOutputObjs.test(obj))
307 {
309 if (calleeOutput.getLocToVal().count(obj))
310 {
311 // Replace pre-call [1,9] with returned 10.
312 // Do not JOIN or intersect with the old value:
313 // the call may have changed this Obj.
314 output.store(addr, calleeOutput.load(addr));
315 }
316 else
317 {
318 // The return edge exists, but no Obj value
319 // is available yet. Contribute bottom until
320 // a later WTO iteration supplies the output,
321 // not the pre-call value as a guessed return.
322 output.store(addr, AbstractValue());
323 }
324 }
325 }
326 }
327 // Continue the caller-side D -> C -> R -> ... -> N path
328 // with the Obj outputs collected above. Do not enter or
329 // execute the callee again in this path query.
330 // Whether f can return is not proved here (also conservative
331 // for abort()).
332 successors.emplace_back(retNode, nullptr);
333 }
334 else
335 {
336 // int x = 1; // D
337 // int y = 2; // ordinary node: keep carried x=1
338 // x = 9; // S: still keep D's carried x=1
339 // use(x); // N: may also receive x=9 from S
340 // Follow intra edges only. Do not execute y=2 or x=9;
341 // do not infer that S kills D's contribution.
342 for (const ICFGEdge* out : current->getOutEdges())
343 {
344 if (const auto* intra = SVFUtil::dyn_cast<IntraCFGEdge>(out))
345 successors.emplace_back(intra->getDstNode(), intra);
346 }
347 }
348
349 for (const auto& [next, branch] : successors)
350 {
352 bool feasible = true;
353 if (!next || next->getFun() != source->getFun())
354 {
355 // Structural boundary (not a C-level branch test):
356 // D -> call f -> [missing caller return point]
357 // D[in f] -> next[in g] -> ... -> N[in f]
358 // next is missing or outside D's function. Do not
359 // extend this intra-procedural search through that hop.
360 feasible = false;
361 }
362 else if (!branch || !branch->getCondition() || !hasAbsState(current))
363 {
364 // Unconditional edge:
365 // int x = input(); // D
366 // int y = 2; // M: no branch condition
367 // use(x); // N
368 // Caller-side hop:
369 // D: ...; f(); ...; use(x); // N
370 // Branch state not available yet:
371 // D: ...; if (c) { ...; use(x); } // B -> N
372 // State[B] is absent in this iteration.
373 // Keep the input unchanged: none proves the hop false.
374 }
375 else
376 {
377 // int x = input(); // D
378 // if (x > 5) { // B: an AE state already exists
379 // use(x); // N
380 // }
381 // Read B's existing Boolean result to check its edge;
382 // narrow only this path's copy of D's input below.
385 // int x = 0; // D
386 // if (x > 5) { // B: condition=[0,0]
387 // use(x); // N: true edge requires 1
388 // }
389 // [0,0] cannot take the true edge: reject this hop.
390 feasible = false;
391 else
392 // int x = input(); // D: x=[0,10]
393 // if (x > 5) { // B: condition=[0,1]
394 // use(x); // N: keep true, carry x=[6,10]
395 // }
396 // Nested constraints can reject a carried input:
397 // if (x <= 5) { // B1: candidate x=[0,5]
398 // if (x > 5) { use(x); } // B2 -> N
399 // }
400 // Even if State[B2] still admits true, intersecting
401 // candidate [0,5] with [6,10] gives bottom. Other
402 // unsupported constraints conservatively keep input.
404 }
405
406 // The two feasible-arrival cases below use one diamond:
407 // int x = read_int(); // D: x=[-10,10]
408 // if (x > 0) { then_work(); } // B: true carries [1,10]
409 // else { else_work(); } // false carries [-10,0]
410 // work(); // M (= next), merge point
411 // use(x); // N
412 //
413 // D -> B
414 // true / \ false
415 // [1,10] [-10,0]
416 // \ /
417 // M -> N
418 // "First" means search arrival order, not program order.
419 // Either route can arrive first; the final JOIN is the same.
420 if (!feasible)
421 {
422 // int x = 0; // D
423 // if (x > 5) { ...; }
424 // use(x); // N is after the if
425 // Drop the true route only; the false route can still
426 // reach N. Other SVFG predecessors are not rejected.
427 }
428 else if (!incoming.count(next))
429 {
430 // Diamond above, assuming the true route arrives first:
431 // incoming[M] absent, candidate=[1,10]
432 // -> save incoming[M]=[1,10] and queue M.
433 // The false route has not contributed yet. This is
434 // the same initialization used for any first visit.
436 worklist.push(next);
437 }
438 else
439 {
440 // Same diamond, now the false route arrives at M:
441 // incoming[M]=[1,10], candidate=[-10,0]
442 // -> JOIN gives [-10,10]. M need not have been
443 // processed yet; its first input is already saved.
444 // Both routes started at D, not two different SVFG defs.
447 // Another repeated-arrival pattern is a loop back edge:
448 // int x = read_int(); // D: x=[-10,10]
449 // while (again()) {} // M: head / back edge
450 // use(x); // N
451 // Existing [-10,10] JOIN back-edge [-10,10] is unchanged:
452 // skip the if below and do not queue M again.
453 if (joined != incoming[next])
454 {
455 // Diamond: [1,10] -> [-10,10] enlarged M's input.
456 // Queue M so the added values can also reach N.
457 incoming[next] = std::move(joined);
458 worklist.push(next);
459 }
460 }
461 }
462 }
463 }
464 }
465 return result;
466}
467
469 const ICFGCycleWTO* cycle)
470{
471 // Start from the dense snapshot (ObjVars + any ValVars that happen to
472 // be cached at cycle_head's trace entry).
474
476 if (valVars.empty())
477 return snap; // no cycle ValVars known: nothing to pull
478
479 // Drop stale ValVar entries and pull each cycle ValVar from its
480 // def-site. ValVars without a genuine stored value are skipped to
481 // avoid getAbsValue's top-fallback contaminating body def-sites on
482 // the subsequent widen/narrow scatter.
483 snap.clearValVars();
484 for (const ValVar* v : valVars)
485 {
486 const ICFGNode* defSite = v->getICFGNode();
487 if (!defSite || !hasAbsValue(v, defSite))
488 continue;
489 snap[v->getId()] = getAbsValue(v, defSite);
490 }
491 return snap;
492}
493
495 const AbstractState& prev, const AbstractState& cur, const ICFGCycleWTO* cycle)
496{
497 // Base widens, writes trace[cycle_head], and returns fixpoint bool.
499
500 // Scatter the widened ValVars back to their def-sites so body nodes
501 // observe the widened values on the next iteration. Matches the
502 // pre-refactor semantics: scatter unconditionally, including at
503 // widening fixpoint (see the narrowing-starts-with-stale-body issue
504 // fixed by always writing widened state back).
505 const ICFGNode* cycle_head = cycle->head()->getICFGNode();
507 for (const auto& [id, val] : next.getVarToVal())
509 return fixpoint;
510}
511
513 const AbstractState& prev, const AbstractState& cur, const ICFGCycleWTO* cycle)
514{
515 // Delegate to base. It returns true on the two non-scatter cases
516 // (narrowing disabled, or narrow fixpoint); we preserve the original
517 // "skip scatter at fixpoint" semantics by bailing early here.
519 if (fixpoint)
520 return true;
521
522 // Non-fixpoint: base wrote the narrowed state to trace. Scatter the
523 // narrowed ValVars back to def-sites.
524 const ICFGNode* cycle_head = cycle->head()->getICFGNode();
526 for (const auto& [id, val] : next.getVarToVal())
528 return false;
529}
530
531// =====================================================================
532// Shared SemiSparse/FullSparse state-access overrides. Both use the
533// established ValVar def-site reads and writes below.
534// =====================================================================
535
537 const ICFGNode* node, const AbstractState& state)
538{
539 // Only replace ObjVar state. ValVars live at their def-sites and
540 // must not be overwritten when the predecessor's state is merged in.
542}
543
545 const AbstractState& src)
546{
547 // ValVars live at def-sites in semi-sparse mode; they don't flow
548 // through state merges. Iterate src's ObjVar (_addrToAbsVal) entries
549 // directly and join into dst, leaving dst's ValVar map untouched.
550 // _freedAddrs (used by the null-deref detector) also rides along
551 // ICFG edges — there is no SVFG-level encoding of free events.
552 for (const auto& [id, val] : src.getLocToVal())
553 {
555 if (dst.getLocToVal().count(id))
556 dst.load(addr).join_with(val);
557 else
558 dst.store(addr, val);
559 }
560 for (NodeID a : src.getFreedAddrs())
561 dst.addToFreedAddrs(a);
562}
563
565 const ValVar* var) const
566{
567 // const ValVars are all defined in global node
568 if (!var->getICFGNode())
569 {
570 return svfir->getICFG()->getGlobalICFGNode();
571 }
572 // for return value of callsite, use the ret-site as def-site
573 else if (SVFUtil::isa<CallICFGNode>(var->getICFGNode()) &&
574 SVFUtil::isa<RetValPN>(var))
575 {
576 return SVFUtil::dyn_cast<CallICFGNode>(var->getICFGNode())
577 ->getRetICFGNode();
578 }
579 // for other ValVars, use their def-site as the node to query abstract
580 // value.
581 else
582 {
583 return var->getICFGNode();
584 }
585}
586
588 const AbstractValue& val,
589 const ICFGNode* node)
590{
591 // Write to the var's def-site so getAbsValue stays consistent.
592 const ICFGNode* defNode = var->getICFGNode();
593 abstractTrace[defNode ? defNode : node][var->getId()] = val;
594}
595
597 const ValVar* var, const ICFGNode* node)
598{
599 // Read from the var's def-site (where updateAbsValue wrote it).
601}
602
item next
Definition cJSON.cpp:2224
cJSON * a
Definition cJSON.cpp:2560
newitem prev
Definition cJSON.cpp:2285
AndersenWaveDiff * getPointerAnalysis() const
Accessors for Andersen's results.
Definition AEWTO.h:56
const Set< const ValVar * > getCycleValVars(const ICFGCycleWTO *cycle) const
Definition AEWTO.h:83
AbstractState & getAbsState(const ICFGNode *node)
virtual bool narrowCycleState(const AbstractState &prev, const AbstractState &cur, const ICFGCycleWTO *cycle)
bool isStrongUpdate(const AddressValue &targets, const AbstractState &state) const
virtual bool hasAbsValue(const ValVar *var, const ICFGNode *node) const
Side-effect-free existence check.
virtual AbstractState getFullCycleHeadState(const ICFGCycleWTO *cycle)
bool isBranchEdgeFeasible(const IntraCFGEdge *edge, AbstractState &as)
virtual bool widenCycleState(const AbstractState &prev, const AbstractState &cur, const ICFGCycleWTO *cycle)
bool applyBranchRefinement(const IntraCFGEdge *edge, AbstractState &as)
virtual bool mergeStatesFromPredecessors(const ICFGNode *node)
SVFIR * svfir
Data and helpers reachable from SparseAbstractInterpretation.
virtual const AbstractValue & getAbsValue(const ValVar *var, const ICFGNode *node)
bool hasAbsState(const ICFGNode *node)
Map< const ICFGNode *, AbstractState > abstractTrace
per-node trace; owned here
const AddrToAbsValMap & getLocToVal() const
get loc2val map
u32_t getIDFromAddr(u32_t addr) const
Return the internal index if addr is an address otherwise return the value of idx.
void store(u32_t addr, const AbstractValue &val)
void joinWith(const AbstractState &other)
domain join with other, important! other widen this.
const Set< NodeID > & getFreedAddrs() const
virtual AbstractValue & load(u32_t addr)
static u32_t getVirtualMemAddress(u32_t idx)
The physical address starts with 0x7f...... + idx.
void addToFreedAddrs(NodeID addr)
void updateAddrStateOnly(const AbstractState &other)
Replace address-taken (ObjVar) state with other's, preserving ValVar state.
void join_with(const AbstractValue &other)
AddressValue & getAddrs()
bool push(const Data &data)
Definition WorkList.h:180
bool empty() const
Definition WorkList.h:161
void joinStates(AbstractState &dst, const AbstractState &src, const NodeBS &objs)
bool mergeStatesFromPredecessors(const ICFGNode *node) override
AbstractState filterValueFlowInputByPath(const VFGEdge *edge, const AbstractState &input)
AbstractState CollectObjValueFlowInput(const VFGEdge *edge, NodeBS &objs)
void mergeFreedAddrs(AbstractState &dst, const AbstractState &src)
const GEdgeSetTy & getOutEdges() const
virtual const FunObjVar * getFun() const
Return the function of this ICFGNode.
Definition ICFGNode.h:75
const VFGNodeList & getVFGNodes() const
Definition ICFGNode.h:103
const SVFStmtList & getSVFStmts() const
Definition ICFGNode.h:116
GlobalICFGNode * getGlobalICFGNode() const
Definition ICFG.h:244
const BaseObjVar * getBaseObject(NodeID id) const
Definition SVFIR.h:499
ICFG * getICFG() const
Definition SVFIR.h:232
NodeBS & getAllFieldsObjVars(const BaseObjVar *obj)
Get all fields of an object.
Definition SVFIR.cpp:573
const SVFVar * getSVFVar(NodeID id) const
ObjVar/GepObjVar/BaseObjVar.
Definition SVFIR.h:136
bool narrowCycleState(const AbstractState &prev, const AbstractState &cur, const ICFGCycleWTO *cycle) override
const AbstractValue & getAbsValue(const ValVar *var, const ICFGNode *node) override
AbstractState getFullCycleHeadState(const ICFGCycleWTO *cycle) override
void joinStates(AbstractState &dst, const AbstractState &src) override
bool widenCycleState(const AbstractState &prev, const AbstractState &cur, const ICFGCycleWTO *cycle) override
void updateAbsValue(const ValVar *var, const AbstractValue &val, const ICFGNode *node) override
bool hasAbsValue(const ValVar *var, const ICFGNode *node) const override
Side-effect-free existence check.
void updateAbsState(const ICFGNode *node, const AbstractState &state) override
const ICFGNode * getICFGNode(const ValVar *var) const
bool isExtCall(const FunObjVar *fun)
Definition SVFUtil.cpp:526
for isBitcode
Definition BasicTypes.h:70
u32_t NodeID
Definition GeneralType.h:76
llvm::IRBuilder IRBuilder
Definition BasicTypes.h:76
unsigned u32_t
Definition GeneralType.h:67