Skip to content

Commit d629d14

Browse files
committed
Merge remote-tracking branch 'mrstanb/improve-multi-threaded-valid-memcleanup' into svcomp24-dev
2 parents 765a64e + bc7694b commit d629d14

15 files changed

+440
-23
lines changed

src/analyses/memLeak.ml

Lines changed: 66 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ open MessageCategory
66
open AnalysisStateUtil
77

88
module ToppedVarInfoSet = SetDomain.ToppedSet(CilType.Varinfo)(struct let topname = "All Heap Variables" end)
9-
9+
module WasMallocCalled = BoolDomain.MayBool
1010
module Spec : Analyses.MCPSpec =
1111
struct
1212
include Analyses.IdentitySpec
@@ -17,8 +17,17 @@ struct
1717
module C = D
1818
module P = IdentityP (D)
1919

20+
module V = UnitV
21+
module G = WasMallocCalled
22+
2023
let context _ d = d
2124

25+
let must_be_single_threaded ~since_start ctx =
26+
ctx.ask (Queries.MustBeSingleThreaded { since_start })
27+
28+
let was_malloc_called ctx =
29+
ctx.global ()
30+
2231
(* HELPER FUNCTIONS *)
2332
let get_global_vars () =
2433
List.filter_map (function GVar (v, _, _) | GVarDecl (v, _) -> Some v | _ -> None) !Cilfacade.current_file.globals
@@ -131,11 +140,21 @@ struct
131140
reachable_from_fields @ acc_struct
132141
) []
133142

134-
let warn_for_multi_threaded ctx =
135-
if not (ctx.ask (Queries.MustBeSingleThreaded { since_start = true })) then (
143+
let warn_for_multi_threaded_due_to_abort ctx =
144+
let malloc_called = was_malloc_called ctx in
145+
if not (must_be_single_threaded ctx ~since_start:true) && malloc_called then (
146+
set_mem_safety_flag InvalidMemTrack;
147+
set_mem_safety_flag InvalidMemcleanup;
148+
M.warn ~category:(Behavior (Undefined MemoryLeak)) ~tags:[CWE 401] "Program aborted while running in multi-threaded mode. A memory leak might occur"
149+
)
150+
151+
(* If [is_return] is set to [true], then a thread return occurred, else a thread exit *)
152+
let warn_for_thread_return_or_exit ctx is_return =
153+
if not (ToppedVarInfoSet.is_empty ctx.local) then (
136154
set_mem_safety_flag InvalidMemTrack;
137155
set_mem_safety_flag InvalidMemcleanup;
138-
M.warn ~category:(Behavior (Undefined MemoryLeak)) ~tags:[CWE 401] "Program isn't running in single-threaded mode. A memory leak might occur due to multi-threading"
156+
let current_thread = ctx.ask (Queries.CurrentThreadId) in
157+
M.warn ~category:(Behavior (Undefined MemoryLeak)) ~tags:[CWE 401] "Memory may be leaked at thread %s for thread %a" (if is_return then "return" else "exit") ThreadIdDomain.ThreadLifted.pretty current_thread
139158
)
140159

141160
let check_for_mem_leak ?(assert_exp_imprecise = false) ?(exp = None) ctx =
@@ -159,12 +178,24 @@ struct
159178
M.warn ~category:(Behavior (Undefined MemoryLeak)) ~tags:[CWE 401] "Assert expression %a is unknown. Memory leak might possibly occur for heap variables: %a" d_exp exp D.pretty allocated_mem
160179
| _ ->
161180
set_mem_safety_flag InvalidMemcleanup;
162-
M.warn ~category:(Behavior (Undefined MemoryLeak)) ~tags:[CWE 401] "Memory leak detected for heap variables: %a" D.pretty allocated_mem
181+
M.warn ~category:(Behavior (Undefined MemoryLeak)) ~tags:[CWE 401] "Memory leak detected for heap variables"
163182

164183
(* TRANSFER FUNCTIONS *)
165184
let return ctx (exp:exp option) (f:fundec) : D.t =
185+
(* Check for a valid-memcleanup and memtrack violation in a multi-threaded setting *)
186+
(* The check for multi-threadedness is to ensure that valid-memtrack and valid-memclenaup are treated separately for single-threaded programs *)
187+
if (ctx.ask (Queries.MayBeThreadReturn) && not (must_be_single_threaded ctx ~since_start:true)) then (
188+
warn_for_thread_return_or_exit ctx true
189+
);
166190
(* Returning from "main" is one possible program exit => need to check for memory leaks *)
167-
if f.svar.vname = "main" then check_for_mem_leak ctx;
191+
if f.svar.vname = "main" then (
192+
check_for_mem_leak ctx;
193+
if not (must_be_single_threaded ctx ~since_start:false) && was_malloc_called ctx then begin
194+
set_mem_safety_flag InvalidMemTrack;
195+
set_mem_safety_flag InvalidMemcleanup;
196+
M.warn ~category:(Behavior (Undefined MemoryLeak)) ~tags:[CWE 401] "Possible memory leak: Memory was allocated in a multithreaded program, but not all threads are joined."
197+
end
198+
);
168199
ctx.local
169200

170201
let special ctx (lval:lval option) (f:varinfo) (arglist:exp list) : D.t =
@@ -174,46 +205,61 @@ struct
174205
| Malloc _
175206
| Calloc _
176207
| Realloc _ ->
177-
(* Warn about multi-threaded programs as soon as we encounter a dynamic memory allocation function *)
178-
warn_for_multi_threaded ctx;
208+
(ctx.sideg () true;
179209
begin match ctx.ask (Queries.AllocVar {on_stack = false}) with
180-
| `Lifted var -> D.add var state
210+
| `Lifted var ->
211+
ToppedVarInfoSet.add var state
181212
| _ -> state
182-
end
213+
end)
183214
| Free ptr ->
184215
begin match ctx.ask (Queries.MayPointTo ptr) with
185-
| ad when not (Queries.AD.is_top ad) && Queries.AD.cardinal ad = 1 ->
216+
| ad when (not (Queries.AD.is_top ad)) && Queries.AD.cardinal ad = 1 ->
186217
(* Note: Need to always set "ana.malloc.unique_address_count" to a value > 0 *)
187218
begin match Queries.AD.choose ad with
188-
| Queries.AD.Addr.Addr (v,_) when ctx.ask (Queries.IsAllocVar v) && ctx.ask (Queries.IsHeapVar v) && not @@ ctx.ask (Queries.IsMultiple v) -> D.remove v state (* Unique pointed to heap vars *)
189-
| _ -> state
219+
| Queries.AD.Addr.Addr (v,_) when ctx.ask (Queries.IsAllocVar v) && ctx.ask (Queries.IsHeapVar v) && not @@ ctx.ask (Queries.IsMultiple v) ->
220+
ToppedVarInfoSet.remove v ctx.local
221+
| _ -> ctx.local
190222
end
191-
| _ -> state
223+
| _ -> ctx.local
192224
end
193225
| Abort ->
194-
(* An "Abort" special function indicates program exit => need to check for memory leaks *)
195226
check_for_mem_leak ctx;
227+
(* Upon a call to the "Abort" special function in the multi-threaded case, we give up and conservatively warn *)
228+
warn_for_multi_threaded_due_to_abort ctx;
196229
state
197230
| Assert { exp; _ } ->
198231
let warn_for_assert_exp =
199232
match ctx.ask (Queries.EvalInt exp) with
200233
| a when Queries.ID.is_bot a -> M.warn ~category:Assert "assert expression %a is bottom" d_exp exp
201234
| a ->
202235
begin match Queries.ID.to_bool a with
203-
| Some b ->
236+
| Some b -> (
204237
(* If we know for sure that the expression in "assert" is false => need to check for memory leaks *)
205-
if b = false then
206-
check_for_mem_leak ctx
207-
else ()
208-
| None -> check_for_mem_leak ctx ~assert_exp_imprecise:true ~exp:(Some exp)
238+
if b = false then (
239+
warn_for_multi_threaded_due_to_abort ctx;
240+
check_for_mem_leak ctx
241+
)
242+
else ())
243+
| None ->
244+
(warn_for_multi_threaded_due_to_abort ctx;
245+
check_for_mem_leak ctx ~assert_exp_imprecise:true ~exp:(Some exp))
209246
end
210247
in
211248
warn_for_assert_exp;
212249
state
250+
| ThreadExit _ ->
251+
begin match ctx.ask (Queries.CurrentThreadId) with
252+
| `Lifted tid ->
253+
warn_for_thread_return_or_exit ctx false
254+
| _ -> ()
255+
end;
256+
state
213257
| _ -> state
214258

215259
let startstate v = D.bot ()
216260
let exitstate v = D.top ()
261+
262+
let threadenter ctx ~multiple lval f args = [D.bot ()]
217263
end
218264

219265
let _ =

src/analyses/threadAnalysis.ml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,15 @@ struct
2222
module P = IdentityP (D)
2323

2424
(* transfer functions *)
25-
let return ctx (exp:exp option) (f:fundec) : D.t =
25+
let handle_thread_return ctx (exp: exp option) =
2626
let tid = ThreadId.get_current (Analyses.ask_of_ctx ctx) in
27-
begin match tid with
27+
match tid with
2828
| `Lifted tid -> ctx.sideg tid (false, TS.bot (), not (D.is_empty ctx.local))
2929
| _ -> ()
30-
end;
30+
31+
let return ctx (exp:exp option) _ : D.t =
32+
if ctx.ask Queries.MayBeThreadReturn then
33+
handle_thread_return ctx exp;
3134
ctx.local
3235

3336
let rec is_not_unique ctx tid =
@@ -64,6 +67,9 @@ struct
6467
| [t] -> join_thread ctx.local t (* single thread *)
6568
| _ -> ctx.local (* if several possible threads are may-joined, none are must-joined *)
6669
| exception SetDomain.Unsupported _ -> ctx.local)
70+
| ThreadExit { ret_val } ->
71+
handle_thread_return ctx (Some ret_val);
72+
ctx.local
6773
| _ -> ctx.local
6874

6975
let query ctx (type a) (q: a Queries.t): a Queries.result =

src/common/util/options.schema.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2200,6 +2200,25 @@
22002200
"description": "Output messages in deterministic order. Useful for cram testing.",
22012201
"type": "boolean",
22022202
"default": false
2203+
},
2204+
"memleak": {
2205+
"title": "warn.memleak",
2206+
"type":"object",
2207+
"properties": {
2208+
"memcleanup": {
2209+
"title": "warn.memleak.memcleanup",
2210+
"description": "Enable memory leak warnings only for violations of the SV-COMP \"valid-memcleanup\" category",
2211+
"type": "boolean",
2212+
"default": false
2213+
},
2214+
"memtrack": {
2215+
"title": "warn.memleak.memtrack",
2216+
"description": "Enable memory leak warnings only for violations of the SV-COMP \"valid-memtrack\" category",
2217+
"type": "boolean",
2218+
"default": false
2219+
}
2220+
},
2221+
"additionalProperties": false
22032222
}
22042223
},
22052224
"additionalProperties": false
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak --set ana.activated[+] thread
2+
#include <stdlib.h>
3+
#include <pthread.h>
4+
5+
int *g;
6+
int *m1;
7+
8+
void *f1(void *arg) {
9+
m1 = malloc(sizeof(int));
10+
// Thread t1 leaks m1 here
11+
pthread_exit(NULL); //WARN
12+
}
13+
14+
void *f2(void *arg) {
15+
int *m2;
16+
m2 = malloc(sizeof(int));
17+
free(m2); // No leak for thread t2, since it calls free before exiting
18+
pthread_exit(NULL); //NOWARN
19+
}
20+
21+
int main(int argc, char const *argv[]) {
22+
g = malloc(sizeof(int));
23+
pthread_t t1;
24+
pthread_create(&t1, NULL, f1, NULL);
25+
26+
pthread_t t2;
27+
pthread_create(&t2, NULL, f2, NULL);
28+
29+
free(g);
30+
31+
pthread_join(t1, NULL);
32+
pthread_join(t2, NULL);
33+
34+
// main thread is not leaking anything
35+
return 0; //NOWARN
36+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak --set ana.activated[+] thread
2+
#include <pthread.h>
3+
4+
int *g;
5+
int *m1;
6+
7+
void *f1(void *arg) {
8+
m1 = malloc(sizeof(int));
9+
// Thread t1 leaks m1 here
10+
exit(2); //WARN
11+
}
12+
13+
void *f2(void *arg) {
14+
int *m2;
15+
m2 = malloc(sizeof(int));
16+
free(m2); // No leak for thread t2, since it calls free before exiting
17+
pthread_exit(NULL); //NOWARN
18+
}
19+
20+
int main(int argc, char const *argv[]) {
21+
g = malloc(sizeof(int));
22+
pthread_t t1;
23+
pthread_create(&t1, NULL, f1, NULL);
24+
25+
pthread_t t2;
26+
pthread_create(&t2, NULL, f2, NULL);
27+
28+
free(g);
29+
30+
pthread_join(t1, NULL);
31+
pthread_join(t2, NULL);
32+
33+
// main thread is not leaking anything
34+
return 0; //NOWARN
35+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak
2+
#include <stdlib.h>
3+
#include <pthread.h>
4+
5+
int *g;
6+
int *m1;
7+
int *m2;
8+
9+
void *f1(void *arg) {
10+
int top;
11+
12+
// Thread t1 leaks m0 here
13+
exit(2); //WARN
14+
}
15+
16+
int main(int argc, char const *argv[]) {
17+
pthread_t t1;
18+
pthread_create(&t1, NULL, f1, NULL);
19+
20+
int* m0 = malloc(sizeof(int));
21+
free(m0);
22+
23+
// main thread is not leaking anything
24+
return 0;
25+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak
2+
#include <stdlib.h>
3+
#include <pthread.h>
4+
5+
int *g;
6+
int *m1;
7+
int *m2;
8+
9+
void *f2(void *arg) {
10+
// Thread t2 leaks m0 and t1_ptr here
11+
quick_exit(2); //WARN
12+
}
13+
14+
void *f1(void *arg) {
15+
pthread_t t2;
16+
pthread_create(&t2, NULL, f2, NULL);
17+
18+
int *t1_ptr = malloc(sizeof(int));
19+
20+
pthread_join(t2, NULL);
21+
// t1_ptr is leaked, since t2 calls quick_exit() potentially before this program point
22+
free(t1_ptr);
23+
}
24+
25+
int main(int argc, char const *argv[]) {
26+
pthread_t t1;
27+
pthread_create(&t1, NULL, f1, NULL);
28+
29+
int* m0 = malloc(sizeof(int));
30+
free(m0);
31+
32+
// main thread is not leaking anything
33+
return 0;
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak --disable warn.assert
2+
#include <stdlib.h>
3+
#include <pthread.h>
4+
#include <assert.h>
5+
6+
int *g;
7+
int *m1;
8+
int *m2;
9+
10+
void *f2(void *arg) {
11+
// Thread t2 leaks m0 and t1_ptr here
12+
assert(0); //WARN
13+
}
14+
15+
void *f1(void *arg) {
16+
pthread_t t2;
17+
pthread_create(&t2, NULL, f2, NULL);
18+
19+
int *t1_ptr = malloc(sizeof(int));
20+
assert(1); //NOWARN
21+
pthread_join(t2, NULL);
22+
free(t1_ptr);
23+
}
24+
25+
int main(int argc, char const *argv[]) {
26+
pthread_t t1;
27+
pthread_create(&t1, NULL, f1, NULL);
28+
29+
int* m0 = malloc(sizeof(int));
30+
free(m0);
31+
32+
// main thread is not leaking anything
33+
return 0;
34+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak --disable warn.assert
2+
#include <stdlib.h>
3+
#include <pthread.h>
4+
#include <assert.h>
5+
6+
void *f1(void *arg) {
7+
int top;
8+
assert(top); //WARN
9+
}
10+
11+
int main(int argc, char const *argv[]) {
12+
pthread_t t1;
13+
pthread_create(&t1, NULL, f1, NULL);
14+
15+
int* m0 = malloc(sizeof(int));
16+
free(m0);
17+
18+
// main thread is not leaking anything
19+
return 0;
20+
}

0 commit comments

Comments
 (0)