svf-tools 1.0.1209 → 1.0.1211

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svf-tools",
3
- "version": "1.0.1209",
3
+ "version": "1.0.1211",
4
4
  "description": "* <b>[TypeClone](https://github.com/SVF-tools/SVF/wiki/TypeClone) published in our [ECOOP paper](https://yuleisui.github.io/publications/ecoop20.pdf) is now available in SVF </b> * <b>SVF now uses a single script for its build. Just type [`source ./build.sh`](https://github.com/SVF-tools/SVF/blob/master/build.sh) in your terminal, that's it!</b> * <b>SVF now supports LLVM-10.0.0! </b> * <b>We thank [bsauce](https://github.com/bsauce) for writing a user manual of SVF ([link1](https://www.jianshu.com/p/068a08ec749c) and [link2](https://www.jianshu.com/p/777c30d4240e)) in Chinese </b> * <b>SVF now supports LLVM-9.0.0 (Thank [Byoungyoung Lee](https://github.com/SVF-tools/SVF/issues/142) for his help!). </b> * <b>SVF now supports a set of [field-sensitive pointer analyses](https://yuleisui.github.io/publications/sas2019a.pdf). </b> * <b>[Use SVF as an external lib](https://github.com/SVF-tools/SVF/wiki/Using-SVF-as-a-lib-in-your-own-tool) for your own project (Contributed by [Hongxu Chen](https://github.com/HongxuChen)). </b> * <b>SVF now supports LLVM-7.0.0. </b> * <b>SVF now supports Docker. [Try SVF in Docker](https://github.com/SVF-tools/SVF/wiki/Try-SVF-in-Docker)! </b> * <b>SVF now supports [LLVM-6.0.0](https://github.com/svf-tools/SVF/pull/38) (Contributed by [Jack Anthony](https://github.com/jackanth)). </b> * <b>SVF now supports [LLVM-4.0.0](https://github.com/svf-tools/SVF/pull/23) (Contributed by Jared Carlson. Thank [Jared](https://github.com/jcarlson23) and [Will](https://github.com/dtzWill) for their in-depth [discussions](https://github.com/svf-tools/SVF/pull/18) about updating SVF!) </b> * <b>SVF now supports analysis for C++ programs.</b> <br />",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -138,15 +138,13 @@ private:
138
138
  /// Initialize abstract state for the global ICFG node and process global statements
139
139
  virtual void handleGlobalNode();
140
140
 
141
- /// Merge abstract states from predecessor nodes; return true if icfgNode has feasible incoming state
142
- bool mergeStatesFromPredecessors(const ICFGNode * icfgNode);
141
+ /// Propagate the post-state of a node to all its intra-procedural successors
142
+ void propagateToSuccessor(const ICFGNode* node,
143
+ const Set<const ICFGNode*>* withinSet = nullptr);
143
144
 
144
145
  /// Check if the branch on intraEdge is feasible under abstract state as
145
146
  bool isBranchFeasible(const IntraCFGEdge* intraEdge, AbstractState& as);
146
147
 
147
- /// Process all SVF statements in a singleton WTO component (single basic block)
148
- virtual void handleSingletonWTO(const ICFGSingletonWTO *icfgSingletonWto);
149
-
150
148
  /// Handle a call site node: dispatch to ext-call, direct-call, or indirect-call handling
151
149
  virtual void handleCallSite(const ICFGNode* node);
152
150
 
@@ -156,15 +154,9 @@ private:
156
154
  /// Handle a function body via worklist-driven WTO traversal starting from funEntry
157
155
  void handleFunction(const ICFGNode* funEntry, const CallICFGNode* caller = nullptr);
158
156
 
159
- /// Merge predecessor states, process statements and callsites; return true if state changed
157
+ /// Handle an ICFG node: execute statements; return true if state changed
160
158
  bool handleICFGNode(const ICFGNode* node);
161
159
 
162
- /// Get intra-procedural successor nodes (including call-to-ret shortcut) within the same function
163
- std::vector<const ICFGNode*> getNextNodes(const ICFGNode* node) const;
164
-
165
- /// Get successor nodes that exit the given WTO cycle (skipping inner sub-cycles)
166
- std::vector<const ICFGNode*> getNextNodesOfCycle(const ICFGCycleWTO* cycle) const;
167
-
168
160
  /// Dispatch an SVF statement (Addr/Binary/Cmp/Load/Store/Copy/Gep/Select/Phi/Call/Ret) to its handler
169
161
  virtual void handleSVFStatement(const SVFStmt* stmt);
170
162
 
@@ -232,7 +224,7 @@ private:
232
224
  // there data should be shared with subclasses
233
225
  Map<std::string, std::function<void(const CallICFGNode*)>> func_map;
234
226
 
235
- Map<const ICFGNode*, AbstractState> abstractTrace; // abstract states immediately after nodes
227
+ Map<const ICFGNode*, AbstractState> abstractTrace; // abstract states for nodes (pre-state before execution, post-state after)
236
228
  Set<const ICFGNode*> allAnalyzedNodes; // All nodes ever analyzed (across all entry points)
237
229
  std::string moduleName;
238
230
 
@@ -255,76 +255,60 @@ void AbstractInterpretation::handleGlobalNode()
255
255
  AddressValue(BlackHoleObjAddr);
256
256
  }
257
257
 
258
- /// get execution state by merging states of predecessor blocks
259
- /// Scenario 1: preblock -----(intraEdge)----> block, join the preES of inEdges
260
- /// Scenario 2: preblock -----(callEdge)----> block
261
- bool AbstractInterpretation::mergeStatesFromPredecessors(const ICFGNode * icfgNode)
258
+ /// Propagate the post-state of a node to all its intra-procedural successors.
259
+ /// For conditional branches, the state is refined along each outgoing edge via
260
+ /// isBranchFeasible. The refined state is joined into the successor's abstractTrace entry.
261
+ /// Call/return edges are handled explicitly by handleFunCall, not here.
262
+ ///
263
+ /// If withinSet is non-null, only propagate to successors contained in that set.
264
+ /// This is used during cycle iteration to avoid propagating to nodes outside the
265
+ /// cycle, which would cause stale accumulation and lose narrowing precision.
266
+ /// Additionally, when withinSet is provided and a RetCFGEdge target is within the set,
267
+ /// the state is also propagated along that edge. This handles the FunExit -> RetNode
268
+ /// path inside recursive function cycles, where the recursive call is skipped and
269
+ /// the RetCFGEdge effectively acts as intra-procedural control flow.
270
+ void AbstractInterpretation::propagateToSuccessor(const ICFGNode* node,
271
+ const Set<const ICFGNode*>* withinSet)
262
272
  {
263
- std::vector<AbstractState> workList;
264
- AbstractState preAs;
265
- for (auto& edge: icfgNode->getInEdges())
273
+ if (!hasAbsStateFromTrace(node))
274
+ return;
275
+
276
+ for (auto& edge : node->getOutEdges())
266
277
  {
267
- if (abstractTrace.find(edge->getSrcNode()) != abstractTrace.end())
278
+ if (const IntraCFGEdge* intraEdge =
279
+ SVFUtil::dyn_cast<IntraCFGEdge>(edge))
268
280
  {
281
+ const ICFGNode* dst = intraEdge->getDstNode();
282
+ // If a filter set is provided, skip successors not in the set
283
+ if (withinSet && withinSet->find(dst) == withinSet->end())
284
+ continue;
269
285
 
270
- if (const IntraCFGEdge *intraCfgEdge =
271
- SVFUtil::dyn_cast<IntraCFGEdge>(edge))
272
- {
273
- AbstractState tmpEs = abstractTrace[edge->getSrcNode()];
274
- if (intraCfgEdge->getCondition())
275
- {
276
- if (isBranchFeasible(intraCfgEdge, tmpEs))
277
- {
278
- workList.push_back(tmpEs);
279
- }
280
- else
281
- {
282
- // do nothing
283
- }
284
- }
285
- else
286
- {
287
- workList.push_back(tmpEs);
288
- }
289
- }
290
- else if (const CallCFGEdge *callCfgEdge =
291
- SVFUtil::dyn_cast<CallCFGEdge>(edge))
292
- {
293
-
294
- // context insensitive implementation
295
- workList.push_back(
296
- abstractTrace[callCfgEdge->getSrcNode()]);
297
- }
298
- else if (const RetCFGEdge *retCfgEdge =
299
- SVFUtil::dyn_cast<RetCFGEdge>(edge))
286
+ AbstractState state = abstractTrace[node];
287
+ if (intraEdge->getCondition())
300
288
  {
301
- // Only include return edge if the corresponding callsite was processed
302
- // (skipped recursive callsites in WIDEN_ONLY/WIDEN_NARROW won't have state)
303
- const RetICFGNode* retNode = SVFUtil::dyn_cast<RetICFGNode>(icfgNode);
304
- if (hasAbsStateFromTrace(retNode->getCallICFGNode()))
305
- {
306
- workList.push_back(abstractTrace[retCfgEdge->getSrcNode()]);
307
- }
289
+ if (!isBranchFeasible(intraEdge, state))
290
+ continue; // infeasible branch, do not propagate
291
+ // state has been refined in-place by isBranchFeasible
308
292
  }
293
+ if (hasAbsStateFromTrace(dst))
294
+ abstractTrace[dst].joinWith(state);
309
295
  else
310
- assert(false && "Unhandled ICFGEdge type encountered!");
296
+ abstractTrace[dst] = state;
311
297
  }
312
- }
313
- if (workList.size() == 0)
314
- {
315
- return false;
316
- }
317
- else
318
- {
319
- while (!workList.empty())
298
+ else if (withinSet && SVFUtil::isa<RetCFGEdge>(edge))
320
299
  {
321
- preAs.joinWith(workList.back());
322
- workList.pop_back();
300
+ // In recursive cycles, FunExit and RetNode are both inside the cycle.
301
+ // Propagate along RetCFGEdge when both endpoints are in the cycle.
302
+ const ICFGNode* dst = edge->getDstNode();
303
+ if (withinSet->find(dst) != withinSet->end())
304
+ {
305
+ if (hasAbsStateFromTrace(dst))
306
+ abstractTrace[dst].joinWith(abstractTrace[node]);
307
+ else
308
+ abstractTrace[dst] = abstractTrace[node];
309
+ }
323
310
  }
324
- // Has ES on the in edges - Feasible block
325
- // update post as
326
- abstractTrace[icfgNode] = preAs;
327
- return true;
311
+ // CallCFGEdge is handled by handleFunCall
328
312
  }
329
313
  }
330
314
 
@@ -626,68 +610,36 @@ bool AbstractInterpretation::isBranchFeasible(const IntraCFGEdge* intraEdge,
626
610
  }
627
611
  return true;
628
612
  }
629
- /// handle instructions in svf basic blocks
630
- void AbstractInterpretation::handleSingletonWTO(const ICFGSingletonWTO *icfgSingletonWto)
631
- {
632
- const ICFGNode* node = icfgSingletonWto->getICFGNode();
633
- stat->getBlockTrace()++;
634
-
635
- std::deque<const ICFGNode*> worklist;
636
-
637
- stat->getICFGNodeTrace()++;
638
- // handle SVF Stmt
639
- for (const SVFStmt *stmt: node->getSVFStmts())
640
- {
641
- handleSVFStatement(stmt);
642
- }
643
- // inlining the callee by calling handleFunc for the callee function
644
- if (const CallICFGNode* callnode = SVFUtil::dyn_cast<CallICFGNode>(node))
645
- {
646
- handleCallSite(callnode);
647
- }
648
- for (auto& detector: detectors)
649
- detector->detect(getAbstractState(node), node);
650
- stat->countStateSize();
651
- }
652
-
653
613
  /**
654
- * Handle an ICFG node by merging states from predecessors and processing statements
655
- * Returns true if the abstract state has changed, false if fixpoint reached or infeasible
614
+ * Handle an ICFG node: execute statements and propagate post-state to successors.
615
+ * The node's pre-state must already be in abstractTrace (set by predecessors'
616
+ * propagateToSuccessor, or by handleGlobalNode/handleFunction for entry nodes).
617
+ * Returns true if the abstract state has changed, false if fixpoint reached or unreachable.
656
618
  */
657
619
  bool AbstractInterpretation::handleICFGNode(const ICFGNode* node)
658
620
  {
659
- // Store the previous state for fixpoint detection
660
- AbstractState prevState;
661
- bool hadPrevState = hasAbsStateFromTrace(node);
662
- if (hadPrevState)
663
- prevState = abstractTrace[node];
664
-
665
- // For function entry nodes, initialize state from predecessors or global
621
+ // Check reachability: pre-state must have been propagated by predecessors
666
622
  bool isFunEntry = SVFUtil::isa<FunEntryICFGNode>(node);
667
- if (isFunEntry)
623
+ if (!hasAbsStateFromTrace(node))
668
624
  {
669
- // Try to merge from predecessors first (handles call edges)
670
- if (!mergeStatesFromPredecessors(node))
625
+ if (isFunEntry)
671
626
  {
672
- // No predecessors with state - inherit from global node
627
+ // Entry point with no callers: inherit from global node
673
628
  const ICFGNode* globalNode = icfg->getGlobalICFGNode();
674
629
  if (hasAbsStateFromTrace(globalNode))
675
- {
676
630
  abstractTrace[node] = abstractTrace[globalNode];
677
- }
678
631
  else
679
- {
680
632
  abstractTrace[node] = AbstractState();
681
- }
682
633
  }
683
- }
684
- else
685
- {
686
- // Merge states from predecessors
687
- if (!mergeStatesFromPredecessors(node))
688
- return false;
634
+ else
635
+ {
636
+ return false; // unreachable node
637
+ }
689
638
  }
690
639
 
640
+ // Store the previous state for fixpoint detection
641
+ AbstractState prevState = abstractTrace[node];
642
+
691
643
  stat->getBlockTrace()++;
692
644
  stat->getICFGNodeTrace()++;
693
645
 
@@ -711,95 +663,12 @@ bool AbstractInterpretation::handleICFGNode(const ICFGNode* node)
711
663
  // Track this node as analyzed (for coverage statistics across all entry points)
712
664
  allAnalyzedNodes.insert(node);
713
665
 
714
- // Check if state changed (for fixpoint detection)
715
- // For entry nodes on first visit, always return true to process successors
716
- if (isFunEntry && !hadPrevState)
717
- return true;
718
-
719
666
  if (abstractTrace[node] == prevState)
720
667
  return false;
721
668
 
722
669
  return true;
723
670
  }
724
671
 
725
- /**
726
- * Get the next nodes of a node within the same function
727
- * For CallICFGNode, includes shortcut to RetICFGNode
728
- */
729
- std::vector<const ICFGNode*> AbstractInterpretation::getNextNodes(const ICFGNode* node) const
730
- {
731
- std::vector<const ICFGNode*> outEdges;
732
- for (const ICFGEdge* edge : node->getOutEdges())
733
- {
734
- const ICFGNode* dst = edge->getDstNode();
735
- // Only nodes inside the same function are included
736
- if (dst->getFun() == node->getFun())
737
- {
738
- outEdges.push_back(dst);
739
- }
740
- }
741
- if (const CallICFGNode* callNode = SVFUtil::dyn_cast<CallICFGNode>(node))
742
- {
743
- // Shortcut to the RetICFGNode
744
- const ICFGNode* retNode = callNode->getRetICFGNode();
745
- outEdges.push_back(retNode);
746
- }
747
- return outEdges;
748
- }
749
-
750
- /**
751
- * Get the next nodes outside a cycle
752
- * Inner cycles are skipped since their next nodes cannot be outside the outer cycle
753
- */
754
- std::vector<const ICFGNode*> AbstractInterpretation::getNextNodesOfCycle(const ICFGCycleWTO* cycle) const
755
- {
756
- Set<const ICFGNode*> cycleNodes;
757
- // Insert the head of the cycle and all component nodes
758
- cycleNodes.insert(cycle->head()->getICFGNode());
759
- for (const ICFGWTOComp* comp : cycle->getWTOComponents())
760
- {
761
- if (const ICFGSingletonWTO* singleton = SVFUtil::dyn_cast<ICFGSingletonWTO>(comp))
762
- {
763
- cycleNodes.insert(singleton->getICFGNode());
764
- }
765
- else if (const ICFGCycleWTO* subCycle = SVFUtil::dyn_cast<ICFGCycleWTO>(comp))
766
- {
767
- cycleNodes.insert(subCycle->head()->getICFGNode());
768
- }
769
- }
770
-
771
- std::vector<const ICFGNode*> outEdges;
772
-
773
- // Check head's successors
774
- std::vector<const ICFGNode*> nextNodes = getNextNodes(cycle->head()->getICFGNode());
775
- for (const ICFGNode* nextNode : nextNodes)
776
- {
777
- // Only nodes that point outside the cycle are included
778
- if (cycleNodes.find(nextNode) == cycleNodes.end())
779
- {
780
- outEdges.push_back(nextNode);
781
- }
782
- }
783
-
784
- // Check each component's successors
785
- for (const ICFGWTOComp* comp : cycle->getWTOComponents())
786
- {
787
- if (const ICFGSingletonWTO* singleton = SVFUtil::dyn_cast<ICFGSingletonWTO>(comp))
788
- {
789
- std::vector<const ICFGNode*> compNextNodes = getNextNodes(singleton->getICFGNode());
790
- for (const ICFGNode* nextNode : compNextNodes)
791
- {
792
- if (cycleNodes.find(nextNode) == cycleNodes.end())
793
- {
794
- outEdges.push_back(nextNode);
795
- }
796
- }
797
- }
798
- // Skip inner cycles - they are handled within the outer cycle
799
- }
800
- return outEdges;
801
- }
802
-
803
672
  /**
804
673
  * Handle a function using worklist algorithm guided by WTO order.
805
674
  * All top-level WTO components are pushed into the worklist upfront,
@@ -821,7 +690,9 @@ void AbstractInterpretation::handleFunction(const ICFGNode* funEntry, const Call
821
690
 
822
691
  if (const ICFGSingletonWTO* singleton = SVFUtil::dyn_cast<ICFGSingletonWTO>(comp))
823
692
  {
824
- handleICFGNode(singleton->getICFGNode());
693
+ const ICFGNode* node = singleton->getICFGNode();
694
+ handleICFGNode(node);
695
+ propagateToSuccessor(node);
825
696
  }
826
697
  else if (const ICFGCycleWTO* cycle = SVFUtil::dyn_cast<ICFGCycleWTO>(comp))
827
698
  {
@@ -981,37 +852,78 @@ void AbstractInterpretation::handleFunCall(const CallICFGNode *callNode)
981
852
  {
982
853
  AbstractState& as = getAbstractState(callNode);
983
854
  abstractTrace[callNode] = as;
984
-
985
- // Skip recursive callsites (within SCC); entry calls are not skipped
855
+ // Skip recursive callsites (within SCC); entry calls are not skipped.
856
+ // For skipped recursive calls, propagate the caller's state to the callee
857
+ // entry as a back-edge contribution. This is needed because the IntraCFGEdge
858
+ // from CallNode to RetNode was removed by the ICFG builder (replaced by
859
+ // CallCFGEdge + RetCFGEdge), so propagateToSuccessor cannot propagate the
860
+ // back-edge. We manually push the CallPE parameters to the callee entry.
986
861
  if (skipRecursiveCall(callNode))
862
+ {
863
+ const FunObjVar* callee = getCallee(callNode);
864
+ const ICFGNode* calleeEntry = icfg->getFunEntryICFGNode(callee);
865
+ // Push caller's state to callee entry (back-edge of recursive cycle)
866
+ if (hasAbsStateFromTrace(calleeEntry))
867
+ abstractTrace[calleeEntry].joinWith(abstractTrace[callNode]);
868
+ else
869
+ abstractTrace[calleeEntry] = abstractTrace[callNode];
987
870
  return;
871
+ }
988
872
 
989
873
  // Direct call: callee is known
990
874
  if (const FunObjVar* callee = callNode->getCalledFunction())
991
875
  {
992
876
  const ICFGNode* calleeEntry = icfg->getFunEntryICFGNode(callee);
877
+ const ICFGNode* calleeExit = icfg->getFunExitICFGNode(callee);
878
+ // Push caller's state to callee entry so CallPE can copy parameters
879
+ abstractTrace[calleeEntry] = abstractTrace[callNode];
993
880
  handleFunction(calleeEntry, callNode);
881
+ // Use callee exit state (which has memory side effects) for the return node
994
882
  const RetICFGNode* retNode = callNode->getRetICFGNode();
995
- abstractTrace[retNode] = abstractTrace[callNode];
883
+ if (hasAbsStateFromTrace(calleeExit))
884
+ abstractTrace[retNode] = abstractTrace[calleeExit];
885
+ else
886
+ abstractTrace[retNode] = abstractTrace[callNode];
996
887
  return;
997
888
  }
998
889
 
999
890
  // Indirect call: use Andersen's call graph to get all resolved callees.
1000
891
  // The call graph was built during PreAnalysis::initWTO() by running Andersen's pointer analysis,
1001
892
  // which over-approximates the set of possible targets for each indirect callsite.
893
+ const RetICFGNode* retNode = callNode->getRetICFGNode();
1002
894
  if (callGraph->hasIndCSCallees(callNode))
1003
895
  {
1004
896
  const auto& callees = callGraph->getIndCSCallees(callNode);
897
+ bool firstCallee = true;
1005
898
  for (const FunObjVar* callee : callees)
1006
899
  {
1007
900
  if (callee->isDeclaration())
1008
901
  continue;
1009
902
  const ICFGNode* calleeEntry = icfg->getFunEntryICFGNode(callee);
903
+ const ICFGNode* calleeExit = icfg->getFunExitICFGNode(callee);
904
+ // Push caller's state to callee entry
905
+ abstractTrace[calleeEntry] = abstractTrace[callNode];
1010
906
  handleFunction(calleeEntry, callNode);
907
+ // Use callee exit state for retNode (first callee assigns, rest join)
908
+ if (hasAbsStateFromTrace(calleeExit))
909
+ {
910
+ if (firstCallee)
911
+ {
912
+ abstractTrace[retNode] = abstractTrace[calleeExit];
913
+ firstCallee = false;
914
+ }
915
+ else
916
+ abstractTrace[retNode].joinWith(abstractTrace[calleeExit]);
917
+ }
1011
918
  }
919
+ // If no callee was processed, fall back to caller's state
920
+ if (firstCallee)
921
+ abstractTrace[retNode] = abstractTrace[callNode];
922
+ }
923
+ else
924
+ {
925
+ abstractTrace[retNode] = abstractTrace[callNode];
1012
926
  }
1013
- const RetICFGNode* retNode = callNode->getRetICFGNode();
1014
- abstractTrace[retNode] = abstractTrace[callNode];
1015
927
  }
1016
928
 
1017
929
  /// Handle WTO cycle (loop or recursive function) using widening/narrowing iteration.
@@ -1069,67 +981,140 @@ void AbstractInterpretation::handleLoopOrRecursion(const ICFGCycleWTO* cycle, co
1069
981
  return;
1070
982
  }
1071
983
 
1072
- // WIDEN_ONLY / WIDEN_NARROW modes (and regular loops): iterate until fixpoint
984
+ // Snapshot the external pre-state of the cycle head (contributions from
985
+ // outside the cycle, already propagated before we enter the cycle).
986
+ // This is fixed across all iterations; only back-edge contributions change.
987
+ AbstractState externalPre;
988
+ if (hasAbsStateFromTrace(cycle_head))
989
+ externalPre = abstractTrace[cycle_head];
990
+
991
+ // Collect all cycle body node pointers (excluding head) for clearing,
992
+ // and build cycleNodes set (head + body) for restricting propagation.
993
+ std::vector<const ICFGNode*> bodyNodes;
994
+ Set<const ICFGNode*> cycleNodes;
995
+ cycleNodes.insert(cycle_head);
996
+ for (const ICFGWTOComp* comp : cycle->getWTOComponents())
997
+ {
998
+ if (const ICFGSingletonWTO* singleton = SVFUtil::dyn_cast<ICFGSingletonWTO>(comp))
999
+ {
1000
+ bodyNodes.push_back(singleton->getICFGNode());
1001
+ cycleNodes.insert(singleton->getICFGNode());
1002
+ }
1003
+ // Sub-cycle heads are handled recursively; their state is managed
1004
+ // by their own handleLoopOrRecursion call, but we still need to
1005
+ // clear them to avoid stale accumulation from previous outer iterations.
1006
+ else if (const ICFGCycleWTO* subCycle = SVFUtil::dyn_cast<ICFGCycleWTO>(comp))
1007
+ {
1008
+ bodyNodes.push_back(subCycle->head()->getICFGNode());
1009
+ cycleNodes.insert(subCycle->head()->getICFGNode());
1010
+ }
1011
+ }
1012
+
1013
+ // WIDEN_ONLY / WIDEN_NARROW modes (and regular loops): iterate until fixpoint.
1014
+ // Widening/narrowing is applied to the head's PRE-state (before execution).
1015
+ // Each iteration: widen/narrow pre-state -> execute head -> propagate ->
1016
+ // execute body -> collect back-edge -> rebuild pre-state for next iteration.
1073
1017
  bool increasing = true;
1074
1018
  u32_t widen_delay = Options::WidenDelay();
1019
+ AbstractState prev_head_pre;
1020
+ if (hasAbsStateFromTrace(cycle_head))
1021
+ prev_head_pre = abstractTrace[cycle_head];
1075
1022
 
1076
1023
  for (u32_t cur_iter = 0;; cur_iter++)
1077
1024
  {
1078
- // Get the abstract state before processing the cycle head
1079
- AbstractState prev_head_state;
1080
- if (hasAbsStateFromTrace(cycle_head))
1081
- prev_head_state = abstractTrace[cycle_head];
1082
-
1083
- // Process the cycle head node
1084
- handleICFGNode(cycle_head);
1085
- AbstractState cur_head_state = abstractTrace[cycle_head];
1025
+ // Clear cycle body states to prevent stale accumulation from previous iteration.
1026
+ for (const ICFGNode* bodyNode : bodyNodes)
1027
+ abstractTrace.erase(bodyNode);
1086
1028
 
1087
- // Start widening or narrowing if cur_iter >= widen delay threshold
1029
+ // Apply widening or narrowing to the head's pre-state.
1030
+ // Compare current pre-state with the previous iteration's pre-state.
1088
1031
  if (cur_iter >= widen_delay)
1089
1032
  {
1033
+ AbstractState cur_head_pre = abstractTrace[cycle_head];
1090
1034
  if (increasing)
1091
1035
  {
1092
- // Apply widening operator
1093
- abstractTrace[cycle_head] = prev_head_state.widening(cur_head_state);
1094
-
1095
- if (abstractTrace[cycle_head] == prev_head_state)
1036
+ abstractTrace[cycle_head] = prev_head_pre.widening(cur_head_pre);
1037
+ if (abstractTrace[cycle_head] == prev_head_pre)
1096
1038
  {
1097
- // Widening fixpoint reached, switch to narrowing phase
1098
1039
  increasing = false;
1099
- continue;
1040
+ // Recompute with the stabilized state
1100
1041
  }
1101
1042
  }
1102
1043
  else
1103
1044
  {
1104
- // Narrowing phase - check if narrowing should be applied
1105
1045
  if (!shouldApplyNarrowing(cycle_head->getFun()))
1106
- {
1107
1046
  break;
1108
- }
1109
1047
 
1110
- // Apply narrowing
1111
- abstractTrace[cycle_head] = prev_head_state.narrowing(cur_head_state);
1112
- if (abstractTrace[cycle_head] == prev_head_state)
1113
- {
1114
- // Narrowing fixpoint reached, exit loop
1048
+ abstractTrace[cycle_head] = prev_head_pre.narrowing(cur_head_pre);
1049
+ if (abstractTrace[cycle_head] == prev_head_pre)
1115
1050
  break;
1116
- }
1117
1051
  }
1118
1052
  }
1053
+ // Save this iteration's pre-state for next iteration's comparison
1054
+ prev_head_pre = abstractTrace[cycle_head];
1055
+
1056
+ // Process the cycle head node (execute stmts, transforms pre to post)
1057
+ handleICFGNode(cycle_head);
1119
1058
 
1120
- // Process cycle body components
1059
+ // Propagate head's post-state to body successor nodes within the cycle,
1060
+ // then erase head so the back-edge from body tail lands in a clean slot
1061
+ // (not mixed with head's post-state).
1062
+ propagateToSuccessor(cycle_head, &cycleNodes);
1063
+ abstractTrace.erase(cycle_head);
1064
+
1065
+ // Process cycle body components (propagation restricted to cycle nodes)
1121
1066
  for (const ICFGWTOComp* comp : cycle->getWTOComponents())
1122
1067
  {
1123
1068
  if (const ICFGSingletonWTO* singleton = SVFUtil::dyn_cast<ICFGSingletonWTO>(comp))
1124
1069
  {
1125
- handleICFGNode(singleton->getICFGNode());
1070
+ const ICFGNode* bodyNode = singleton->getICFGNode();
1071
+ handleICFGNode(bodyNode);
1072
+ propagateToSuccessor(bodyNode, &cycleNodes);
1126
1073
  }
1127
1074
  else if (const ICFGCycleWTO* subCycle = SVFUtil::dyn_cast<ICFGCycleWTO>(comp))
1128
1075
  {
1129
- // Handle nested cycle recursively
1130
1076
  handleLoopOrRecursion(subCycle, caller);
1131
1077
  }
1132
1078
  }
1079
+
1080
+ // After body processing, abstractTrace[cycle_head] holds only the
1081
+ // back-edge contribution (if any). Rebuild head's pre-state for the
1082
+ // next iteration: externalPre joined with back-edge.
1083
+ if (hasAbsStateFromTrace(cycle_head))
1084
+ {
1085
+ AbstractState backEdge = abstractTrace[cycle_head];
1086
+ abstractTrace[cycle_head] = externalPre;
1087
+ abstractTrace[cycle_head].joinWith(backEdge);
1088
+ }
1089
+ else
1090
+ {
1091
+ abstractTrace[cycle_head] = externalPre;
1092
+ }
1093
+ }
1094
+
1095
+ // Final pass: re-execute all cycle nodes and propagate without restriction.
1096
+ // During iterations, propagation was restricted to cycle-internal nodes.
1097
+ // Now re-execute and propagate from all cycle nodes without restriction,
1098
+ // so that exit edges (outside the cycle) receive the converged states.
1099
+ // We must clear body nodes first and re-process them from scratch using
1100
+ // the converged head pre-state, since body nodes may contain stale states
1101
+ // from the last iteration that predate the final head re-execution.
1102
+ for (const ICFGNode* bodyNode : bodyNodes)
1103
+ abstractTrace.erase(bodyNode);
1104
+ handleICFGNode(cycle_head);
1105
+ propagateToSuccessor(cycle_head);
1106
+ for (const ICFGWTOComp* comp : cycle->getWTOComponents())
1107
+ {
1108
+ if (const ICFGSingletonWTO* singleton = SVFUtil::dyn_cast<ICFGSingletonWTO>(comp))
1109
+ {
1110
+ const ICFGNode* bodyNode = singleton->getICFGNode();
1111
+ handleICFGNode(bodyNode);
1112
+ propagateToSuccessor(bodyNode);
1113
+ }
1114
+ else if (const ICFGCycleWTO* subCycle = SVFUtil::dyn_cast<ICFGCycleWTO>(comp))
1115
+ {
1116
+ handleLoopOrRecursion(subCycle, caller);
1117
+ }
1133
1118
  }
1134
1119
  }
1135
1120