LLVM 之 Clang 静态分析器篇(7):程序缺陷诊断——内存泄露

Author: stormQ

Created: Saturday, 19. June 2021 07:08PM

Last Modified: Tuesday, 29. June 2021 10:53PM



摘要

本文基于release/12.x版本的 LLVM 源码,在第一版plugin.unix.Malloc检查器(仅实现了检测 C 程序中的内存重复释放)的基础上实现了用于检测 C 程序中内存泄露程序缺陷的功能。从而,便于真正地理解 Clang 静态分析器是如何在unix.Malloc检查器中实现该功能的。除此之外,也有助于按照自身需求修改或扩展 Clang 静态分析器。


研究过程


unix.Malloc检查器的源码实现目录如下:

plugin.unix.Malloc检查器的源码实现目录如下:


C 程序中典型的内存泄露

本节介绍了几个 C 程序中典型的内存泄露示例,涉及mallocfreerealloccalloc函数。这些示例均来自 LLVM 项目中的测试文件 malloc.c(定义在 clang/test/Analysis/malloc.c 文件中)。

示例 1:

49 void f1() {
50   int *p = malloc(12);
51   return// expected-warning{{Potential leak of memory pointed to by 'p'}}
52 }

如果局部变量p所指向的内存分配成功(大多数情况下),那么每次调用f1()函数后该内存都不会被释放。也就是说,在该情况下会发生内存泄露。

示例 2:

71 void reallocNotNullPtr(unsigned sizeIn) {
72   unsigned size = 12;
73   char *p = (char*)malloc(size);
74   if (p) {
75     char *q = (char*)realloc(p, sizeIn);
76     char x = *q; // expected-warning {{Potential leak of memory pointed to by 'q'}}
77   }
78 }

如果局部变量p所指向的内存分配成功(大多数情况下),那么语句char *q = (char*)realloc(p, sizeIn);会被调用,可以分为以下两种情况:

示例 3:

160 void reallocRadar6337483_1() {
161     char *buf = malloc(100);
162     buf = (char*)realloc(buf, 0x1000000);
163     if (!buf) {
164         return;// expected-warning {{Potential leak of memory pointed to by}}
165     }
166     free(buf);
167 }

如果局部变量buf所指向的大小为 100 字节的内存分配成功(大多数情况下),那么语句buf = (char*)realloc(buf, 0x1000000);会被调用,可以分为以下两种情况:

示例 4:

708 char callocZeroesBad () {
709         char *buf = calloc(2,2);
710         char result = buf[3]; // no-warning
711         if (buf[1] != 0) {
712           free(buf); // expected-warning{{never executed}}
713         }
714         return result; // expected-warning{{Potential leak of memory pointed to by 'buf'}}
715 }

如果局部变量buf所指向的内存(大小为 4 字节)分配成功(大多数情况下),那么语句free(buf);不可能被调用。这是因为calloc()函数会将所分配的内存全部初始化为 0。因此,每次调用callocZeroesBad()函数后该内存都不会被释放。也就是说,在该情况下会发生内存泄露。

示例 5:

908 typedef struct _StructWithPtr {
909   int *memP;
910 } StructWithPtr;

省略 ...

934 void testStructLeak() {
935   StructWithPtr St;
936   St.memP = malloc(12);
937   return// expected-warning {{Potential leak of memory pointed to by 'St.memP'}}
938 }

如果局部变量St的数据成员memP所指向的内存分配成功(大多数情况下),那么每次调用testStructLeak()函数后局部变量St(存储在栈上)会被销毁,但其数据成员memP所指向的内存(存储在堆上)不会被释放。也就是说,在该情况下会发生内存泄露。

返回上一级


原检查器订阅的程序点

unix.Malloc检查器中,检查器类MallocChecker用于实现内存相关错误(比如:内存泄露内存重复释放)的诊断功能,其源码实现如下(定义在 clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp 文件中):

286 namespace {
287 
288 class MallocChecker
289     :
 public Checker<check::DeadSymbols, check::PointerEscape,
290                      check::ConstPointerEscape, check::PreStmt<ReturnStmt>,
291                      check::EndFunction, check::PreCall, check::PostCall,
292                      check::NewAllocator, check::PostStmt<BlockExpr>,
293                      check::PostObjCMessage, check::Location, eval::Assume> {
// 省略 ...
728 };
// 省略 ...
859 } // end anonymous namespace

从上面的代码可以看出,该检查器类共订阅了 12 个程序点。如果要在第一版plugin.unix.Malloc检查器的基础上实现用于检测 C 程序中内存泄露程序缺陷的功能,那么需要订阅clang::ento::check::DeadSymbolsclang::ento::check::PointerEscape程序点。

订阅clang::ento::check::DeadSymbols程序点主要是为了判断是否发生了内存泄露。如果一个符号在被回收时,该符号所代表的内存尚未释放,那么表明发生了内存泄露

订阅clang::ento::check::PointerEscape程序点是为了能够在分析器引擎无法有效地跟踪内存所对应的符号时做一些处理,从而避免在某些情况下误报内存泄露程序缺陷。

返回上一级


自己动手实现内存泄露的检测

step 1: 定义检查器类

要实现检测 C 程序中内存泄露程序缺陷的功能,我们需要在第一版plugin.unix.Malloc检查器实现的基础上订阅clang::ento::check::DeadSymbolsclang::ento::check::PointerEscape程序点。

因此,修改后的检查器类定义如下:

 10 namespace {
 11 
 省略 ...
 18 
 19 class MallocChecker : public Checker<check::PostCall, eval::Assume,
 20                                      check::DeadSymbols, check::PointerEscape> {
 省略 ...
 79 };
 80 
 省略 ...
102 
103 } // anonymous namespace

另外,我们需要实现函数原型如下的 2 个成员函数:

74   void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const;
75   ProgramStateRef checkPointerEscape(ProgramStateRef State,
76                                      const InvalidatedSymbols &Escaped,
77                                      const CallEvent *Call,
78                                      PointerEscapeKind Kind)
 const
;

step 2: 实现检查器类

1) 修改checkPostCall()成员函数

108 void MallocChecker::checkPostCall(const CallEvent &Call,
109                                   CheckerContext &C) const {
110   if (!Call.isGlobalCFunction()) {
111     return;
112   }
113 
114   if (const CheckFn *Callback = FreeingMemFnMap.lookup(Call)) {
115     (*Callback)(this, Call, C);
116     return;
117   }
118 
119   if (const CheckFn *Callback = AllocatingMemFnMap.lookup(Call)) {
120     (*Callback)(this, Call, C);
121     return;
122   }
123 
124   if (const CheckFn *Callback = ReallocatingMemFnMap.lookup(Call)) {
125     (*Callback)(this, Call, C);
126     return;
127   }
128 }

为了更容易地维护和扩展,我们将malloc等函数以及它们所对应的处理函数放到各自的映射表中。

a) 定义并初始化FreeingMemFnMap等映射表

 10 namespace {
 11 
 省略 ...
 18 
 19 class MallocChecker : public Checker<check::PostCall, eval::Assume,
 20                                      check::DeadSymbols, check::PointerEscape> {
 省略 ...
 51   using CheckFn = std::function<void(const MallocChecker *,
 52                                      const CallEvent &Call, CheckerContext &C)>;
 53 
 54   const CallDescriptionMap<CheckFn> FreeingMemFnMap{
 55     {{"free"1}, &MallocChecker::checkFree},
 56   };
 57 
 58   const CallDescriptionMap<CheckFn> AllocatingMemFnMap{
 59     {{"malloc"1}, &MallocChecker::checkMalloc},
 60     {{"valloc"1}, &MallocChecker::checkMalloc},
 61     {{"calloc"2}, &MallocChecker::checkCalloc},
 62   };
 63 
 64   const CallDescriptionMap<CheckFn> ReallocatingMemFnMap{
 65     {{"realloc"2}, &MallocChecker::checkRealloc},
 66   };
 省略 ...
 79 };
 80 
 省略 ...
102 
103 } // anonymous namespace

映射表FreeingMemFnMap的数据类型为const CallDescriptionMap<CheckFn>。其中key的数据类型为CallDescription(这里构造函数的参数分别为"free"1,前者表示函数名称,后者表示函数的实参个数),value的数据类型为模板参数T(这里是CheckFn)。

需要注意的是, CallDescriptionMap<T>的内部是用std::vector<std::pair<CallDescription, T>>实现的。因此,其成员函数lookup()是通过线性遍历实现查找的。

2) 修改MallocMemAux()成员函数

137 ProgramStateRef
138 MallocChecker::MallocMemAux(const CallEvent &Call, CheckerContext &C,
139                             ProgramStateRef State,
140                             Optional<SVal> Init /*= None*/const {
141   if (!State) {
142     return nullptr;
143   }
144 
145   SymbolRef Sym = Call.getReturnValue().getAsSymbol();
146   if (!Sym) {
147     return nullptr;
148   }
149 
150   const Expr *CE = Call.getOriginExpr();
151   const unsigned Count = C.blockCount();
152   SValBuilder &SVB = C.getSValBuilder();
153   const LocationContext *LCtx = C.getPredecessor()->getLocationContext();
154   if (Optional<DefinedSVal> DV =
155       SVB.getConjuredHeapSymbolVal(CE, LCtx, Count).getAs<DefinedSVal>()) {
156     State = State->BindExpr(CE, C.getLocationContext(), *DV);
157     if (Init) {
158       State = State->bindDefaultInitial(*DV, *Init, LCtx);
159     }
160   }
161 
162   return State->set<RegionState>(Sym, Allocated);
163 }

相比于第一版plugin.unix.Malloc检查器的实现,MallocMemAux()成员函数做了如下两处修改:

C 程序中的calloc()函数在分配内存后会将其全部初始化为 0。为了能够在处理calloc()函数时复用MallocMemAux()函数。因此,需要增加一个数据类型为SVal的参数,用于表示内存的初始值。同时,为了避免不加区分地在处理malloc()函数时仍执行内存初始化操作:将所分配的内存初始化为未定义的值UndefinedVal()。因此,将该参数的数据类型设置为Optional<SVal>,并提供一个相当于空指针的默认值None

需要注意的是, 如果在处理calloc()函数时不将所分配的内存初始化为 0,那么在某些情况下会误报内存泄露程序缺陷。比如:

699 char callocZeroesGood () {
700         char *buf = calloc(2,2);
701         char result = buf[3]; // no-warning
702         if (buf[1] == 0) {
703           free(buf);
704         }
705         return result; // no-warning
706 }

误报的诊断结果如下:

malloc.c:705:9: warning: Potential leak of memory pointed to by 'buf' located at malloc.part.c:700:14 [plugin.unix.Malloc]
        return result; // no-warning
               ^~~~~~

如果两个内存来自不同的已知内存空间,那么它们不会相等。比如:一个指向栈内存的指针的值与一个指向堆内存的指针的值不会相等。因此,如果在条件表达式中出现两个来自不同已知内存空间的指针做比较,那么分析器引擎仅会建立这两个指针不相等的执行路径。所以,在处理malloc()等分配内存的函数时需要绑定符号所代表的内存是堆内存。从而,避免在某些情况下误报内存泄露程序缺陷。比如:

1341 void dependsOnValueOfPtr(int *g, unsigned f) {
1342   int *p;
1343 
1344   if (f) {
1345     p = g;
1346   } else {
1347     p = malloc(12);
1348   }
1349 
1350   if (p != g)
1351     free(p);
1352   else
1353     return// no warning
1354   return;
1355 }

误报的诊断结果如下:

malloc.c:1353:5: warning: Potential leak of memory pointed to by 'p' located at malloc.part.c:1347:9 [plugin.unix.Malloc]
    return// no warning
    ^~~~~~

3) 修改FreeMemAux()成员函数

172 ProgramStateRef
173 MallocChecker::FreeMemAux(const CallEvent &Call, CheckerContext &C,
174                           ProgramStateRef State,
175                           bool *IsKnownToBeAllocated /*= nullptr*/const {
176   if (!State) {
177     return nullptr;
178   }
179 
180   SymbolRef Sym = Call.getArgSVal(0).getAsSymbol(true);
181   if (!Sym) {
182     return nullptr;
183   }
184 
185   const auto K = State->get<RegionState>(Sym);
186   if (!K) {
187     return State->set<RegionState>(Sym, Released);
188   }
189   if (*K == Allocated) {
190     if (IsKnownToBeAllocated) {
191       *IsKnownToBeAllocated = true;
192     }
193     return State->set<RegionState>(Sym, Released);
194   }
195   if (*K == Released) {
196     reportDoubleFree(C);
197   }
198 
199   return nullptr;
200 }

相比于第一版plugin.unix.Malloc检查器的实现,FreeMemAux()成员函数做了如下两处修改:

如果将getAsSymbol()函数的参数设置为true,那么表示允许从符号的SuperRegion中查找符号表达式。从而,避免在某些情况下漏报内存泄露程序缺陷。比如:

1852 long *global_a;
1853 void realloc_crash() {
1854   long *c = global_a;
1855   c--;
1856   realloc(c, 8); // no-crash
1857 } // expected-warning{{Potential memory leak [unix.Malloc]}}

其漏报原因分析如下。

使用 gdb 调试上述示例时,查看realloc函数的第一个参数(即c--的计算结果)的符号:

(gdb) p Call.getArgSVal(0).dump()
&Element{SymRegion{reg_$0<long * global_a>},-1 S64b,long}$6 = void

从上面的结果看出,该符号实际由clang::ento::ElementRegion类表示,并作为另一个符号(由clang::ento::SymbolicRegion类表示)的元素,元素下标为 -1。

要获取符号SVal中所包裹的符号表达式SymbolRef,可以直接从clang::ento::SymbolicRegion类(持有一个数据类型为const SymbolRef的数据成员sym)的对象中获得,但不能直接从clang::ento::ElementRegion类的对象中获得。因此,对于上述示例,原获取符号表达式的方式会失败,即Sym的值为nullptr

4) 修改ReallocMemAux()成员函数

209 ProgramStateRef
210 MallocChecker::ReallocMemAux(const CallEvent &Call, CheckerContext &C,
211                              ProgramStateRef State) const {
212   if (!State) {
213     return nullptr;
214   }
215 
216   Optional<DefinedSVal> Arg0 = Call.getArgSVal(0).getAs<DefinedSVal>();
217   Optional<DefinedSVal> Arg1 = Call.getArgSVal(1).getAs<DefinedSVal>();
218   if (!Arg0 || !Arg1) {
219     return nullptr;
220   }
221 
222   ConstraintManager &CM = C.getConstraintManager();
223   ProgramStateRef StatePtrNotNull, StatePtrIsNull;
224   std::tie(StatePtrNotNull, StatePtrIsNull) = CM.assumeDual(State, *Arg0);
225   ProgramStateRef StateSizeNotZero, StateSizeIsZero;
226   std::tie(StateSizeNotZero, StateSizeIsZero) = CM.assumeDual(State, *Arg1);
227   const bool PtrIsNull = StatePtrIsNull && !StatePtrNotNull;
228   const bool SizeIsZero = StateSizeIsZero && !StateSizeNotZero;
229 
230   if (PtrIsNull) {
231     ProgramStateRef StateMalloc = MallocMemAux(Call, C, StatePtrIsNull);
232     return StateMalloc;
233   }
234 
235   if (!PtrIsNull && SizeIsZero) {
236     ProgramStateRef StateFree = FreeMemAux(Call, C, StateSizeIsZero);
237     return StateFree;
238   }
239 
240   bool IsKnownToBeAllocated = false;
241   if (ProgramStateRef StateFree
242       = FreeMemAux(Call, C, State, &IsKnownToBeAllocated)) {
243     ProgramStateRef StateRealloc = MallocMemAux(Call, C, StateFree);
244     if (!StateRealloc) {
245       return nullptr;
246     }
247 
248     SymbolRef FromPtr = Call.getArgSVal(0).getAsSymbol(true);
249     SymbolRef ToPtr = Call.getReturnValue().getAsSymbol();
250     assert(FromPtr && ToPtr &&
251            "By this point, FreeMemAux and MallocMemAux should have checked "
252            "whether the argument or the return value is symbolic!");
253     return StateRealloc->set<ReallocPairs>(ToPtr, ReallocPair(FromPtr,
254       IsKnownToBeAllocated ? ReallocPair::OAR_NeedToFreeAfterFailure
255                            : ReallocPair::OAR_DoNotTrackAfterFailure));
256   }
257 
258   return nullptr;
259 }

相比于第一版plugin.unix.Malloc检查器的实现,ReallocMemAux()成员函数做了如下两处修改:

结构体ReallocPair的定义如下:

 81 struct ReallocPair {
 82   enum OwnershipAfterReallocKind {
 83     OAR_NeedToFreeAfterFailure,
 84     OAR_DoNotTrackAfterFailure,
 85   };
 86 
 87   SymbolRef RealloctedSym;
 88   OwnershipAfterReallocKind Kind;
 89 
 90   ReallocPair(SymbolRef FromPtr, OwnershipAfterReallocKind K)
 91     : RealloctedSym(FromPtr), Kind(K) {}
 92 
 93   void Profile(llvm::FoldingSetNodeID &ID) const {
 94     ID.AddPointer(RealloctedSym);
 95     ID.AddInteger(Kind);
 96   }
 97 
 98   bool operator==(const ReallocPair &Other) const {
 99     return RealloctedSym == Other.RealloctedSym && Kind == Other.Kind;
100   }
101 };

除了保存FromPtr对应的符号之外(原来的做法只保存了该值),结构体ReallocPair 中还保存了FromPtr的状态。状态的可取值以及含义如下:

如果不这样区分(原来的做法未区分上述两种情况),那么在某些情况下会误报内存泄露程序缺陷。比如:

1496 // We should not warn in this case since the caller will presumably free a->p in all cases.
1497 int reallocButNoMallocPR13674(struct HasPtr *a, int c, int size) {
1498   int *s;
1499   char *b = realloc(a->p, size);
1500   if (b == 0)
1501     return -1;
1502   a->p = b;
1503   return 0;
1504 }

省略 ...

1695 void testReallocEscaped(void **memory) {
1696   *memory = malloc(47);
1697   char *new_memory = realloc(*memory, 47);
1698   if (new_memory != 0) {
1699     *memory = new_memory;
1700   }
1701 }

误报的诊断结果如下:

malloc.c:1501:13warningPotential memory leak [plugin.unix.Malloc]
    return -1;
            ^
省略 ...
malloc.c:1701:1warningPotential memory leak [plugin.unix.Malloc]
}
^

对应地,修改后的evalAssume()成员函数如下:

282 ProgramStateRef MallocChecker::evalAssume(ProgramStateRef State,
283                                           const SVal &Cond,
284                                           bool Assumption) const {
285   auto isNull = [&](ProgramStateRef State, SymbolRef Sym) {
286     // Return true if a symbol is NULL.
287     return State->getConstraintManager().isNull(State, Sym).isConstrainedTrue();
288   };
289 
290   for (const auto &TrackedRegion : State->get<RegionState>()) {
291     SymbolRef R = TrackedRegion.first;
292     if (isNull(State, R)) {
293       State = State->remove<RegionState>(R);
294     }
295   }
296 
297   for (const auto &ReallocPair : State->get<ReallocPairs>()) {
298     SymbolRef ToPtr = ReallocPair.first;
299     if (isNull(State, ToPtr)) {
300       State = State->remove<ReallocPairs>(ToPtr);
301       SymbolRef FromPtr = ReallocPair.second.RealloctedSym;
302       switch (ReallocPair.second.Kind) {
303         case ReallocPair::OAR_NeedToFreeAfterFailure:
304           State = State->set<RegionState>(FromPtr, Allocated);
305           break;
306         case ReallocPair::OAR_DoNotTrackAfterFailure:
307           State = State->remove<RegionState>(FromPtr);
308           break;
309         default:
310           llvm_unreachable("Unkonwn OwnershipAfterReallocKind.");
311       }
312     }
313   }
314 
315   return State;
316 }

5) 实现checkCalloc()成员函数

273 void MallocChecker::checkCalloc(const CallEvent &Call,
274                                 CheckerContext &C) const {
275   ProgramStateRef State = C.getState();
276   SValBuilder &SVB = C.getSValBuilder();
277   SVal ZeroVal = SVB.makeZeroVal(SVB.getContext().CharTy);
278   State = MallocMemAux(Call, C, State, ZeroVal);
279   C.addTransition(State);
280 }

上述代码的逻辑为:

6) 实现checkPointerEscape()成员函数

428 ProgramStateRef
429 MallocChecker::checkPointerEscape(ProgramStateRef State,
430                                   const InvalidatedSymbols &Escaped,
431                                   const CallEvent *Call,
432                                   PointerEscapeKind Kind) const {
433   if (Kind == PSK_DirectEscapeOnCall &&
434       !mayFreeAnyEscapedMemoryOrIsModeledExplicitly(Call)) {
435     return State;
436   }
437 
438   for (SymbolRef Sym : Escaped) {
439     State = State->remove<RegionState>(Sym);
440     State = State->remove<ReallocPairs>(Sym);
441   }
442   return State;
443 }

上述代码的逻辑为:

要检测内存泄露,为什么需要处理符号发生PointerEscape的情形。接下来通过如下示例说明其必要性。

733 void mallocEscapeFoo() {
734   int *p = malloc(12);
735   myfoo(p);
736   return// no warning
737 }

上述示例中的函数myfoo()是用户自定义的。如果该函数的源码实现是不透明的,那么分析器引擎无法确定其是否会释放实参p所指向的内存。在该情况下,要么假设用户自定义函数会释放内存,要么假设用户自定义函数不会释放内存。前者可以避免误报,但可能发生漏报。而后者虽然不会漏报,但可能产生误报。

由于 Clang 静态分析器采用的是前者。因此,需要处理符号发生PointerEscape的情形。从而,可以在分析器引擎无法有效地跟踪符号时移除相关的自定义状态以避免误报内存泄露程序缺陷。

需要注意的是, 如果可以确定哪些用户自定义函数一定会释放内存或者一定不会释放内存,那么即使这些函数的源码实现是不透明的,也可以在checkPointerEscape()函数中对这些函数进行特殊处理。从而,既不漏报也不误报内存泄露程序缺陷。

a) 实现mayFreeAnyEscapedMemoryOrIsModeledExplicitly()成员函数

445 bool MallocChecker::mayFreeAnyEscapedMemoryOrIsModeledExplicitly(
446                                               const CallEvent *Call) const {
447   assert(Call);
448 
449   if (isMemCall(*Call)) {
450     return false;
451   }
452 
453   if (!Call->isInSystemHeader()) {
454     return true;
455   }
456 
457   if (Call->isCalled(FunOpenFn)) {
458     if (Call->getNumArgs() >= 4 && Call->getArgSVal(4).isConstant(0)) {
459       return false;
460     }
461   }
462 
463   if (Call->argumentsMayEscape()) {
464     return true;
465   }
466 
467   return false;
468 }

上述代码的逻辑为:

b) 实现isMemCall()成员函数

470 bool MallocChecker::isMemCall(const CallEvent &Call) const {
471   if (!Call.isGlobalCFunction()) {
472     return false;
473   }
474 
475   if (FreeingMemFnMap.lookup(Call) || AllocatingMemFnMap.lookup(Call) ||
476       ReallocatingMemFnMap.lookup(Call)) {
477     return true;
478   }
479 
480   return false;
481 }

7) 实现checkDeadSymbols()成员函数

318 void MallocChecker::checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const {
319   auto isNotNull = [&](ProgramStateRef State, SymbolRef Sym) {
320     // Return true if a symbol is not NULL.
321     return !State->getConstraintManager().isNull(State, Sym).isConstrainedTrue();
322   };
323 
324   ProgramStateRef State = C.getState();
325 
326   SymbolVector LeakedSyms;
327   for (const auto &TrackedRegion : State->get<RegionState>()) {
328     SymbolRef Sym = TrackedRegion.first;
329     if (SR.isDead(Sym)) {
330       if (TrackedRegion.second == Allocated && isNotNull(State, Sym)) {
331         LeakedSyms.push_back(Sym);
332       }
333       State = State->remove<RegionState>(Sym);
334     }
335   }
336 
337   for (const auto &ReallocPair : State->get<ReallocPairs>()) {
338     if (SR.isDead(ReallocPair.first)) {
339       State = State->remove<ReallocPairs>(ReallocPair.first);
340     }
341   }
342 
343   if (LeakedSyms.empty()) {
344     return;
345   }
346 
347   if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
348     reportLeaks(LeakedSyms, C, N);
349     C.addTransition(State, N);
350   }
351 }

上述代码的逻辑为:

需要注意的是, 上述代码在第 344 行直接返回了,即实际上未将程序状态State(可能发生变化)更新到执行环境中。如果在返回之前使用C.addTransition(State)更新程序状态,那么在某些情况下会遗漏详细的诊断信息。比如:

179 void reallocRadar6337483_3() {
180     char * buf = malloc(100);
181     char * tmp;
182     tmp = (char*)realloc(buf, 0x1000000);
183     if (!tmp) {
184         free(buf);
185         return;
186     }
187     buf = tmp;
188     free(buf);
189 }

省略 ..

191 void reallocRadar6337483_4() {
192     char *buf = malloc(100);
193     char *buf2 = (char*)realloc(buf, 0x1000000);
194     if (!buf2) {
195       return;  // expected-warning {{Potential leak of memory pointed to by}}
196     } else {
197       free(buf2);
198     }
199 }

诊断结果如下(遗漏了详细的诊断信息):

malloc.c:177:1: warning: Potential memory leak [plugin.unix.Malloc]
// expected-warning {{Potential leak of memory pointed to by}}
^
malloc.c:195:7: warning: Potential memory leak [plugin.unix.Malloc]
      return;  // expected-warning {{Potential leak of memory pointed to by}}
      ^~~~~~

而期望的诊断结果如下:

malloc.c:177:1: warning: Potential leak of memory pointed to by 'buf' located at malloc.c:170:17 [plugin.unix.Malloc]
// expected-warning {{Potential leak of memory pointed to by}}
^
malloc.c:195:7: warning: Potential leak of memory pointed to by 'buf' located at malloc.c:192:17 [plugin.unix.Malloc]
      return;  // expected-warning {{Potential leak of memory pointed to by}}
      ^~~~~~

另外,需要注意的是, 上述代码在第 349 行使用C.addTransition(State, N)更新程序状态。

如果在报告内存泄露程序缺陷后不更新程序状态或者使用C.addTransition(State)更新程序状态,那么在某些情况下会误报内存泄露程序缺陷。比如:

1004 extern void exit(int) __attribute__ ((__noreturn__));
1005 void mallocExit(int *g{
1006   struct xx *p = malloc(12);
1007   if (g != 0)
1008     exit(1);
1009   free(p);
1010   return;
1011 }
1012 
1013 extern void __assert_fail (__const char *__assertion, __const char *__file,
1014     unsigned int __line, __const char *__function)
1015      __attribute__ ((__noreturn__));
1016 #define assert(expr) \
1017   ((expr)  ? (void)(0)  : __assert_fail (#expr, __FILE__, __LINE__, __func__))
1018 void mallocAssert(int *g{
1019   struct xx *p = malloc(12);
1020 
1021   assert(g != 0);
1022   free(p);
1023   return;
1024 }

误报的诊断结果如下:

malloc.c:1011:1: warning: Potential leak of memory pointed to by 'p' located at malloc.c:1006:18 [plugin.unix.Malloc]
}
^
malloc.c:1024:1: warning: Potential leak of memory pointed to by 'p' located at malloc.c:1019:18 [plugin.unix.Malloc]
}
^

8) 报告程序缺陷

a) 定义相关数据成员和成员函数

 10 namespace {
 11 
 省略 ...
 18 
 19 class MallocChecker : public Checker<check::PostCall, eval::Assume,
 20                                      check::DeadSymbols, check::PointerEscape> {
 省略 ...
 22   mutable std::unique_ptr<BugType> LeakBT;
 省略 ...
 79 };
 80 
 省略 ...
102 
103 } // anonymous namespace

我们需要定义一个数据类型为std::unique_ptr<BugType>的数据成员(这里是LeakBT),用于表示一个新的程序缺陷。为了在对象析构时能够自动释放内存,因此使用了智能指针。

另外,我们将报告该程序缺陷的处理逻辑封装到成员函数reportLeaks()中。

b) 实现程序缺陷报告逻辑

353 void MallocChecker::reportLeaks(ArrayRef<SymbolRef> LeakedSyms,
354                                 CheckerContext &C,
355                                 ExplodedNode *N) const {
356   assert(N);
357 
358   if (!LeakBT) {
359     LeakBT.reset(new BugType(this"Memory Leak""Memory Error",
360       /*SuppressOnSink=*/true));
361   }
362 
363   for (const auto &LeakedSym : LeakedSyms) {
364     PathDiagnosticLocation LocUsedForUniqueing;
365     const ExplodedNode *AllocNode = nullptr;
366     const MemRegion *Region = nullptr;
367     std::tie(AllocNode, Region) = getAllocationSite(N, LeakedSym, C);
368 
369     const Stmt *AllocationStmt = AllocNode->getStmtForDiagnostics();
370     if (AllocationStmt) {
371       LocUsedForUniqueing = PathDiagnosticLocation::createBegin(AllocationStmt,
372         C.getSourceManager(), AllocNode->getLocationContext());
373     }
374 
375     SmallString<200> buf;
376     llvm::raw_svector_ostream os(buf);
377     if (Region && Region->canPrintPretty()) {
378       os << "Potential leak of memory pointed to by ";
379       Region->printPretty(os);
380       os << " located at ";
381       LocUsedForUniqueing.asLocation().print(os, C.getSourceManager());
382     }
383     else {
384       os << "Potential memory leak";
385     }
386 
387     auto R = std::make_unique<PathSensitiveBugReport>(*LeakBT, os.str(), N,
388       LocUsedForUniqueing, AllocNode->getLocationContext()->getDecl());
389     C.emitReport(std::move(R));
390   }
391 }

上述代码的逻辑为:

如果将第 364~388 行修改为如下代码:

auto R = std::make_unique<PathSensitiveBugReport>(
  *LeakBT, LeakBT->getDescription(), N);

那么在某些情况下会漏报内存泄露。比如:

791 void mallocMalloc() {
792   int *p = malloc(12);
793   p = malloc(12);
794 } // expected-warning {{Potential leak of memory pointed to by}}\
795   // expected-warning {{Potential leak of memory pointed to by}}

诊断结果如下(漏报了一个内存泄露):

malloc.c:794:1: warning: Memory Leak [plugin.unix.Malloc]
// expected-warning {{Potential leak of memory pointed to by}}\

而期望的诊断结果如下:

malloc.c:794:1: warning: Potential leak of memory pointed to by 'p' located at malloc.c:792:12 [plugin.unix.Malloc]
// expected-warning {{Potential leak of memory pointed to by}}\
^
malloc.c:794:1: warning: Potential leak of memory pointed to by 'p' located at malloc.c:793:7 [plugin.unix.Malloc]

c) 实现getAllocationSite()成员函数

393 MallocChecker::LeakInfo
394 MallocChecker::getAllocationSite(const ExplodedNode *N, SymbolRef Sym,
395                                  CheckerContext &C) {
396   const LocationContext *LeakContext = N->getLocationContext();
397   const ExplodedNode *AllocNode = N;
398   const MemRegion *ReferenceRegion = nullptr;
399 
400   while (N) {
401     ProgramStateRef State = N->getState();
402     if (!State->get<RegionState>(Sym)) {
403       break;
404     }
405 
406     if (!ReferenceRegion) {
407       if (const MemRegion *MR = C.getLocationRegionIfPostStore(N)) {
408         SVal Val = State->getSVal(MR);
409         if (Val.getAsLocSymbol() == Sym) {
410           const VarRegion *VR = MR->getBaseRegion()->getAs<VarRegion>();
411           if (!VR || (VR->getStackFrame() == LeakContext->getStackFrame())) {
412             ReferenceRegion = MR;
413           }
414         }
415       }
416     }
417 
418     const LocationContext *NContext = N->getLocationContext();
419     if (NContext == LeakContext || NContext->isParentOf(LeakContext)) {
420       AllocNode = N;
421     }
422     N = N->pred_empty() ? nullptr : *(N->pred_begin());
423   }
424 
425   return LeakInfo(AllocNode, ReferenceRegion);
426 }

step 3: 构建测试

1) 构建

注:具体过程可参考笔者的另一篇文章:《LLVM 之 Clang 静态分析器篇(2):如何扩展 Clang 静态分析器》。

2) 诊断结果对比

对于同一个测试文件malloc.c,分别运行自己实现的检查器plugin.unix.malloc和 Clang 静态分析器实现的检查器unix.malloc

1) 运行检查器plugin.unix.malloc

执行命令:

$ clang -cc1 -w -load ~/git-projects/llvm-project/build_ninja/lib/MallocCheckerPlugin.so -analyze -analyzer-checker=plugin.unix.Malloc -analyzer-checker=unix.cstring.CStringModeling -I ~/git-projects/llvm-project/clang/test/Analysis/ malloc.c

需要注意的是, 在运行检查器plugin.unix.malloc的同时必须启用unix.cstring.CStringModeling检查器以对memcpymemcmp等函数的行为进行建模。从而,避免在某些情况下漏报内存泄露的程序缺陷。比如:

1544 void testPassConstPointerIndirectlyStruct() {
1545   struct HasPtr hp;
1546   hp.p = malloc(10);
1547   memcmp(&hp, &hp, sizeof(hp));
1548   return// expected-warning {{Potential leak of memory pointed to by 'hp.p'}}
1549 }

输出结果如下(只保留内存泄露程序缺陷):

malloc.c:51:3: warning: Potential leak of memory pointed to by 'p' located at malloc.c:50:12 [plugin.unix.Malloc]
  return// expected-warning{{Potential leak of memory pointed to by 'p'}}
  ^~~~~~
省略 ...
malloc.c:76:5: warning: Potential leak of memory pointed to by 'q' located at malloc.c:75:22 [plugin.unix.Malloc]
    char x = *q; // expected-warning {{Potential leak of memory pointed to by 'q'}}
    ^~~~~~
省略 ...
malloc.c:143:1: warning: Potential leak of memory pointed to by 'r' located at malloc.c:142:13 [plugin.unix.Malloc]
}
^
malloc.c:147:1: warning: Potential leak of memory pointed to by 'r' located at malloc.c:146:13 [plugin.unix.Malloc]
// expected-warning {{Potential leak of memory pointed to by 'r'}}
^
malloc.c:164:9: warning: Potential leak of memory pointed to by 'buf' located at malloc.c:161:17 [plugin.unix.Malloc]
        return;// expected-warning {{Potential leak of memory pointed to by}}
        ^~~~~~
malloc.c:177:1: warning: Potential leak of memory pointed to by 'buf' located at malloc.c:170:17 [plugin.unix.Malloc]
// expected-warning {{Potential leak of memory pointed to by}}
^
malloc.c:195:7: warning: Potential leak of memory pointed to by 'buf' located at malloc.c:192:17 [plugin.unix.Malloc]
      return;  // expected-warning {{Potential leak of memory pointed to by}}
      ^~~~~~
malloc.c:283:1: warning: Potential leak of memory pointed to by 'p' located at malloc.c:281:12 [plugin.unix.Malloc]
}
^
malloc.c:449:1: warning: Potential leak of memory pointed to by 'x' located at malloc.c:448:12 [plugin.unix.Malloc]
}
^
malloc.c:454:1: warning: Potential leak of memory pointed to by 'buf' located at malloc.c:452:14 [plugin.unix.Malloc]
}
^
malloc.c:695:3: warning: Potential leak of memory pointed to by 'buf' located at malloc.c:694:15 [plugin.unix.Malloc]
  return// expected-warning{{Potential leak of memory pointed to by 'buf'}}
  ^~~~~~
malloc.c:714:9: warning: Potential leak of memory pointed to by 'buf' located at malloc.c:709:14 [plugin.unix.Malloc]
        return result; // expected-warning{{Potential leak of memory pointed to by 'buf'}}
               ^~~~~~
省略 ...
malloc.c:789:1: warning: Potential leak of memory pointed to by 'p' located at malloc.c:788:7 [plugin.unix.Malloc]
// expected-warning{{Potential leak of memory pointed to by}}
^
malloc.c:794:1: warning: Potential leak of memory pointed to by 'p' located at malloc.c:792:12 [plugin.unix.Malloc]
// expected-warning {{Potential leak of memory pointed to by}}\
^
malloc.c:794:1: warning: Potential leak of memory pointed to by 'p' located at malloc.c:793:7 [plugin.unix.Malloc]
malloc.c:854:5: warning: Potential leak of memory pointed to by 'p' located at malloc.c:850:12 [plugin.unix.Malloc]
    return// expected-warning {{Potential leak of memory pointed to by}}
    ^~~~~~
malloc.c:860:1: warning: Potential leak of memory pointed to by 'p' located at malloc.c:858:13 [plugin.unix.Malloc]
// expected-warning {{leak}}
^
malloc.c:864:3: warning: Potential leak of memory pointed to by 'mem' located at malloc.c:863:15 [plugin.unix.Malloc]
  return 0// expected-warning {{Potential leak of memory pointed to by}}
  ^~~~~~~~
malloc.c:937:3: warning: Potential leak of memory pointed to by 'St.memP' located at malloc.c:936:13 [plugin.unix.Malloc]
  return// expected-warning {{Potential leak of memory pointed to by 'St.memP'}}
  ^~~~~~
malloc.c:987:3: warning: Potential leak of memory pointed to by 'px' located at malloc.c:984:17 [plugin.unix.Malloc]
  return px; // expected-warning {{Potential leak of memory pointed to by}}
  ^~~~~~~~~
malloc.c:1001:3: warning: Potential leak of memory pointed to by 'p' located at malloc.c:998:12 [plugin.unix.Malloc]
  return 0;// expected-warning {{Potential leak of memory pointed to by}}
  ^~~~~~~~
malloc.c:1035:1: warning: Potential leak of memory pointed to by 'p' located at malloc.c:1027:13 [plugin.unix.Malloc]
// expected-warning {{leak}}
^
malloc.c:1060:3: warning: Potential leak of memory pointed to by 'p' located at malloc.c:1057:19 [plugin.unix.Malloc]
  int *ptrs1[2]; // expected-warning {{Potential leak of memory pointed to by}}
  ^~~~~~~~~~
malloc.c:1064:3: warning: Potential memory leak [plugin.unix.Malloc]
  int *ptrs2[2]; // expected-warning {{Potential memory leak}}
  ^~~~~~~~~~
malloc.c:1068:3: warning: Potential memory leak [plugin.unix.Malloc]
  int *ptrs3[2]; // expected-warning {{Potential memory leak}}
  ^~~~~~~~~~
malloc.c:1071:1: warning: Potential memory leak [plugin.unix.Malloc]
// expected-warning {{Potential memory leak}}
^
malloc.c:1199:12: warning: Potential leak of memory pointed to by 'ctx' located at malloc.c:1194:17 [plugin.unix.Malloc]
    return f; // expected-warning{{leak}}
           ^
malloc.c:1211:10: warning: Potential leak of memory pointed to by 'ctx' located at malloc.c:1206:15 [plugin.unix.Malloc]
  return f; // expected-warning{{leak}}
         ^
malloc.c:1218:5: warning: Potential leak of memory pointed to by 'p' located at malloc.c:1216:15 [plugin.unix.Malloc]
    return 0;
    ^~~~~~~~
malloc.c:1223:5: warning: Potential leak of memory pointed to by 'p' located at malloc.c:1221:15 [plugin.unix.Malloc]
    return 0;
    ^~~~~~~~
malloc.c:1228:5: warning: Potential leak of memory pointed to by 'p' located at malloc.c:1226:15 [plugin.unix.Malloc]
    return 0;// expected-warning {{leak}}
    ^~~~~~~~
malloc.c:1262:5: warning: Potential leak of memory pointed to by 'buffer' located at malloc.c:1256:14 [plugin.unix.Malloc]
    return// expected-warning {{leak}}
    ^~~~~~
malloc.c:1277:3: warning: Potential leak of memory pointed to by 'p' located at malloc.c:1275:60 [plugin.unix.Malloc]
  return p->n.m; // expected-warning {{leak}}
  ^~~~~~~~~~~~~
malloc.c:1300:1: warning: Potential leak of memory pointed to by 'ptr' located at malloc.c:1298:15 [plugin.unix.Malloc]
// expected-warning {{leak}}
^
malloc.c:1315:1: warning: Potential leak of memory pointed to by 'x' located at malloc.c:1313:13 [plugin.unix.Malloc]
// expected-warning {{leak}}
^
malloc.c:1416:1: warning: Potential memory leak [plugin.unix.Malloc]
// expected-warning {{leak}}
^
malloc.c:1422:1: warning: Potential leak of memory pointed to by 'St.memP' located at malloc.c:1421:15 [plugin.unix.Malloc]
// expected-warning{{Potential leak of memory pointed to by}}
^
malloc.c:1440:1: warning: Potential leak of memory pointed to by 'ptr' located at malloc.c:1438:15 [plugin.unix.Malloc]
// expected-warning {{leak}}
^
省略 ...
malloc.c:1534:3: warning: Potential leak of memory pointed to by 'string' located at malloc.c:1532:19 [plugin.unix.Malloc]
  return// expected-warning {{leak}}
  ^~~~~~
malloc.c:1541:3: warning: Potential leak of memory pointed to by 'p' located at malloc.c:1538:13 [plugin.unix.Malloc]
  return// expected-warning {{leak}}
  ^~~~~~
malloc.c:1548:3: warning: Potential leak of memory pointed to by 'hp.p' located at malloc.c:1546:10 [plugin.unix.Malloc]
  return// expected-warning {{Potential leak of memory pointed to by 'hp.p'}}
  ^~~~~~
省略 ...
malloc.c:1665:3: warning: Potential leak of memory pointed to by 'string' located at malloc.c:1663:19 [plugin.unix.Malloc]
  int length = strlen(string); // expected-warning {{Potential leak of memory pointed to by 'string'}}
  ^~~~~~~~~~
malloc.c:1744:1: warning: Potential leak of memory pointed to by 'data' located at malloc.c:1742:22 [plugin.unix.Malloc]
//expected-warning{{Potential leak}}
^
malloc.c:1796:1: warning: Potential leak of memory pointed to by 'ptr' located at malloc.c:1795:3 [plugin.unix.Malloc]
// expected-warning {{Potential leak of memory pointed to by 'ptr'}}
^
malloc.c:1857:1: warning: Potential memory leak [plugin.unix.Malloc]
// expected-warning{{Potential memory leak [unix.Malloc]}}
^
54 warnings generated.

从上面的输出结果可以看出,检查器plugin.unix.malloc检测到malloc.c文件中存在 45 个内存泄露程序缺陷。

2) 运行检查器unix.malloc

执行命令:

$ clang -cc1 -w -analyze -analyzer-checker=unix.Malloc malloc.c -I ~/git-projects/llvm-project/clang/test/Analysis/

输出结果如下(只保留内存泄露程序缺陷):

malloc.c:51:3: warning: Potential leak of memory pointed to by 'p' [unix.Malloc]
  return// expected-warning{{Potential leak of memory pointed to by 'p'}}
  ^~~~~~
省略 ...
malloc.c:76:5: warning: Potential leak of memory pointed to by 'q' [unix.Malloc]
    char x = *q; // expected-warning {{Potential leak of memory pointed to by 'q'}}
    ^~~~~~
省略 ...
malloc.c:147:1: warning: Potential leak of memory pointed to by 'r' [unix.Malloc]
// expected-warning {{Potential leak of memory pointed to by 'r'}}
^
malloc.c:164:9: warning: Potential leak of memory pointed to by 'buf' [unix.Malloc]
        return;// expected-warning {{Potential leak of memory pointed to by}}
        ^~~~~~
malloc.c:177:1: warning: Potential leak of memory pointed to by 'buf' [unix.Malloc]
// expected-warning {{Potential leak of memory pointed to by}}
^
malloc.c:195:7: warning: Potential leak of memory pointed to by 'buf' [unix.Malloc]
      return;  // expected-warning {{Potential leak of memory pointed to by}}
      ^~~~~~
省略 ...
malloc.c:231:1: warning: Potential leak of memory pointed to by 'r' [unix.Malloc]
// expected-warning {{Potential leak of memory pointed to by}}
^
省略 ...
malloc.c:449:1: warning: Potential leak of memory pointed to by 'x' [unix.Malloc]
}
^
malloc.c:454:1: warning: Potential leak of memory pointed to by 'buf' [unix.Malloc]
}
^
malloc.c:695:3: warning: Potential leak of memory pointed to by 'buf' [unix.Malloc]
  return// expected-warning{{Potential leak of memory pointed to by 'buf'}}
  ^~~~~~
malloc.c:714:9: warning: Potential leak of memory pointed to by 'buf' [unix.Malloc]
        return result; // expected-warning{{Potential leak of memory pointed to by 'buf'}}
               ^~~~~~
省略 ...
malloc.c:789:1: warning: Potential leak of memory pointed to by 'p' [unix.Malloc]
// expected-warning{{Potential leak of memory pointed to by}}
^
malloc.c:794:1: warning: Potential leak of memory pointed to by 'p' [unix.Malloc]
// expected-warning {{Potential leak of memory pointed to by}}\
^
malloc.c:794:1: warning: Potential leak of memory pointed to by 'p' [unix.Malloc]
省略 ...
malloc.c:854:5: warning: Potential leak of memory pointed to by 'p' [unix.Malloc]
    return// expected-warning {{Potential leak of memory pointed to by}}
    ^~~~~~
malloc.c:860:1: warning: Potential leak of memory pointed to by 'p' [unix.Malloc]
// expected-warning {{leak}}
^
malloc.c:864:3: warning: Potential leak of memory pointed to by 'mem' [unix.Malloc]
  return 0// expected-warning {{Potential leak of memory pointed to by}}
  ^~~~~~~~
省略 ...
malloc.c:937:3: warning: Potential leak of memory pointed to by 'St.memP' [unix.Malloc]
  return// expected-warning {{Potential leak of memory pointed to by 'St.memP'}}
  ^~~~~~
malloc.c:987:3: warning: Potential leak of memory pointed to by 'px' [unix.Malloc]
  return px; // expected-warning {{Potential leak of memory pointed to by}}
  ^~~~~~~~~
malloc.c:1001:3: warning: Potential leak of memory pointed to by 'p' [unix.Malloc]
  return 0;// expected-warning {{Potential leak of memory pointed to by}}
  ^~~~~~~~
malloc.c:1035:1: warning: Potential leak of memory pointed to by 'p' [unix.Malloc]
// expected-warning {{leak}}
^
malloc.c:1060:3: warning: Potential leak of memory pointed to by 'p' [unix.Malloc]
  int *ptrs1[2]; // expected-warning {{Potential leak of memory pointed to by}}
  ^~~~~~~~~~
malloc.c:1064:3: warning: Potential memory leak [unix.Malloc]
  int *ptrs2[2]; // expected-warning {{Potential memory leak}}
  ^~~~~~~~~~
malloc.c:1068:3: warning: Potential memory leak [unix.Malloc]
  int *ptrs3[2]; // expected-warning {{Potential memory leak}}
  ^~~~~~~~~~
malloc.c:1071:1: warning: Potential memory leak [unix.Malloc]
// expected-warning {{Potential memory leak}}
^
malloc.c:1118:1: warning: Potential leak of memory pointed to by 's2' [unix.Malloc]
// expected-warning {{Potential leak of memory pointed to by}}
^
malloc.c:1123:1: warning: Potential leak of memory pointed to by 's2' [unix.Malloc]
// expected-warning {{Potential leak of memory pointed to by}}
^
malloc.c:1128:1: warning: Potential leak of memory pointed to by 's2' [unix.Malloc]
// expected-warning {{Potential leak of memory pointed to by}}
^
malloc.c:1133:1: warning: Potential leak of memory pointed to by 's2' [unix.Malloc]
// expected-warning {{Potential leak of memory pointed to by}}
^
malloc.c:1141:12: warning: Potential leak of memory pointed to by 's2' [unix.Malloc]
    return 1;// expected-warning {{Potential leak of memory pointed to by}}
           ^
malloc.c:1199:12: warning: Potential leak of memory pointed to by 'ctx' [unix.Malloc]
    return f; // expected-warning{{leak}}
           ^
malloc.c:1211:10: warning: Potential leak of memory pointed to by 'ctx' [unix.Malloc]
  return f; // expected-warning{{leak}}
         ^
malloc.c:1228:5: warning: Potential leak of memory pointed to by 'p' [unix.Malloc]
    return 0;// expected-warning {{leak}}
    ^~~~~~~~
malloc.c:1262:5: warning: Potential leak of memory pointed to by 'buffer' [unix.Malloc]
    return// expected-warning {{leak}}
    ^~~~~~
malloc.c:1277:3: warning: Potential leak of memory pointed to by 'p' [unix.Malloc]
  return p->n.m; // expected-warning {{leak}}
  ^~~~~~~~~~~~~
malloc.c:1300:1: warning: Potential leak of memory pointed to by 'ptr' [unix.Malloc]
// expected-warning {{leak}}
^
malloc.c:1315:1: warning: Potential leak of memory pointed to by 'x' [unix.Malloc]
// expected-warning {{leak}}
^
malloc.c:1416:1: warning: Potential memory leak [unix.Malloc]
// expected-warning {{leak}}
^
malloc.c:1422:1: warning: Potential leak of memory pointed to by 'St.memP' [unix.Malloc]
// expected-warning{{Potential leak of memory pointed to by}}
^
malloc.c:1440:1: warning: Potential leak of memory pointed to by 'ptr' [unix.Malloc]
// expected-warning {{leak}}
^
省略 ...
malloc.c:1518:3: warning: Potential memory leak [unix.Malloc]
  return strdup(strdup(str)); // expected-warning{{leak}}
  ^~~~~~~~~~~~~~~~~~~~~~~~~~
malloc.c:1522:3: warning: Potential memory leak [unix.Malloc]
  return _strdup(_strdup(str)); // expected-warning{{leak}}
  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
malloc.c:1526:3: warning: Potential memory leak [unix.Malloc]
  return _wcsdup(_wcsdup(str)); // expected-warning{{leak}}
  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
malloc.c:1534:3: warning: Potential leak of memory pointed to by 'string' [unix.Malloc]
  return// expected-warning {{leak}}
  ^~~~~~
malloc.c:1541:3: warning: Potential leak of memory pointed to by 'p' [unix.Malloc]
  return// expected-warning {{leak}}
  ^~~~~~
malloc.c:1548:3: warning: Potential leak of memory pointed to by 'hp.p' [unix.Malloc]
  return// expected-warning {{Potential leak of memory pointed to by 'hp.p'}}
  ^~~~~~
省略 ...
malloc.c:1665:3: warning: Potential leak of memory pointed to by 'string' [unix.Malloc]
  int length = strlen(string); // expected-warning {{Potential leak of memory pointed to by 'string'}}
  ^~~~~~~~~~
省略 ...
malloc.c:1744:1: warning: Potential leak of memory pointed to by 'data' [unix.Malloc]
//expected-warning{{Potential leak}}
^
省略 ...
malloc.c:1796:1: warning: Potential leak of memory pointed to by 'ptr' [unix.Malloc]
// expected-warning {{Potential leak of memory pointed to by 'ptr'}}
^
省略 ...
malloc.c:1857:1: warning: Potential memory leak [unix.Malloc]
// expected-warning{{Potential memory leak [unix.Malloc]}}
^
95 warnings generated.

从上面的输出结果可以看出,检查器unix.malloc检测到malloc.c文件中存在 50 个内存泄露程序缺陷。

将上述两个诊断结果进行对比,两个检查器的差异如下表所示。

编号 报告的内存泄露在文件中的位置(行号) plugin.unix.malloc 是否报告了 unix.malloc 是否报告了
1 231
2 1118、1123、1128、1133、1141、1518、1522、1526
3 143、283
4 1218、1223

plugin.unix.malloc检查器目前的实现未对reallocfstrdupwcsdupstrndup_strdup_wcsdup函数的行为进行建模。因此,会漏报编号为 1、2 两处的内存泄露程序缺陷。除此之外,也未对setbuf函数的行为进行建模。因此,会误报编号为 4 处的内存泄露程序缺陷。

unix.malloc检查器漏报了编号为 3 处的内存泄露程序缺陷。

141 void reallocSizeZero5() {
142   char *r = realloc(00);
143 }

省略 ...

276 void UseZeroAllocated(int *p) {
277   if (p)
278     *p = 7// expected-warning {{Use of zero-allocated memory}}
279 }
280 void CheckUseZeroAllocated3() {
281   int *p = malloc(0);
282   UseZeroAllocated(p); 
283 }

上述两个示例都会分配一个大小为 0 字节的内存。由于实际分配的内存除了用户数据(这里为 0 字节)以外,还有chunk相关的开销占用。因此,严格意义上讲,这两个示例都会发生内存泄露程序缺陷。

返回上一级


研究结论

要实现用于检测 C 程序中内存泄露程序缺陷的功能,我们需要对 C 程序中的 API(比如:malloccallocreallocfree等)以及相关函数(比如:memcpymemcmp等)的行为进行建模。并且,在符号被回收时检测是否发生了内存泄露程序缺陷。

Clang 静态分析器假设大多数系统函数不会释放内存。如果确定哪些系统函数一定会释放内存,也可以对这些函数进行特殊处理。从而,既不漏报也不误报内存泄露程序缺陷。

对于源码实现不透明的用户自定义函数,Clang 静态分析器假设这些用户自定义函数会释放内存。这样的做法,虽然可以避免误报,但可能发生漏报。如果可以确定哪些用户自定义函数一定会释放内存或者一定不会释放内存,那么即使这些函数的源码实现是不透明的,也可以对这些函数进行特殊处理。从而,既不漏报也不误报内存泄露程序缺陷。

除此之外,我们需要决定哪些情况下应该处理发生PointerEscape的符号。从而,避免某些情况下误报内存泄露程序缺陷。


References


下一篇:LLVM 之 Clang 静态分析器篇(8):程序缺陷诊断——非法读写已释放的内存

上一篇:LLVM 之 Clang 静态分析器篇(6):程序缺陷诊断——内存重复释放

首页