Skip to content

Commit 9561fda

Browse files
authored
Merge pull request #672 from geoffw0/lgtm1605
CPP: Fix function pointer/lambda related false positives in 'Resource not released in destructor'
2 parents 169bbcd + 5e39e0e commit 9561fda

File tree

4 files changed

+85
-14
lines changed

4 files changed

+85
-14
lines changed

change-notes/1.20/analysis-cpp.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
| Suspicious pointer scaling (`cpp/suspicious-pointer-scaling`) | Fewer false positives | False positives involving types that are not uniquely named in the snapshot have been fixed. |
1919
| Lossy function result cast (`cpp/lossy-function-result-cast`) | Fewer false positive results | The whitelist of rounding functions built into this query has been expanded. |
2020
| Unused static variable (`cpp/unused-static-variable`) | Fewer false positive results | Variables with the attribute `unused` are now excluded from the query. |
21-
| Resource not released in destructor (`cpp/resource-not-released-in-destructor`) | Fewer false positive results | Fix false positives where a resource is released via a virtual method call. |
21+
| Resource not released in destructor (`cpp/resource-not-released-in-destructor`) | Fewer false positive results | Fix false positives where a resource is released via a virtual method call, function pointer, or lambda. |
2222

2323
## Changes to QL libraries
2424

cpp/ql/src/jsf/4.10 Classes/AV Rule 79.ql

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,12 @@ class Resource extends MemberVariable {
140140
)
141141
}
142142

143-
predicate acquisitionWithRequiredRelease(Assignment acquireAssign, string kind) {
143+
predicate acquisitionWithRequiredKind(Assignment acquireAssign, string kind) {
144144
// acquireAssign is an assignment to this resource
145145
acquireAssign.(Assignment).getLValue() = this.getAnAccess() and
146146
// Should be in this class, but *any* member method will do
147147
this.inSameClass(acquireAssign) and
148-
// Check that it is an acquisition function and return the corresponding free
148+
// Check that it is an acquisition function and return the corresponding kind
149149
acquireExpr(acquireAssign.getRValue(), kind)
150150
}
151151

@@ -158,15 +158,22 @@ predicate unreleasedResource(Resource r, Expr acquire, File f, int acquireLine)
158158
// Note: there could be several release functions, because there could be
159159
// several functions called 'fclose' for example. We want to check that
160160
// *none* of these functions are called to release the resource
161-
r.acquisitionWithRequiredRelease(acquire, _) and
162-
not exists(Expr releaseExpr, string releaseName |
163-
r.acquisitionWithRequiredRelease(acquire, releaseName) and
164-
releaseExpr = r.getAReleaseExpr(releaseName) and
161+
r.acquisitionWithRequiredKind(acquire, _) and
162+
not exists(Expr releaseExpr, string kind |
163+
r.acquisitionWithRequiredKind(acquire, kind) and
164+
releaseExpr = r.getAReleaseExpr(kind) and
165165
r.inDestructor(releaseExpr)
166166
)
167167
and f = acquire.getFile()
168168
and acquireLine = acquire.getLocation().getStartLine()
169169

170+
and not exists(ExprCall exprCall |
171+
// expression call (function pointer or lambda) with `r` as an
172+
// argument, which could release it.
173+
exprCall.getAnArgument() = r.getAnAccess() and
174+
r.inDestructor(exprCall)
175+
)
176+
170177
// check that any destructor for this class has a block; if it doesn't,
171178
// we must be missing information.
172179
and forall(Class c, Destructor d |
@@ -181,10 +188,12 @@ predicate unreleasedResource(Resource r, Expr acquire, File f, int acquireLine)
181188

182189
predicate freedInSameMethod(Resource r, Expr acquire) {
183190
unreleasedResource(r, acquire, _, _) and
184-
exists(Expr releaseExpr, string releaseName |
185-
r.acquisitionWithRequiredRelease(acquire, releaseName) and
186-
releaseExpr = r.getAReleaseExpr(releaseName) and
187-
releaseExpr.getEnclosingFunction() = acquire.getEnclosingFunction()
191+
exists(Expr releaseExpr, string kind |
192+
r.acquisitionWithRequiredKind(acquire, kind) and
193+
releaseExpr = r.getAReleaseExpr(kind) and
194+
releaseExpr.getEnclosingFunction().getEnclosingAccessHolder*() = acquire.getEnclosingFunction()
195+
// here, `getEnclosingAccessHolder*` allows us to go from a nested function or lambda
196+
// expression to the class method enclosing it.
188197
)
189198
}
190199

@@ -221,16 +230,21 @@ predicate leakedInSameMethod(Resource r, Expr acquire) {
221230
fc = acquire.getAChild*() // e.g. `r = new MyClass(this)`
222231
)
223232
)
233+
) or exists(FunctionAccess fa, string kind |
234+
// the address of a function that releases `r` is taken (and likely
235+
// used to release `r` at some point).
236+
r.acquisitionWithRequiredKind(acquire, kind) and
237+
fa.getTarget() = r.getAReleaseExpr(kind).getEnclosingFunction()
224238
)
225239
)
226240
}
227241

228242
pragma[noopt] predicate badRelease(Resource r, Expr acquire, Function functionCallingRelease, int line) {
229243
unreleasedResource(r, acquire, _, _) and
230-
exists(Expr releaseExpr, string releaseName,
244+
exists(Expr releaseExpr, string kind,
231245
Location releaseExprLocation, Function acquireFunction |
232-
r.acquisitionWithRequiredRelease(acquire, releaseName) and
233-
releaseExpr = r.getAReleaseExpr(releaseName) and
246+
r.acquisitionWithRequiredKind(acquire, kind) and
247+
releaseExpr = r.getAReleaseExpr(kind) and
234248
releaseExpr.getEnclosingFunction() = functionCallingRelease and
235249
functionCallingRelease.getDeclaringType() = r.getDeclaringType() and
236250
releaseExprLocation = releaseExpr.getLocation() and

cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
| DeleteThis.cpp:60:3:60:24 | ... = ... | Resource ptr14 is acquired by class MyClass3 but not released anywhere in this class. |
1111
| DeleteThis.cpp:127:3:127:20 | ... = ... | Resource d is acquired by class MyClass9 but not released anywhere in this class. |
1212
| ExternalOwners.cpp:49:3:49:20 | ... = ... | Resource a is acquired by class MyScreen but not released anywhere in this class. |
13+
| Lambda.cpp:24:3:24:21 | ... = ... | Resource r4 is acquired by class testLambda but not released anywhere in this class. |
1314
| ListDelete.cpp:21:3:21:21 | ... = ... | Resource first is acquired by class MyThingColection but not released anywhere in this class. |
1415
| NoDestructor.cpp:23:3:23:20 | ... = ... | Resource n is acquired by class MyClass5 but not released anywhere in this class. |
1516
| PlacementNew.cpp:36:3:36:36 | ... = ... | Resource p1 is acquired by class MyTestForPlacementNew but not released anywhere in this class. |
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
2+
class testLambda
3+
{
4+
public:
5+
testLambda()
6+
{
7+
r1 = new char[4096]; // GOOD
8+
deleter1 = [](char *r) {
9+
delete [] r;
10+
};
11+
12+
r2 = new char[4096]; // GOOD
13+
auto deleter2 = [this]() {
14+
delete [] r2;
15+
};
16+
deleter2();
17+
18+
r3 = new char[4096]; // GOOD
19+
auto deleter3 = [&r = r3]() {
20+
delete [] r;
21+
};
22+
deleter3();
23+
24+
r4 = new char[4096]; // BAD
25+
26+
r5 = new char[4096]; // GOOD
27+
deleter5 = &deleter_for_r5;
28+
29+
r6 = new char[4096]; // GOOD
30+
deleter6 = &testLambda::deleter_for_r6;
31+
}
32+
33+
static void deleter_for_r5(char *r)
34+
{
35+
delete [] r;
36+
}
37+
38+
void deleter_for_r6()
39+
{
40+
delete [] r6;
41+
}
42+
43+
~testLambda()
44+
{
45+
deleter1(r1);
46+
deleter5(r5);
47+
((*this).*deleter6)();
48+
}
49+
50+
private:
51+
char *r1, *r2, *r3, *r4, *r5, *r6;
52+
53+
void (*deleter1)(char *r);
54+
void (*deleter5)(char *r);
55+
void (testLambda::*deleter6)();
56+
};

0 commit comments

Comments
 (0)