svf-tools 1.0.1193 → 1.0.1195

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.1193",
3
+ "version": "1.0.1195",
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": {
@@ -114,7 +114,7 @@ public:
114
114
  /// Return the internal index if addr is an address otherwise return the value of idx
115
115
  inline u32_t getIDFromAddr(u32_t addr)
116
116
  {
117
- return _freedAddrs.count(addr) ? AddressValue::getInternalID(InvalidMemAddr) : AddressValue::getInternalID(addr);
117
+ return _freedAddrs.count(addr) ? AddressValue::getInternalID(BlackHoleObjAddr) : AddressValue::getInternalID(addr);
118
118
  }
119
119
 
120
120
  AbstractState&operator=(const AbstractState&rhs)
@@ -187,9 +187,9 @@ public:
187
187
  return addr == NullMemAddr;
188
188
  }
189
189
 
190
- static inline bool isInvalidMem(u32_t addr)
190
+ static inline bool isBlackHoleObjAddr(u32_t addr)
191
191
  {
192
- return addr == InvalidMemAddr;
192
+ return addr == BlackHoleObjAddr;
193
193
  }
194
194
 
195
195
 
@@ -32,8 +32,8 @@
32
32
 
33
33
  #define AddressMask 0x7f000000
34
34
  #define FlippedAddressMask (AddressMask^0xffffffff)
35
- // the address of InvalidMem(the black hole), getVirtualMemAddress(2);
36
- #define InvalidMemAddr 0x7f000000 + 2
35
+ // the address of BlackHole object, getVirtualMemAddress(2);
36
+ #define BlackHoleObjAddr 0x7f000000 + 2
37
37
  // the address of NullMem, getVirtualMemAddress(0);
38
38
  #define NullMemAddr 0x7f000000
39
39
 
@@ -74,43 +74,23 @@ public:
74
74
  */
75
75
  void handleExtAPI(const CallICFGNode *call);
76
76
 
77
- /**
78
- * @brief Handles the strcpy API call.
79
- * @param call Pointer to the call ICFG node.
80
- */
81
- void handleStrcpy(const CallICFGNode *call);
77
+ // --- Shared primitives used by string/memory handlers ---
82
78
 
83
- /**
84
- * @brief Calculates the length of a string.
85
- * @param as Reference to the abstract state.
86
- * @param strValue Pointer to the SVF variable representing the string.
87
- * @return The interval value representing the string length.
88
- */
79
+ /// Get the byte size of each element for a pointer/array variable.
80
+ u32_t getElementSize(AbstractState& as, const SVFVar* var);
81
+
82
+ /// Check if an interval length is usable (not bottom, not unbounded).
83
+ static bool isValidLength(const IntervalValue& len);
84
+
85
+ /// Calculate the length of a null-terminated string in abstract state.
89
86
  IntervalValue getStrlen(AbstractState& as, const SVF::SVFVar *strValue);
90
87
 
91
- /**
92
- * @brief Handles the strcat API call.
93
- * @param call Pointer to the call ICFG node.
94
- */
95
- void handleStrcat(const SVF::CallICFGNode *call);
88
+ // --- String/memory operation handlers ---
96
89
 
97
- /**
98
- * @brief Handles the memcpy API call.
99
- * @param as Reference to the abstract state.
100
- * @param dst Pointer to the destination SVF variable.
101
- * @param src Pointer to the source SVF variable.
102
- * @param len The interval value representing the length to copy.
103
- * @param start_idx The starting index for copying.
104
- */
90
+ void handleStrcpy(const CallICFGNode *call);
91
+ void handleStrcat(const CallICFGNode *call);
92
+ void handleStrncat(const CallICFGNode *call);
105
93
  void handleMemcpy(AbstractState& as, const SVF::SVFVar *dst, const SVF::SVFVar *src, IntervalValue len, u32_t start_idx);
106
-
107
- /**
108
- * @brief Handles the memset API call.
109
- * @param as Reference to the abstract state.
110
- * @param dst Pointer to the destination SVF variable.
111
- * @param elem The interval value representing the element to set.
112
- * @param len The interval value representing the length to set.
113
- */
114
94
  void handleMemset(AbstractState& as, const SVFVar* dst, IntervalValue elem, IntervalValue len);
115
95
 
116
96
  /**
@@ -36,6 +36,8 @@
36
36
  #include "Util/SVFBugReport.h"
37
37
  #include "Util/SVFStat.h"
38
38
  #include "Graphs/SCC.h"
39
+ #include "Graphs/CallGraph.h"
40
+ #include <deque>
39
41
 
40
42
  namespace SVF
41
43
  {
@@ -144,6 +146,13 @@ public:
144
146
  /// Program entry
145
147
  void analyse();
146
148
 
149
+ /// Analyze all entry points (functions without callers)
150
+ void analyzeFromAllProgEntries();
151
+
152
+ /// Get all entry point functions (functions without callers)
153
+ std::deque<const FunObjVar*> collectProgEntryFuns();
154
+
155
+
147
156
  static AbstractInterpretation& getAEInstance()
148
157
  {
149
158
  static AbstractInterpretation instance;
@@ -218,14 +227,14 @@ private:
218
227
  *
219
228
  * @param cycle WTOCycle which has weak topo order of basic blocks and nested cycles
220
229
  */
221
- virtual void handleLoopOrRecursion(const ICFGCycleWTO* cycle);
230
+ virtual void handleLoopOrRecursion(const ICFGCycleWTO* cycle, const CallICFGNode* caller = nullptr);
222
231
 
223
232
  /**
224
233
  * Handle a function using worklist algorithm
225
234
  *
226
235
  * @param funEntry The entry node of the function to handle
227
236
  */
228
- void handleFunction(const ICFGNode* funEntry);
237
+ void handleFunction(const ICFGNode* funEntry, const CallICFGNode* caller = nullptr);
229
238
 
230
239
  /**
231
240
  * Handle an ICFG node by merging states and processing statements
@@ -322,9 +331,9 @@ private:
322
331
  AEAPI* api{nullptr};
323
332
 
324
333
  ICFG* icfg;
334
+ CallGraph* callGraph;
325
335
  AEStat* stat;
326
336
 
327
- std::vector<const CallICFGNode*> callSiteStack;
328
337
  Map<const FunObjVar*, const ICFGWTO*> funcToWTO;
329
338
  Set<std::pair<const CallICFGNode*, NodeID>> nonRecursiveCallSites;
330
339
  Set<const FunObjVar*> recursiveFuns;
@@ -358,6 +367,7 @@ private:
358
367
  Map<std::string, std::function<void(const CallICFGNode*)>> func_map;
359
368
 
360
369
  Map<const ICFGNode*, AbstractState> abstractTrace; // abstract states immediately after nodes
370
+ Set<const ICFGNode*> allAnalyzedNodes; // All nodes ever analyzed (across all entry points)
361
371
  std::string moduleName;
362
372
 
363
373
  std::vector<std::unique_ptr<AEDetector>> detectors;
@@ -367,7 +367,7 @@ void BufOverflowDetector::updateGepObjOffsetFromBase(AbstractState& as, SVF::Add
367
367
  }
368
368
  else
369
369
  {
370
- assert(AbstractState::isInvalidMem(gepAddr) && "GEP object is neither a GepObjVar nor an invalid memory address");
370
+ assert(AbstractState::isBlackHoleObjAddr(gepAddr) && "GEP object is neither a GepObjVar nor an invalid memory address");
371
371
  }
372
372
  }
373
373
  }
@@ -398,7 +398,7 @@ void BufOverflowDetector::updateGepObjOffsetFromBase(AbstractState& as, SVF::Add
398
398
  }
399
399
  else
400
400
  {
401
- assert(AbstractState::isInvalidMem(gepAddr) && "GEP object is neither a GepObjVar nor an invalid memory address");
401
+ assert(AbstractState::isBlackHoleObjAddr(gepAddr) && "GEP object is neither a GepObjVar nor an invalid memory address");
402
402
  }
403
403
  }
404
404
  }
@@ -479,7 +479,23 @@ bool BufOverflowDetector::canSafelyAccessMemory(AbstractState& as, const SVF::SV
479
479
  SVFIR* svfir = PAG::getPAG();
480
480
  NodeID value_id = value->getId();
481
481
 
482
- assert(as[value_id].isAddr());
482
+ // Lazy initialization for uninitialized pointer parameters in multi-entry analysis.
483
+ // When analyzing a function as an entry point (e.g., not called from main),
484
+ // pointer parameters may not have been initialized via AddrStmt.
485
+ //
486
+ // Example:
487
+ // void process_buffer(char* buf, int len) {
488
+ // buf[0] = 'a'; // accessing buf
489
+ // }
490
+ // When analyzing process_buffer as an entry point, 'buf' is a function parameter
491
+ // with no AddrStmt, so it has no address information in the abstract state.
492
+ // We lazily initialize it to point to the black hole object (BlkPtr), representing
493
+ // an unknown but valid memory location. This allows the analysis to continue
494
+ // while being conservatively sound.
495
+ if (!as[value_id].isAddr())
496
+ {
497
+ as[value_id] = AddressValue(BlackHoleObjAddr);
498
+ }
483
499
  for (const auto& addr : as[value_id].getAddrs())
484
500
  {
485
501
  NodeID objId = as.getIDFromAddr(addr);
@@ -687,7 +703,7 @@ bool NullptrDerefDetector::canSafelyDerefPtr(AbstractState& as, const SVFVar* va
687
703
  for (const auto &addr: AbsVal.getAddrs())
688
704
  {
689
705
  // if the addr itself is invalid mem, report unsafe
690
- if (AbstractState::isInvalidMem(addr))
706
+ if (AbstractState::isBlackHoleObjAddr(addr))
691
707
  return false;
692
708
  // if nullptr is detected, return unsafe
693
709
  else if (AbstractState::isNullMem(addr))
@@ -272,60 +272,19 @@ void AbsExtAPI::initExtFunMap()
272
272
 
273
273
  auto sse_strlen = [&](const CallICFGNode *callNode)
274
274
  {
275
- // check the arg size
276
275
  if (callNode->arg_size() < 1) return;
277
- const SVFVar* strValue = callNode->getArgument(0);
278
276
  AbstractState& as = getAbsStateFromTrace(callNode);
279
- NodeID value_id = strValue->getId();
280
277
  u32_t lhsId = callNode->getRetICFGNode()->getActualRet()->getId();
281
- u32_t dst_size = 0;
282
- for (const auto& addr : as[value_id].getAddrs())
283
- {
284
- NodeID objId = as.getIDFromAddr(addr);
285
- if (svfir->getBaseObject(objId)->isConstantByteSize())
286
- {
287
- dst_size = svfir->getBaseObject(objId)->getByteSizeOfObj();
288
- }
289
- else
290
- {
291
- const ICFGNode* addrNode = svfir->getBaseObject(objId)->getICFGNode();
292
- for (const SVFStmt* stmt2: addrNode->getSVFStmts())
293
- {
294
- if (const AddrStmt* addrStmt = SVFUtil::dyn_cast<AddrStmt>(stmt2))
295
- {
296
- dst_size = as.getAllocaInstByteSize(addrStmt);
297
- }
298
- }
299
- }
300
- }
301
- u32_t len = 0;
302
- NodeID dstid = strValue->getId();
303
- if (as.inVarToAddrsTable(dstid))
304
- {
305
- for (u32_t index = 0; index < dst_size; index++)
306
- {
307
- AbstractValue expr0 =
308
- as.getGepObjAddrs(dstid, IntervalValue(index));
309
- AbstractValue val;
310
- for (const auto &addr: expr0.getAddrs())
311
- {
312
- val.join_with(as.load(addr));
313
- }
314
- if (val.getInterval().is_numeral() && (char) val.getInterval().getIntNumeral() == '\0')
315
- {
316
- break;
317
- }
318
- ++len;
319
- }
320
- }
321
- if (len == 0)
322
- {
323
- as[lhsId] = IntervalValue((s64_t)0, (s64_t)Options::MaxFieldLimit());
324
- }
278
+ // strlen/wcslen return the number of characters (not bytes).
279
+ // getStrlen returns byte-scaled length (len * elemSize) for use
280
+ // by memcpy/strcpy. Here we need the raw character count, so
281
+ // divide back by elemSize.
282
+ IntervalValue byteLen = getStrlen(as, callNode->getArgument(0));
283
+ u32_t elemSize = getElementSize(as, callNode->getArgument(0));
284
+ if (byteLen.is_numeral() && elemSize > 1)
285
+ as[lhsId] = IntervalValue(byteLen.getIntNumeral() / (s64_t)elemSize);
325
286
  else
326
- {
327
- as[lhsId] = IntervalValue(len);
328
- }
287
+ as[lhsId] = byteLen;
329
288
  };
330
289
  func_map["strlen"] = sse_strlen;
331
290
  func_map["wcslen"] = sse_strlen;
@@ -350,7 +309,7 @@ void AbsExtAPI::initExtFunMap()
350
309
  const u32_t freePtr = callNode->getArgument(0)->getId();
351
310
  for (auto addr: as[freePtr].getAddrs())
352
311
  {
353
- if (AbstractState::isInvalidMem(addr))
312
+ if (AbstractState::isBlackHoleObjAddr(addr))
354
313
  {
355
314
  // Detected a double free — the address has already been freed.
356
315
  // No action is taken at this point.
@@ -480,7 +439,13 @@ void AbsExtAPI::handleExtAPI(const CallICFGNode *call)
480
439
  }
481
440
  else if (extType == STRCAT)
482
441
  {
483
- handleStrcat(call);
442
+ // Both strcat and strncat are annotated as STRCAT.
443
+ // Distinguish by name: strncat/wcsncat contain "ncat".
444
+ const std::string& name = fun->getName();
445
+ if (name.find("ncat") != std::string::npos)
446
+ handleStrncat(call);
447
+ else
448
+ handleStrcat(call);
484
449
  }
485
450
  else
486
451
  {
@@ -489,21 +454,50 @@ void AbsExtAPI::handleExtAPI(const CallICFGNode *call)
489
454
  return;
490
455
  }
491
456
 
492
- void AbsExtAPI::handleStrcpy(const CallICFGNode *call)
457
+ // ===----------------------------------------------------------------------===//
458
+ // Shared primitives for string/memory handlers
459
+ // ===----------------------------------------------------------------------===//
460
+
461
+ /// Get the byte size of each element for a pointer/array variable.
462
+ /// Shared by handleMemcpy, handleMemset, and getStrlen to avoid duplication.
463
+ u32_t AbsExtAPI::getElementSize(AbstractState& as, const SVFVar* var)
493
464
  {
494
- // strcpy, __strcpy_chk, stpcpy , wcscpy, __wcscpy_chk
495
- // get the dst and src
496
- AbstractState& as = getAbsStateFromTrace(call);
497
- const SVFVar* arg0Val = call->getArgument(0);
498
- const SVFVar* arg1Val = call->getArgument(1);
499
- IntervalValue strLen = getStrlen(as, arg1Val);
500
- // no need to -1, since it has \0 as the last byte
501
- handleMemcpy(as, arg0Val, arg1Val, strLen, strLen.lb().getIntNumeral());
465
+ if (var->getType()->isArrayTy())
466
+ {
467
+ return SVFUtil::dyn_cast<SVFArrayType>(var->getType())
468
+ ->getTypeOfElement()->getByteSize();
469
+ }
470
+ if (var->getType()->isPointerTy())
471
+ {
472
+ if (const SVFType* elemType = as.getPointeeElement(var->getId()))
473
+ {
474
+ if (elemType->isArrayTy())
475
+ return SVFUtil::dyn_cast<SVFArrayType>(elemType)
476
+ ->getTypeOfElement()->getByteSize();
477
+ return elemType->getByteSize();
478
+ }
479
+ return 1;
480
+ }
481
+ assert(false && "unsupported type for element size");
482
+ return 1;
483
+ }
484
+
485
+ /// Check if an interval length is usable for memory operations.
486
+ /// Returns false for bottom (no information) or unbounded lower bound
487
+ /// (cannot determine a concrete start for iteration).
488
+ bool AbsExtAPI::isValidLength(const IntervalValue& len)
489
+ {
490
+ return !len.isBottom() && !len.lb().is_minus_infinity();
502
491
  }
503
492
 
493
+ /// Calculate the length of a null-terminated string in abstract state.
494
+ /// Scans memory from the base of strValue looking for a '\0' byte.
495
+ /// Returns an IntervalValue: exact length if '\0' found, otherwise [0, MaxFieldLimit].
504
496
  IntervalValue AbsExtAPI::getStrlen(AbstractState& as, const SVF::SVFVar *strValue)
505
497
  {
506
498
  NodeID value_id = strValue->getId();
499
+
500
+ // Step 1: determine the buffer size (in bytes) backing this pointer
507
501
  u32_t dst_size = 0;
508
502
  for (const auto& addr : as[value_id].getAddrs())
509
503
  {
@@ -524,8 +518,9 @@ IntervalValue AbsExtAPI::getStrlen(AbstractState& as, const SVF::SVFVar *strValu
524
518
  }
525
519
  }
526
520
  }
521
+
522
+ // Step 2: scan for '\0' terminator
527
523
  u32_t len = 0;
528
- u32_t elemSize = 1;
529
524
  if (as.inVarToAddrsTable(value_id))
530
525
  {
531
526
  for (u32_t index = 0; index < dst_size; index++)
@@ -537,188 +532,153 @@ IntervalValue AbsExtAPI::getStrlen(AbstractState& as, const SVF::SVFVar *strValu
537
532
  {
538
533
  val.join_with(as.load(addr));
539
534
  }
540
- if (val.getInterval().is_numeral() && (char) val.getInterval().getIntNumeral() == '\0')
535
+ if (val.getInterval().is_numeral() &&
536
+ (char) val.getInterval().getIntNumeral() == '\0')
541
537
  {
542
538
  break;
543
539
  }
544
540
  ++len;
545
541
  }
546
- if (strValue->getType()->isArrayTy())
547
- {
548
- elemSize = SVFUtil::dyn_cast<SVFArrayType>(strValue->getType())->getTypeOfElement()->getByteSize();
549
- }
550
- else if (strValue->getType()->isPointerTy())
551
- {
552
- if (const SVFType* elemType = as.getPointeeElement(value_id))
553
- {
554
- if (elemType->isArrayTy())
555
- elemSize = SVFUtil::dyn_cast<SVFArrayType>(elemType)->getTypeOfElement()->getByteSize();
556
- else
557
- elemSize = elemType->getByteSize();
558
- }
559
- else
560
- {
561
- elemSize = 1;
562
- }
563
- }
564
- else
565
- {
566
- assert(false && "we cannot support this type");
567
- }
568
542
  }
543
+
544
+ // Step 3: scale by element size and return
545
+ u32_t elemSize = getElementSize(as, strValue);
569
546
  if (len == 0)
570
- {
571
547
  return IntervalValue((s64_t)0, (s64_t)Options::MaxFieldLimit());
572
- }
573
- else
574
- {
575
- return IntervalValue(len * elemSize);
576
- }
548
+ return IntervalValue(len * elemSize);
549
+ }
550
+
551
+ // ===----------------------------------------------------------------------===//
552
+ // String/memory operation handlers
553
+ // ===----------------------------------------------------------------------===//
554
+
555
+ /// strcpy(dst, src): copy all of src (including '\0') into dst.
556
+ /// Covers: strcpy, __strcpy_chk, stpcpy, wcscpy, __wcscpy_chk
557
+ void AbsExtAPI::handleStrcpy(const CallICFGNode *call)
558
+ {
559
+ AbstractState& as = getAbsStateFromTrace(call);
560
+ const SVFVar* dst = call->getArgument(0);
561
+ const SVFVar* src = call->getArgument(1);
562
+ IntervalValue srcLen = getStrlen(as, src);
563
+ // no need to -1, since srcLen includes up to (but not past) '\0'
564
+ if (!isValidLength(srcLen)) return;
565
+ handleMemcpy(as, dst, src, srcLen, 0);
577
566
  }
578
567
 
568
+ /// strcat(dst, src): append all of src after the end of dst.
569
+ /// Covers: strcat, __strcat_chk, wcscat, __wcscat_chk
570
+ void AbsExtAPI::handleStrcat(const CallICFGNode *call)
571
+ {
572
+ AbstractState& as = getAbsStateFromTrace(call);
573
+ const SVFVar* dst = call->getArgument(0);
574
+ const SVFVar* src = call->getArgument(1);
575
+ IntervalValue dstLen = getStrlen(as, dst);
576
+ IntervalValue srcLen = getStrlen(as, src);
577
+ if (!isValidLength(dstLen)) return;
578
+ handleMemcpy(as, dst, src, srcLen, dstLen.lb().getIntNumeral());
579
+ }
579
580
 
580
- void AbsExtAPI::handleStrcat(const SVF::CallICFGNode *call)
581
+ /// strncat(dst, src, n): append at most n bytes of src after the end of dst.
582
+ /// Covers: strncat, __strncat_chk, wcsncat, __wcsncat_chk
583
+ void AbsExtAPI::handleStrncat(const CallICFGNode *call)
581
584
  {
582
- // __strcat_chk, strcat, __wcscat_chk, wcscat, __strncat_chk, strncat, __wcsncat_chk, wcsncat
583
- // to check it is strcat group or strncat group
584
585
  AbstractState& as = getAbsStateFromTrace(call);
585
- const FunObjVar *fun = call->getCalledFunction();
586
- const std::vector<std::string> strcatGroup = {"__strcat_chk", "strcat", "__wcscat_chk", "wcscat"};
587
- const std::vector<std::string> strncatGroup = {"__strncat_chk", "strncat", "__wcsncat_chk", "wcsncat"};
588
- if (std::find(strcatGroup.begin(), strcatGroup.end(), fun->getName()) != strcatGroup.end())
589
- {
590
- const SVFVar* arg0Val = call->getArgument(0);
591
- const SVFVar* arg1Val = call->getArgument(1);
592
- IntervalValue strLen0 = getStrlen(as, arg0Val);
593
- IntervalValue strLen1 = getStrlen(as, arg1Val);
594
- IntervalValue totalLen = strLen0 + strLen1;
595
- handleMemcpy(as, arg0Val, arg1Val, strLen1, strLen0.lb().getIntNumeral());
596
- // do memcpy
597
- }
598
- else if (std::find(strncatGroup.begin(), strncatGroup.end(), fun->getName()) != strncatGroup.end())
599
- {
600
- const SVFVar* arg0Val = call->getArgument(0);
601
- const SVFVar* arg1Val = call->getArgument(1);
602
- const SVFVar* arg2Val = call->getArgument(2);
603
- IntervalValue arg2Num = as[arg2Val->getId()].getInterval();
604
- IntervalValue strLen0 = getStrlen(as, arg0Val);
605
- IntervalValue totalLen = strLen0 + arg2Num;
606
- handleMemcpy(as, arg0Val, arg1Val, arg2Num, strLen0.lb().getIntNumeral());
607
- // do memcpy
608
- }
609
- else
610
- {
611
- assert(false && "unknown strcat function, please add it to strcatGroup or strncatGroup");
612
- }
586
+ const SVFVar* dst = call->getArgument(0);
587
+ const SVFVar* src = call->getArgument(1);
588
+ IntervalValue n = as[call->getArgument(2)->getId()].getInterval();
589
+ IntervalValue dstLen = getStrlen(as, dst);
590
+ if (!isValidLength(dstLen)) return;
591
+ handleMemcpy(as, dst, src, n, dstLen.lb().getIntNumeral());
613
592
  }
614
593
 
615
- void AbsExtAPI::handleMemcpy(AbstractState& as, const SVF::SVFVar *dst, const SVF::SVFVar *src, IntervalValue len, u32_t start_idx)
594
+ /// Core memcpy: copy `len` bytes from src to dst starting at dst[start_idx].
595
+ void AbsExtAPI::handleMemcpy(AbstractState& as, const SVF::SVFVar *dst,
596
+ const SVF::SVFVar *src, IntervalValue len,
597
+ u32_t start_idx)
616
598
  {
617
- u32_t dstId = dst->getId(); // pts(dstId) = {objid} objbar objtypeinfo->getType().
599
+ if (!isValidLength(len)) return;
600
+
601
+ u32_t dstId = dst->getId();
618
602
  u32_t srcId = src->getId();
619
- u32_t elemSize = 1;
620
- if (dst->getType()->isArrayTy())
621
- {
622
- elemSize = SVFUtil::dyn_cast<SVFArrayType>(dst->getType())->getTypeOfElement()->getByteSize();
623
- }
624
- // memcpy(i32*, i32*, 40)
625
- else if (dst->getType()->isPointerTy())
626
- {
627
- if (const SVFType* elemType = as.getPointeeElement(dstId))
628
- {
629
- if (elemType->isArrayTy())
630
- elemSize = SVFUtil::dyn_cast<SVFArrayType>(elemType)->getTypeOfElement()->getByteSize();
631
- else
632
- elemSize = elemType->getByteSize();
633
- }
634
- else
635
- {
636
- elemSize = 1;
637
- }
638
- }
639
- else
640
- {
641
- assert(false && "we cannot support this type");
642
- }
643
- u32_t size = std::min((u32_t)Options::MaxFieldLimit(), (u32_t) len.lb().getIntNumeral());
603
+ u32_t elemSize = getElementSize(as, dst);
604
+ u32_t size = std::min((u32_t)Options::MaxFieldLimit(),
605
+ (u32_t)len.lb().getIntNumeral());
644
606
  u32_t range_val = size / elemSize;
645
- if (as.inVarToAddrsTable(srcId) && as.inVarToAddrsTable(dstId))
607
+
608
+ if (!as.inVarToAddrsTable(srcId) || !as.inVarToAddrsTable(dstId))
609
+ return;
610
+
611
+ for (u32_t index = 0; index < range_val; index++)
646
612
  {
647
- for (u32_t index = 0; index < range_val; index++)
613
+ AbstractValue expr_src =
614
+ as.getGepObjAddrs(srcId, IntervalValue(index));
615
+ AbstractValue expr_dst =
616
+ as.getGepObjAddrs(dstId, IntervalValue(index + start_idx));
617
+ for (const auto &dstAddr: expr_dst.getAddrs())
648
618
  {
649
- // dead loop for string and break if there's a \0. If no \0, it will throw err.
650
- AbstractValue expr_src =
651
- as.getGepObjAddrs(srcId, IntervalValue(index));
652
- AbstractValue expr_dst =
653
- as.getGepObjAddrs(dstId, IntervalValue(index + start_idx));
654
- for (const auto &dst: expr_dst.getAddrs())
619
+ for (const auto &srcAddr: expr_src.getAddrs())
655
620
  {
656
- for (const auto &src: expr_src.getAddrs())
621
+ u32_t objId = as.getIDFromAddr(srcAddr);
622
+ if (as.inAddrToValTable(objId) || as.inAddrToAddrsTable(objId))
657
623
  {
658
- u32_t objId = as.getIDFromAddr(src);
659
- if (as.inAddrToValTable(objId))
660
- {
661
- as.store(dst, as.load(src));
662
- }
663
- else if (as.inAddrToAddrsTable(objId))
664
- {
665
- as.store(dst, as.load(src));
666
- }
624
+ as.store(dstAddr, as.load(srcAddr));
667
625
  }
668
626
  }
669
627
  }
670
628
  }
671
629
  }
672
630
 
673
- void AbsExtAPI::handleMemset(AbstractState& as, const SVF::SVFVar *dst, IntervalValue elem, IntervalValue len)
631
+ /// Core memset: fill dst with `elem` for `len` bytes.
632
+ /// Note: elemSize here uses the pointee type's full size (not array element size)
633
+ /// to match how LLVM memset/wmemset intrinsics measure `len`. For a pointer to
634
+ /// wchar_t[100], elemSize = sizeof(wchar_t[100]), so range_val reflects the
635
+ /// number of top-level GEP fields, not individual array elements.
636
+ void AbsExtAPI::handleMemset(AbstractState& as, const SVF::SVFVar *dst,
637
+ IntervalValue elem, IntervalValue len)
674
638
  {
639
+ if (!isValidLength(len)) return;
640
+
675
641
  u32_t dstId = dst->getId();
676
- u32_t size = std::min((u32_t)Options::MaxFieldLimit(), (u32_t) len.lb().getIntNumeral());
677
642
  u32_t elemSize = 1;
678
643
  if (dst->getType()->isArrayTy())
679
644
  {
680
- elemSize = SVFUtil::dyn_cast<SVFArrayType>(dst->getType())->getTypeOfElement()->getByteSize();
645
+ elemSize = SVFUtil::dyn_cast<SVFArrayType>(dst->getType())
646
+ ->getTypeOfElement()->getByteSize();
681
647
  }
682
648
  else if (dst->getType()->isPointerTy())
683
649
  {
684
650
  if (const SVFType* elemType = as.getPointeeElement(dstId))
685
- {
686
651
  elemSize = elemType->getByteSize();
687
- }
688
652
  else
689
- {
690
653
  elemSize = 1;
691
- }
692
654
  }
693
655
  else
694
656
  {
695
- assert(false && "we cannot support this type");
657
+ assert(false && "unsupported type for element size");
696
658
  }
697
-
659
+ u32_t size = std::min((u32_t)Options::MaxFieldLimit(),
660
+ (u32_t)len.lb().getIntNumeral());
698
661
  u32_t range_val = size / elemSize;
662
+
699
663
  for (u32_t index = 0; index < range_val; index++)
700
664
  {
701
- // dead loop for string and break if there's a \0. If no \0, it will throw err.
702
- if (as.inVarToAddrsTable(dstId))
665
+ if (!as.inVarToAddrsTable(dstId))
666
+ break;
667
+ AbstractValue lhs_gep = as.getGepObjAddrs(dstId, IntervalValue(index));
668
+ for (const auto &addr: lhs_gep.getAddrs())
703
669
  {
704
- AbstractValue lhs_gep = as.getGepObjAddrs(dstId, IntervalValue(index));
705
- for (const auto &addr: lhs_gep.getAddrs())
670
+ u32_t objId = as.getIDFromAddr(addr);
671
+ if (as.inAddrToValTable(objId))
706
672
  {
707
- u32_t objId = as.getIDFromAddr(addr);
708
- if (as.inAddrToValTable(objId))
709
- {
710
- AbstractValue tmp = as.load(addr);
711
- tmp.join_with(elem);
712
- as.store(addr, tmp);
713
- }
714
- else
715
- {
716
- as.store(addr, elem);
717
- }
673
+ AbstractValue tmp = as.load(addr);
674
+ tmp.join_with(elem);
675
+ as.store(addr, tmp);
676
+ }
677
+ else
678
+ {
679
+ as.store(addr, elem);
718
680
  }
719
681
  }
720
- else
721
- break;
722
682
  }
723
683
  }
724
684
 
@@ -34,6 +34,7 @@
34
34
  #include "Graphs/CallGraph.h"
35
35
  #include "WPA/Andersen.h"
36
36
  #include <cmath>
37
+ #include <deque>
37
38
 
38
39
  using namespace SVF;
39
40
  using namespace SVFUtil;
@@ -62,6 +63,8 @@ void AbstractInterpretation::runOnModule(ICFG *_icfg)
62
63
 
63
64
  AbstractInterpretation::AbstractInterpretation()
64
65
  {
66
+ AndersenWaveDiff* ander = AndersenWaveDiff::createAndersenWaveDiff(svfir);
67
+ callGraph = ander->getCallGraph();
65
68
  stat = new AEStat(this);
66
69
  }
67
70
  /// Destructor
@@ -95,11 +98,8 @@ void AbstractInterpretation::collectCycleHeads(const std::list<const ICFGWTOComp
95
98
  void AbstractInterpretation::initWTO()
96
99
  {
97
100
  AndersenWaveDiff* ander = AndersenWaveDiff::createAndersenWaveDiff(svfir);
98
- // Detect if the call graph has cycles by finding its strongly connected components (SCC)
99
- Andersen::CallGraphSCC* callGraphScc = ander->getCallGraphSCC();
101
+ CallGraphSCC* callGraphScc = ander->getCallGraphSCC();
100
102
  callGraphScc->find();
101
- CallGraph* callGraph = ander->getCallGraph();
102
-
103
103
  // Iterate through the call graph
104
104
  for (auto it = callGraph->begin(); it != callGraph->end(); it++)
105
105
  {
@@ -162,33 +162,103 @@ void AbstractInterpretation::initWTO()
162
162
  }
163
163
  }
164
164
 
165
- /// Program entry
165
+ /// Collect entry point functions for analysis.
166
+ /// Entry points are functions without callers (no incoming edges in CallGraph).
167
+ /// Uses a deque to allow efficient insertion at front for prioritizing main()
168
+ std::deque<const FunObjVar*> AbstractInterpretation::collectProgEntryFuns()
169
+ {
170
+ std::deque<const FunObjVar*> entryFunctions;
171
+
172
+ for (auto it = callGraph->begin(); it != callGraph->end(); ++it)
173
+ {
174
+ const CallGraphNode* cgNode = it->second;
175
+ const FunObjVar* fun = cgNode->getFunction();
176
+
177
+ // Skip declarations
178
+ if (fun->isDeclaration())
179
+ continue;
180
+
181
+ // Entry points are functions without callers (no incoming edges)
182
+ if (cgNode->getInEdges().empty())
183
+ {
184
+ // If main exists, put it first for priority using deque's push_front
185
+ if (fun->getName() == "main")
186
+ {
187
+ entryFunctions.push_front(fun);
188
+ }
189
+ else
190
+ {
191
+ entryFunctions.push_back(fun);
192
+ }
193
+ }
194
+ }
195
+
196
+ return entryFunctions;
197
+ }
198
+
199
+
200
+ /// Program entry - analyze from all entry points (multi-entry analysis is the default)
166
201
  void AbstractInterpretation::analyse()
167
202
  {
168
203
  initWTO();
204
+
205
+ // Always use multi-entry analysis from all entry points
206
+ analyzeFromAllProgEntries();
207
+ }
208
+
209
+ /// Analyze all entry points (functions without callers) - for whole-program analysis.
210
+ /// Abstract state is shared across entry points so that functions analyzed from
211
+ /// earlier entries are not re-analyzed from scratch.
212
+ void AbstractInterpretation::analyzeFromAllProgEntries()
213
+ {
214
+ // Collect all entry point functions
215
+ std::deque<const FunObjVar*> entryFunctions = collectProgEntryFuns();
216
+
217
+ if (entryFunctions.empty())
218
+ {
219
+ assert(false && "No entry functions found for analysis");
220
+ return;
221
+ }
169
222
  // handle Global ICFGNode of SVFModule
170
223
  handleGlobalNode();
171
- getAbsStateFromTrace(
172
- icfg->getGlobalICFGNode())[PAG::getPAG()->getBlkPtr()] = IntervalValue::top();
173
- if (const CallGraphNode* cgn = svfir->getCallGraph()->getCallGraphNode("main"))
224
+ for (const FunObjVar* entryFun : entryFunctions)
174
225
  {
175
- // Use worklist-based function handling instead of recursive WTO component handling
176
- const ICFGNode* mainEntry = icfg->getFunEntryICFGNode(cgn->getFunction());
177
- handleFunction(mainEntry);
226
+ const ICFGNode* funEntry = icfg->getFunEntryICFGNode(entryFun);
227
+ handleFunction(funEntry);
178
228
  }
179
229
  }
180
230
 
181
231
  /// handle global node
232
+ /// Initializes the abstract state for the global ICFG node and processes all global statements.
233
+ /// This includes setting up the null pointer and black hole pointer (blkPtr).
234
+ /// BlkPtr is initialized to point to the BlackHole object, representing
235
+ /// an unknown memory location that cannot be statically resolved.
182
236
  void AbstractInterpretation::handleGlobalNode()
183
237
  {
184
238
  const ICFGNode* node = icfg->getGlobalICFGNode();
185
239
  abstractTrace[node] = AbstractState();
186
240
  abstractTrace[node][IRGraph::NullPtr] = AddressValue();
241
+
187
242
  // Global Node, we just need to handle addr, load, store, copy and gep
188
243
  for (const SVFStmt *stmt: node->getSVFStmts())
189
244
  {
190
245
  handleSVFStatement(stmt);
191
246
  }
247
+
248
+ // BlkPtr represents a pointer whose target is statically unknown (e.g., from
249
+ // int2ptr casts, external function returns, or unmodeled instructions like
250
+ // AtomicCmpXchg). It should be an address pointing to the BlackHole object
251
+ // (ID=2), NOT an interval top.
252
+ //
253
+ // History: this was originally set to IntervalValue::top() as a quick fix when
254
+ // the analysis crashed on programs containing uninitialized BlkPtr. However,
255
+ // BlkPtr is semantically a *pointer* (address domain), not a numeric value
256
+ // (interval domain). Setting it to interval top broke cross-domain consistency:
257
+ // the interval domain and address domain gave contradictory information for the
258
+ // same variable. The correct representation is an AddressValue containing the
259
+ // BlackHole virtual address, which means "points to unknown memory".
260
+ abstractTrace[node][PAG::getPAG()->getBlkPtr()] =
261
+ AddressValue(BlackHoleObjAddr);
192
262
  }
193
263
 
194
264
  /// get execution state by merging states of predecessor blocks
@@ -598,39 +668,22 @@ bool AbstractInterpretation::handleICFGNode(const ICFGNode* node)
598
668
  if (hadPrevState)
599
669
  prevState = abstractTrace[node];
600
670
 
601
- // For function entry nodes, initialize state from caller or global
671
+ // For function entry nodes, initialize state from predecessors or global
602
672
  bool isFunEntry = SVFUtil::isa<FunEntryICFGNode>(node);
603
673
  if (isFunEntry)
604
674
  {
605
675
  // Try to merge from predecessors first (handles call edges)
606
676
  if (!mergeStatesFromPredecessors(node))
607
677
  {
608
- // No predecessors with state - initialize from caller or global
609
- if (!callSiteStack.empty())
678
+ // No predecessors with state - inherit from global node
679
+ const ICFGNode* globalNode = icfg->getGlobalICFGNode();
680
+ if (hasAbsStateFromTrace(globalNode))
610
681
  {
611
- // Get state from the most recent call site
612
- const CallICFGNode* caller = callSiteStack.back();
613
- if (hasAbsStateFromTrace(caller))
614
- {
615
- abstractTrace[node] = abstractTrace[caller];
616
- }
617
- else
618
- {
619
- abstractTrace[node] = AbstractState();
620
- }
682
+ abstractTrace[node] = abstractTrace[globalNode];
621
683
  }
622
684
  else
623
685
  {
624
- // This is the main function entry, inherit from global node
625
- const ICFGNode* globalNode = icfg->getGlobalICFGNode();
626
- if (hasAbsStateFromTrace(globalNode))
627
- {
628
- abstractTrace[node] = abstractTrace[globalNode];
629
- }
630
- else
631
- {
632
- abstractTrace[node] = AbstractState();
633
- }
686
+ abstractTrace[node] = AbstractState();
634
687
  }
635
688
  }
636
689
  }
@@ -661,6 +714,9 @@ bool AbstractInterpretation::handleICFGNode(const ICFGNode* node)
661
714
  detector->detect(getAbsStateFromTrace(node), node);
662
715
  stat->countStateSize();
663
716
 
717
+ // Track this node as analyzed (for coverage statistics across all entry points)
718
+ allAnalyzedNodes.insert(node);
719
+
664
720
  // Check if state changed (for fixpoint detection)
665
721
  // For entry nodes on first visit, always return true to process successors
666
722
  if (isFunEntry && !hadPrevState)
@@ -754,7 +810,7 @@ std::vector<const ICFGNode*> AbstractInterpretation::getNextNodesOfCycle(const I
754
810
  * Handle a function using worklist algorithm
755
811
  * This replaces the recursive WTO component handling with explicit worklist iteration
756
812
  */
757
- void AbstractInterpretation::handleFunction(const ICFGNode* funEntry)
813
+ void AbstractInterpretation::handleFunction(const ICFGNode* funEntry, const CallICFGNode* caller)
758
814
  {
759
815
  FIFOWorkList<const ICFGNode*> worklist;
760
816
  worklist.push(funEntry);
@@ -767,7 +823,7 @@ void AbstractInterpretation::handleFunction(const ICFGNode* funEntry)
767
823
  if (cycleHeadToCycle.find(node) != cycleHeadToCycle.end())
768
824
  {
769
825
  const ICFGCycleWTO* cycle = cycleHeadToCycle[node];
770
- handleLoopOrRecursion(cycle);
826
+ handleLoopOrRecursion(cycle, caller);
771
827
 
772
828
  // Push nodes outside the cycle to the worklist
773
829
  std::vector<const ICFGNode*> cycleNextNodes = getNextNodesOfCycle(cycle);
@@ -821,13 +877,11 @@ bool AbstractInterpretation::isExtCall(const CallICFGNode *callNode)
821
877
 
822
878
  void AbstractInterpretation::handleExtCall(const CallICFGNode *callNode)
823
879
  {
824
- callSiteStack.push_back(callNode);
825
880
  utils->handleExtAPI(callNode);
826
881
  for (auto& detector : detectors)
827
882
  {
828
883
  detector->handleStubFunctions(callNode);
829
884
  }
830
- callSiteStack.pop_back();
831
885
  }
832
886
 
833
887
  /// Check if a function is recursive (part of a call graph SCC)
@@ -945,7 +999,15 @@ bool AbstractInterpretation::shouldApplyNarrowing(const FunObjVar* fun)
945
999
  return false;
946
1000
  }
947
1001
  }
948
- /// Handle direct or indirect call: get callee, process function body, set return state
1002
+ /// Handle direct or indirect call: get callee(s), process function body, set return state.
1003
+ ///
1004
+ /// For direct calls, the callee is known statically.
1005
+ /// For indirect calls, the previous implementation resolved callees from the abstract
1006
+ /// state's address domain, which only picked the first address and missed other targets.
1007
+ /// Since the abstract state's address domain is not an over-approximation for function
1008
+ /// pointers (it may be uninitialized or incomplete), we now use Andersen's pointer
1009
+ /// analysis results from the pre-computed call graph, which soundly resolves all
1010
+ /// possible indirect call targets.
949
1011
  void AbstractInterpretation::handleFunCall(const CallICFGNode *callNode)
950
1012
  {
951
1013
  AbstractState& as = getAbsStateFromTrace(callNode);
@@ -955,16 +1017,30 @@ void AbstractInterpretation::handleFunCall(const CallICFGNode *callNode)
955
1017
  if (skipRecursiveCall(callNode))
956
1018
  return;
957
1019
 
958
- const FunObjVar* callee = getCallee(callNode);
959
- if (!callee)
1020
+ // Direct call: callee is known
1021
+ if (const FunObjVar* callee = callNode->getCalledFunction())
1022
+ {
1023
+ const ICFGNode* calleeEntry = icfg->getFunEntryICFGNode(callee);
1024
+ handleFunction(calleeEntry, callNode);
1025
+ const RetICFGNode* retNode = callNode->getRetICFGNode();
1026
+ abstractTrace[retNode] = abstractTrace[callNode];
960
1027
  return;
1028
+ }
961
1029
 
962
- callSiteStack.push_back(callNode);
963
-
964
- const ICFGNode* calleeEntry = icfg->getFunEntryICFGNode(callee);
965
- handleFunction(calleeEntry);
966
-
967
- callSiteStack.pop_back();
1030
+ // Indirect call: use Andersen's call graph to get all resolved callees.
1031
+ // The call graph was built during initWTO() by running Andersen's pointer analysis,
1032
+ // which over-approximates the set of possible targets for each indirect callsite.
1033
+ if (callGraph->hasIndCSCallees(callNode))
1034
+ {
1035
+ const auto& callees = callGraph->getIndCSCallees(callNode);
1036
+ for (const FunObjVar* callee : callees)
1037
+ {
1038
+ if (callee->isDeclaration())
1039
+ continue;
1040
+ const ICFGNode* calleeEntry = icfg->getFunEntryICFGNode(callee);
1041
+ handleFunction(calleeEntry, callNode);
1042
+ }
1043
+ }
968
1044
  const RetICFGNode* retNode = callNode->getRetICFGNode();
969
1045
  abstractTrace[retNode] = abstractTrace[callNode];
970
1046
  }
@@ -1009,7 +1085,7 @@ void AbstractInterpretation::handleFunCall(const CallICFGNode *callNode)
1009
1085
  /// Example:
1010
1086
  /// int factorial(int n) { return n <= 1 ? 1 : n * factorial(n-1); }
1011
1087
  /// factorial(5) -> returns [10000, 10000] (precise after narrowing)
1012
- void AbstractInterpretation::handleLoopOrRecursion(const ICFGCycleWTO* cycle)
1088
+ void AbstractInterpretation::handleLoopOrRecursion(const ICFGCycleWTO* cycle, const CallICFGNode* caller)
1013
1089
  {
1014
1090
  const ICFGNode* cycle_head = cycle->head()->getICFGNode();
1015
1091
 
@@ -1017,11 +1093,9 @@ void AbstractInterpretation::handleLoopOrRecursion(const ICFGCycleWTO* cycle)
1017
1093
  // all stores and return value to TOP, maintaining original semantics
1018
1094
  if (Options::HandleRecur() == TOP && isRecursiveFun(cycle_head->getFun()))
1019
1095
  {
1020
- // Get the call node from callSiteStack (the call that entered this function)
1021
- if (!callSiteStack.empty())
1096
+ if (caller)
1022
1097
  {
1023
- const CallICFGNode* callNode = callSiteStack.back();
1024
- recursiveCallPass(callNode);
1098
+ recursiveCallPass(caller);
1025
1099
  }
1026
1100
  return;
1027
1101
  }
@@ -1084,7 +1158,7 @@ void AbstractInterpretation::handleLoopOrRecursion(const ICFGCycleWTO* cycle)
1084
1158
  else if (const ICFGCycleWTO* subCycle = SVFUtil::dyn_cast<ICFGCycleWTO>(comp))
1085
1159
  {
1086
1160
  // Handle nested cycle recursively
1087
- handleLoopOrRecursion(subCycle);
1161
+ handleLoopOrRecursion(subCycle, caller);
1088
1162
  }
1089
1163
  }
1090
1164
  }
@@ -1229,15 +1303,39 @@ void AEStat::finializeStat()
1229
1303
  generalNumMap["ES_Loc_Addr_AVG_Num"] /= count;
1230
1304
  }
1231
1305
  generalNumMap["SVF_STMT_NUM"] = count;
1232
- generalNumMap["ICFG_Node_Num"] = _ae->svfir->getICFG()->nodeNum;
1306
+
1307
+ u32_t totalICFGNodes = _ae->svfir->getICFG()->nodeNum;
1308
+ generalNumMap["ICFG_Node_Num"] = totalICFGNodes;
1309
+
1310
+ // Calculate coverage: use allAnalyzedNodes which tracks all nodes across all entry points
1311
+ u32_t analyzedNodes = _ae->allAnalyzedNodes.size();
1312
+ generalNumMap["Analyzed_ICFG_Node_Num"] = analyzedNodes;
1313
+
1314
+ // Coverage percentage (stored as integer percentage * 100 for precision)
1315
+ if (totalICFGNodes > 0)
1316
+ {
1317
+ double coveragePercent = (double)analyzedNodes / (double)totalICFGNodes * 100.0;
1318
+ generalNumMap["ICFG_Coverage_Percent"] = (u32_t)(coveragePercent * 100); // Store as percentage * 100
1319
+ }
1320
+ else
1321
+ {
1322
+ generalNumMap["ICFG_Coverage_Percent"] = 0;
1323
+ }
1324
+
1233
1325
  u32_t callSiteNum = 0;
1234
1326
  u32_t extCallSiteNum = 0;
1235
1327
  Set<const FunObjVar *> funs;
1328
+ Set<const FunObjVar *> analyzedFuns;
1236
1329
  for (const auto &it: *_ae->svfir->getICFG())
1237
1330
  {
1238
1331
  if (it.second->getFun())
1239
1332
  {
1240
1333
  funs.insert(it.second->getFun());
1334
+ // Check if this node was analyzed (across all entry points)
1335
+ if (_ae->allAnalyzedNodes.find(it.second) != _ae->allAnalyzedNodes.end())
1336
+ {
1337
+ analyzedFuns.insert(it.second->getFun());
1338
+ }
1241
1339
  }
1242
1340
  if (const CallICFGNode *callNode = dyn_cast<CallICFGNode>(it.second))
1243
1341
  {
@@ -1252,6 +1350,19 @@ void AEStat::finializeStat()
1252
1350
  }
1253
1351
  }
1254
1352
  generalNumMap["Func_Num"] = funs.size();
1353
+ generalNumMap["Analyzed_Func_Num"] = analyzedFuns.size();
1354
+
1355
+ // Function coverage percentage
1356
+ if (funs.size() > 0)
1357
+ {
1358
+ double funcCoveragePercent = (double)analyzedFuns.size() / (double)funs.size() * 100.0;
1359
+ generalNumMap["Func_Coverage_Percent"] = (u32_t)(funcCoveragePercent * 100); // Store as percentage * 100
1360
+ }
1361
+ else
1362
+ {
1363
+ generalNumMap["Func_Coverage_Percent"] = 0;
1364
+ }
1365
+
1255
1366
  generalNumMap["EXT_CallSite_Num"] = extCallSiteNum;
1256
1367
  generalNumMap["NonEXT_CallSite_Num"] = callSiteNum;
1257
1368
  timeStatMap["Total_Time(sec)"] = (double)(endTime - startTime) / TIMEINTERVAL;
@@ -1280,8 +1391,16 @@ void AEStat::performStat()
1280
1391
  unsigned field_width = 30;
1281
1392
  for (NUMStatMap::iterator it = generalNumMap.begin(), eit = generalNumMap.end(); it != eit; ++it)
1282
1393
  {
1283
- // format out put with width 20 space
1284
- std::cout << std::setw(field_width) << it->first << it->second << "\n";
1394
+ // Special handling for percentage fields (stored as percentage * 100)
1395
+ if (it->first == "ICFG_Coverage_Percent" || it->first == "Func_Coverage_Percent")
1396
+ {
1397
+ double percent = (double)it->second / 100.0;
1398
+ std::cout << std::setw(field_width) << it->first << std::fixed << std::setprecision(2) << percent << "%\n";
1399
+ }
1400
+ else
1401
+ {
1402
+ std::cout << std::setw(field_width) << it->first << it->second << "\n";
1403
+ }
1285
1404
  }
1286
1405
  SVFUtil::outs() << "-------------------------------------------------------\n";
1287
1406
  for (TIMEStatMap::iterator it = timeStatMap.begin(), eit = timeStatMap.end(); it != eit; ++it)
@@ -1605,6 +1724,13 @@ void AbstractInterpretation::updateStateOnCmp(const CmpStmt *cmp)
1605
1724
  case CmpStmt::FCMP_TRUE:
1606
1725
  resVal = IntervalValue(1, 1);
1607
1726
  break;
1727
+ case CmpStmt::FCMP_ORD:
1728
+ case CmpStmt::FCMP_UNO:
1729
+ // FCMP_ORD: true if both operands are not NaN
1730
+ // FCMP_UNO: true if either operand is NaN
1731
+ // Conservatively return [0, 1] since we don't track NaN
1732
+ resVal = IntervalValue(0, 1);
1733
+ break;
1608
1734
  default:
1609
1735
  assert(false && "undefined compare: ");
1610
1736
  }
@@ -1719,6 +1845,13 @@ void AbstractInterpretation::updateStateOnCmp(const CmpStmt *cmp)
1719
1845
  case CmpStmt::FCMP_TRUE:
1720
1846
  resVal = IntervalValue(1, 1);
1721
1847
  break;
1848
+ case CmpStmt::FCMP_ORD:
1849
+ case CmpStmt::FCMP_UNO:
1850
+ // FCMP_ORD: true if both operands are not NaN
1851
+ // FCMP_UNO: true if either operand is NaN
1852
+ // Conservatively return [0, 1] since we don't track NaN
1853
+ resVal = IntervalValue(0, 1);
1854
+ break;
1722
1855
  default:
1723
1856
  assert(false && "undefined compare: ");
1724
1857
  }
@@ -173,7 +173,8 @@ void CHGBuilder::connectInheritEdgeViaCall(const Function* caller, const CallBas
173
173
  {
174
174
  if (cs->arg_size() < 1 || (cs->arg_size() < 2 && cs->paramHasAttr(0, llvm::Attribute::StructRet)))
175
175
  return;
176
- if(caller->arg_size() == 0){
176
+ if(caller->arg_size() == 0)
177
+ {
177
178
  return;
178
179
  }
179
180
  const Value* csThisPtr = cppUtil::getVCallThisPtr(cs);