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
检查器的源码实现目录如下:
clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
plugin.unix.Malloc
检查器的源码实现目录如下:
clang/lib/Analysis/plugins/CheckerMalloc/MallocChecker.cpp
本节介绍了几个 C 程序中典型的内存泄露示例,涉及malloc
、free
、realloc
和calloc
函数。这些示例均来自 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);
会被调用,可以分为以下两种情况:
如果realloc()
函数重新分配内存成功,那么p
所指向的内存会被释放,但是局部变量q
所指向的内存不会被释放。也就是说,在该情况下会发生内存泄露。
如果realloc()
函数重新分配内存失败,那么局部变量q
的值将为NULL
,而p
仍指向原内存且该内存不会被释放。也就是说,在该情况下会发生进程崩溃,也会发生内存泄露。
示例 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);
会被调用,可以分为以下两种情况:
如果realloc()
函数重新分配内存成功,那么buf
所指向的原内存(大小为 100 字节)会被释放,并且buf
所指向的新内存(大小为 0x1000000 字节)也会被释放。
如果realloc()
函数重新分配内存失败,那么buf
的值将为NULL
,并且大小为 100 字节的内存(无任何变量指向该内存)不会被释放。也就是说,在该情况下会发生内存泄露。
示例 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::DeadSymbols
和clang::ento::check::PointerEscape
程序点。
clang::ento::check::DeadSymbols
,表示分析器引擎对任意符号进行垃圾回收(即在之后的分析过程中不会再遇到该符号)时就会调用检查器的程序点。这意味着,如果启用了该检查器,那么MallocChecker::checkDeadSymbols()
函数在每个符号被回收时都会被调用。
clang::ento::check::PointerEscape
,表示分析器引擎无法有效地跟踪符号时就会调用检查器的程序点。这意味着,如果启用了该检查器,那么MallocChecker::checkPointerEscape()
函数在任意符号无法被跟踪时都会被调用。
订阅clang::ento::check::DeadSymbols
程序点主要是为了判断是否发生了内存泄露
。如果一个符号在被回收时,该符号所代表的内存尚未释放,那么表明发生了内存泄露
。
订阅clang::ento::check::PointerEscape
程序点是为了能够在分析器引擎无法有效地跟踪内存所对应的符号时做一些处理,从而避免在某些情况下误报内存泄露
程序缺陷。
step 1: 定义检查器类
要实现检测 C 程序中内存泄露
程序缺陷的功能,我们需要在第一版plugin.unix.Malloc
检查器实现的基础上订阅clang::ento::check::DeadSymbols
和clang::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()
成员函数做了如下两处修改:
增加了默认参数Optional<SVal> Init = None
,表示所分配内存的初始值。
增加了第 150~160 行的代码,用于绑定符号所代表的内存是堆内存并初始化所分配的内存(如果有需要)。
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()
成员函数做了如下两处修改:
更改了符号表达式的获取方式。即将原来的SymbolRef Sym = Call.getArgSVal(0).getAsSymbol();
语句修改为了SymbolRef Sym = Call.getArgSVal(0).getAsSymbol(true);
(对应地,在ReallocMemAux()
成员函数中也需要做类似的修改,详见下文)。
增加了默认参数bool *IsKnownToBeAllocated = nullptr
。并且,如果要释放的内存处于已分配
状态,那么将该参数的值设置为true
(这样做的原因,详见下文)。
如果将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()
成员函数做了如下两处修改:
更改了符号表达式的获取方式。即将原来的SymbolRef FromPtr = Call.getArgSVal(0).getAsSymbol();
语句修改为SymbolRef FromPtr = Call.getArgSVal(0).getAsSymbol(true);
。
更改了自定义程序状态ReallocPairs
中存储的数据。即将其value
的数据类型从SymbolRef
修改为了ReallocPair
。
结构体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
的状态。状态的可取值以及含义如下:
OAR_NeedToFreeAfterFailure
,表示跟踪到了FromPtr
是由malloc()
等函数分配而来的。因此,在realloc()
函数执行失败时需要将其状态恢复为已分配
。
OAR_DoNotTrackAfterFailure
,表示未跟踪到FromPtr
是由malloc()
等函数分配而来的。因此,在realloc()
函数执行失败时不需要继续跟踪。
如果不这样区分(原来的做法未区分上述两种情况),那么在某些情况下会误报内存泄露
程序缺陷。比如:
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:13: warning: Potential memory leak [plugin.unix.Malloc]
return -1;
^
省略 ...
malloc.c:1701:1: warning: Potential 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 }
上述代码的逻辑为:
第 275~279 行,处理calloc()
函数。
第 276~277 行,创建一个值为 0 的SVal
对象,用于初始化calloc()
函数所分配的内存。
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 }
上述代码的逻辑为:
第 433~436 行,判断符号发生PointerEscape
时是否需要进行处理。如果不需要,则直接返回当前程序状态。
第 438~442 行,移除相关的自定义状态并返回一个新的程序状态。
要检测内存泄露
,为什么需要处理符号发生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 }
上述代码的逻辑为:
第 449~451 行,如果被调用的函数是用于分配或释放内存的,那么返回false
。也就是说,在该情况下不对发生PointerEscape
的符号做任何处理。从而,保留相关的自定义状态以便在符号被回收时用于判断是否发生了内存泄露
。
第 453~455 行,如果被调用的函数不是系统函数(Clang 静态分析器假设大多数系统函数不会释放内存),即被调用的函数是用户自定义的,那么返回true
。也就是说,在该情况下移除相关的自定义状态,即不再跟踪该符号是否会发生内存泄露
。
第 457~461 行,如果被调用的函数是funopen
并且其第四个参数的值是 0,那么认为该情况下不会释放内存。
第 463~465 行,用于处理分配的内存传递给类似pthread_setspecific()
这样的函数。在该情况下,不再跟踪该符号是否会发生内存泄露
。
第 467 行,如果都不是上述情形之一,则返回false
,表示默认情况下不对发生PointerEscape
的符号进行任何处理,即仍跟踪该符号是否会发生内存泄露
。
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 }
上述代码的逻辑为:
第 326~335 行,收集发生内存泄露
的符号,并且移除自定义状态RegionState
中不会再用到的信息。认定符号发生了内存泄露
需要同时满足以下 3 个条件:符号要被回收了;符号所代表的内存处于已分配
状态;符号的值不是空指针NULL
。
第 337~341 行,移除自定义状态ReallocPairs
中不会再用到的信息。
第 343~345 行,如果未检测到内存泄露
程序缺陷,则直接返回,什么都不做。
第 347~350 行,报告所有的内存泄露
程序缺陷,并更新程序状态,最后以用于报告内存泄露
的节点作为前继节点创建一个新节点。
需要注意的是, 上述代码在第 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 }
上述代码的逻辑为:
第 358~361 行,创建内存泄露
程序缺陷实例。注意: 构造函数中的第四个参数的值为true
,表示抑制在Sink
节点中报告内存泄露
程序缺陷。
第 363~390 行,报告所有的内存泄露
程序缺陷。
第 364~388 行,创建一个自定义的独一无二的位置。从而,避免某些情况下漏报内存泄露
。
第 380~381 行,打印分配内存所发生的位置。在大多数情况下,通过打印结果我们可以准确地获知发生内存泄露
的内存是在程序中的什么位置被分配的。而unix.Malloc
检查器的实现中未打印该信息。
如果将第 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
检查器以对memcpy
、memcmp
等函数的行为进行建模。从而,避免在某些情况下漏报内存泄露
的程序缺陷。比如:
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
检查器目前的实现未对reallocf
、strdup
、wcsdup
、strndup
、_strdup
和_wcsdup
函数的行为进行建模。因此,会漏报编号为 1、2 两处的内存泄露
程序缺陷。除此之外,也未对setbuf
函数的行为进行建模。因此,会误报编号为 4 处的内存泄露
程序缺陷。
而unix.malloc
检查器漏报了编号为 3 处的内存泄露
程序缺陷。
141 void reallocSizeZero5() {
142 char *r = realloc(0, 0);
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(比如:malloc
、calloc
、realloc
、free
等)以及相关函数(比如:memcpy
、memcmp
等)的行为进行建模。并且,在符号被回收时检测是否发生了内存泄露
程序缺陷。
Clang 静态分析器假设大多数系统函数不会释放内存。如果确定哪些系统函数一定会释放内存,也可以对这些函数进行特殊处理。从而,既不漏报也不误报内存泄露
程序缺陷。
对于源码实现不透明的用户自定义函数,Clang 静态分析器假设这些用户自定义函数会释放内存。这样的做法,虽然可以避免误报,但可能发生漏报。如果可以确定哪些用户自定义函数一定会释放内存或者一定不会释放内存,那么即使这些函数的源码实现是不透明的,也可以对这些函数进行特殊处理。从而,既不漏报也不误报内存泄露
程序缺陷。
除此之外,我们需要决定哪些情况下应该处理发生PointerEscape
的符号。从而,避免某些情况下误报内存泄露
程序缺陷。
[analyzer] Anti-aliasing: different heap allocations do not alias
[analyzer] If realloc fails on an escaped region, that region doesn't leak.
下一篇:LLVM 之 Clang 静态分析器篇(8):程序缺陷诊断——非法读写已释放的内存