diff --git a/CHANGELOG.md b/CHANGELOG.md index 97cc399133..7300c09206 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## v2.3.0 (unreleased) +Functionally equivalent to Goblint in SV-COMP 2024. + +* Add termination analysis for loops (#1093). +* Add memory out-of-bounds analysis (#1094, #1197). +* Add memory leak analysis (#1127, #1241, #1246). +* Add SV-COMP `termination`, `valid-memsafety` and `valid-memcleanup` properties support (#1220, #1228, #1201, #1199, #1259, #1262). +* Add YAML witness version 2.0 support (#1238, #1240, #1217, #1226, #1225, #1248). +* Add final warnings about unsound results (#1190, #1191). +* Add many library function specifications (#1167, #1174, #1203, #1205, #1212, #1220, #1239, #1242, #1244, #1254, #1269). +* Adapt automatic configuration tuning (#912, #921, #987, #1168, #1214, #1234). + ## v2.2.1 * Bump batteries lower bound to 3.5.0. * Fix flaky dead code elimination transformation test. diff --git a/conf/svcomp.json b/conf/svcomp.json index 3c9c689334..7e30554ceb 100644 --- a/conf/svcomp.json +++ b/conf/svcomp.json @@ -32,6 +32,15 @@ "thread", "threadJoins" ], + "path_sens": [ + "mutex", + "malloc_null", + "uninit", + "expsplit", + "activeSetjmp", + "memLeak", + "threadflag" + ], "context": { "widen": false }, @@ -52,7 +61,8 @@ "ldv_xmalloc", "ldv_xzalloc", - "ldv_calloc" + "ldv_calloc", + "ldv_kzalloc" ] }, "base": { @@ -60,6 +70,10 @@ "domain": "partitioned" } }, + "race": { + "free": false, + "call": false + }, "autotune": { "enabled": true, "activated": [ @@ -72,8 +86,8 @@ "wideningThresholds", "loopUnrollHeuristic", "memsafetySpecification", - "tmpSpecialAnalysis", - "termination" + "termination", + "tmpSpecialAnalysis" ] } }, @@ -97,6 +111,33 @@ "enabled": true, "id": "enumerate", "unknown": false + }, + "yaml": { + "enabled": true, + "format-version": "2.0", + "entry-types": [ + "invariant_set" + ], + "invariant-types": [ + "loop_invariant" + ] + }, + "invariant": { + "loop-head": true, + "after-lock": false, + "other": false, + "accessed": false, + "exact": true, + "exclude-vars": [ + "tmp\\(___[0-9]+\\)?", + "cond", + "RETURN", + "__\\(cil_\\)?tmp_?[0-9]*\\(_[0-9]+\\)?", + ".*____CPAchecker_TMP_[0-9]+", + "__VERIFIER_assert__cond", + "__ksymtab_.*", + "\\(ldv_state_variable\\|ldv_timer_state\\|ldv_timer_list\\|ldv_irq_\\(line_\\|data_\\)?[0-9]+\\|ldv_retval\\)_[0-9]+" + ] } }, "pre": { diff --git a/conf/svcomp24-validate.json b/conf/svcomp24-validate.json new file mode 100644 index 0000000000..7832ffa6af --- /dev/null +++ b/conf/svcomp24-validate.json @@ -0,0 +1,140 @@ +{ + "ana": { + "sv-comp": { + "enabled": true, + "functions": true + }, + "int": { + "def_exc": true, + "enums": false, + "interval": true + }, + "float": { + "interval": true + }, + "activated": [ + "base", + "threadid", + "threadflag", + "threadreturn", + "mallocWrapper", + "mutexEvents", + "mutex", + "access", + "race", + "escape", + "expRelation", + "mhp", + "assert", + "var_eq", + "symb_locks", + "region", + "thread", + "threadJoins", + "unassume" + ], + "path_sens": [ + "mutex", + "malloc_null", + "uninit", + "expsplit", + "activeSetjmp", + "memLeak", + "threadflag" + ], + "context": { + "widen": false + }, + "malloc": { + "wrappers": [ + "kmalloc", + "__kmalloc", + "usb_alloc_urb", + "__builtin_alloca", + "kzalloc", + + "ldv_malloc", + + "kzalloc_node", + "ldv_zalloc", + "kmalloc_array", + "kcalloc", + + "ldv_xmalloc", + "ldv_xzalloc", + "ldv_calloc", + "ldv_kzalloc" + ] + }, + "base": { + "arrays": { + "domain": "partitioned" + } + }, + "race": { + "free": false, + "call": false + }, + "autotune": { + "enabled": true, + "activated": [ + "singleThreaded", + "mallocWrappers", + "noRecursiveIntervals", + "enums", + "congruence", + "octagon", + "wideningThresholds", + "loopUnrollHeuristic", + "memsafetySpecification", + "termination", + "tmpSpecialAnalysis" + ] + }, + "widen": { + "tokens": true + } + }, + "exp": { + "region-offsets": true + }, + "solver": "td3", + "sem": { + "unknown_function": { + "spawn": false + }, + "int": { + "signed_overflow": "assume_none" + }, + "null-pointer": { + "dereference": "assume_none" + } + }, + "witness": { + "graphml": { + "enabled": false + }, + "yaml": { + "enabled": false, + "strict": true, + "format-version": "2.0", + "entry-types": [ + "location_invariant", + "loop_invariant", + "invariant_set" + ], + "invariant-types": [ + "location_invariant", + "loop_invariant" + ] + }, + "invariant": { + "loop-head": true, + "after-lock": true, + "other": true + } + }, + "pre": { + "enabled": false + } +} diff --git a/conf/svcomp24.json b/conf/svcomp24.json new file mode 100644 index 0000000000..7e30554ceb --- /dev/null +++ b/conf/svcomp24.json @@ -0,0 +1,146 @@ +{ + "ana": { + "sv-comp": { + "enabled": true, + "functions": true + }, + "int": { + "def_exc": true, + "enums": false, + "interval": true + }, + "float": { + "interval": true + }, + "activated": [ + "base", + "threadid", + "threadflag", + "threadreturn", + "mallocWrapper", + "mutexEvents", + "mutex", + "access", + "race", + "escape", + "expRelation", + "mhp", + "assert", + "var_eq", + "symb_locks", + "region", + "thread", + "threadJoins" + ], + "path_sens": [ + "mutex", + "malloc_null", + "uninit", + "expsplit", + "activeSetjmp", + "memLeak", + "threadflag" + ], + "context": { + "widen": false + }, + "malloc": { + "wrappers": [ + "kmalloc", + "__kmalloc", + "usb_alloc_urb", + "__builtin_alloca", + "kzalloc", + + "ldv_malloc", + + "kzalloc_node", + "ldv_zalloc", + "kmalloc_array", + "kcalloc", + + "ldv_xmalloc", + "ldv_xzalloc", + "ldv_calloc", + "ldv_kzalloc" + ] + }, + "base": { + "arrays": { + "domain": "partitioned" + } + }, + "race": { + "free": false, + "call": false + }, + "autotune": { + "enabled": true, + "activated": [ + "singleThreaded", + "mallocWrappers", + "noRecursiveIntervals", + "enums", + "congruence", + "octagon", + "wideningThresholds", + "loopUnrollHeuristic", + "memsafetySpecification", + "termination", + "tmpSpecialAnalysis" + ] + } + }, + "exp": { + "region-offsets": true + }, + "solver": "td3", + "sem": { + "unknown_function": { + "spawn": false + }, + "int": { + "signed_overflow": "assume_none" + }, + "null-pointer": { + "dereference": "assume_none" + } + }, + "witness": { + "graphml": { + "enabled": true, + "id": "enumerate", + "unknown": false + }, + "yaml": { + "enabled": true, + "format-version": "2.0", + "entry-types": [ + "invariant_set" + ], + "invariant-types": [ + "loop_invariant" + ] + }, + "invariant": { + "loop-head": true, + "after-lock": false, + "other": false, + "accessed": false, + "exact": true, + "exclude-vars": [ + "tmp\\(___[0-9]+\\)?", + "cond", + "RETURN", + "__\\(cil_\\)?tmp_?[0-9]*\\(_[0-9]+\\)?", + ".*____CPAchecker_TMP_[0-9]+", + "__VERIFIER_assert__cond", + "__ksymtab_.*", + "\\(ldv_state_variable\\|ldv_timer_state\\|ldv_timer_list\\|ldv_irq_\\(line_\\|data_\\)?[0-9]+\\|ldv_retval\\)_[0-9]+" + ] + } + }, + "pre": { + "enabled": false + } +} diff --git a/docs/developer-guide/releasing.md b/docs/developer-guide/releasing.md index 69ffcb2461..fc5f5f68a1 100644 --- a/docs/developer-guide/releasing.md +++ b/docs/developer-guide/releasing.md @@ -97,16 +97,17 @@ This ensures that the environment and the archive have all the correct system libraries. -6. Commit and push the archive to an SV-COMP archives repository branch (but don't open a MR yet): (SV-COMP 2023). -7. Check pushed archive via CoveriTeam-Remote: . +6. Create (or add new version) Zenodo artifact and upload the archive. - 1. Clone coveriteam repository. - 2. Locally modify `actors/goblint.yml` archive location to the raw URL of the pushed archive. - 3. Run Goblint on some sv-benchmarks and properties via CoveriTeam. +7. Open MR with Zenodo version DOI to the [fm-tools](https://gitlab.com/sosy-lab/benchmarking/fm-tools) repository. - This ensures that Goblint runs on SoSy-Lab servers. + ### After all preruns diff --git a/src/analyses/apron/relationAnalysis.apron.ml b/src/analyses/apron/relationAnalysis.apron.ml index 8b128eb271..d2fe7eab9e 100644 --- a/src/analyses/apron/relationAnalysis.apron.ml +++ b/src/analyses/apron/relationAnalysis.apron.ml @@ -70,7 +70,7 @@ struct let visitor = object inherit nopCilVisitor method! vlval = function - | (Var v, NoOffset) when v.vglob || ThreadEscape.has_escaped ask v -> + | (Var v, NoOffset) when (v.vglob || ThreadEscape.has_escaped ask v) && RD.Tracked.varinfo_tracked v -> let v_in = if VH.mem v_ins v then VH.find v_ins v diff --git a/src/analyses/memLeak.ml b/src/analyses/memLeak.ml index 62b6bbe3a7..05e18e2e39 100644 --- a/src/analyses/memLeak.ml +++ b/src/analyses/memLeak.ml @@ -6,7 +6,7 @@ open MessageCategory open AnalysisStateUtil module ToppedVarInfoSet = SetDomain.ToppedSet(CilType.Varinfo)(struct let topname = "All Heap Variables" end) - +module WasMallocCalled = BoolDomain.MayBool module Spec : Analyses.MCPSpec = struct include Analyses.IdentitySpec @@ -17,33 +17,185 @@ struct module C = D module P = IdentityP (D) + module V = UnitV + module G = WasMallocCalled + let context _ d = d + let must_be_single_threaded ~since_start ctx = + ctx.ask (Queries.MustBeSingleThreaded { since_start }) + + let was_malloc_called ctx = + ctx.global () + (* HELPER FUNCTIONS *) - let warn_for_multi_threaded ctx = - if not (ctx.ask (Queries.MustBeSingleThreaded { since_start = true })) then ( + let get_global_vars () = + List.filter_map (function GVar (v, _, _) | GVarDecl (v, _) -> Some v | _ -> None) !Cilfacade.current_file.globals + + let get_global_struct_ptr_vars () = + get_global_vars () + |> List.filter (fun v -> + match unrollType v.vtype with + | TPtr (TComp (ci,_), _) + | TPtr ((TNamed ({ttype = TComp (ci, _); _}, _)), _) -> ci.cstruct + | TComp (_, _) + | (TNamed ({ttype = TComp _; _}, _)) -> false + | _ -> false) + + let get_global_struct_non_ptr_vars () = + get_global_vars () + |> List.filter (fun v -> + match unrollType v.vtype with + | TComp (ci, _) + | (TNamed ({ttype = TComp (ci,_); _}, _)) -> ci.cstruct + | _ -> false) + + let get_reachable_mem_from_globals (global_vars:varinfo list) ctx = + global_vars + |> List.map (fun v -> Lval (Var v, NoOffset)) + |> List.filter_map (fun exp -> + match ctx.ask (Queries.MayPointTo exp) with + | a when not (Queries.AD.is_top a) && Queries.AD.cardinal a = 1 -> + begin match List.hd @@ Queries.AD.elements a with + | Queries.AD.Addr.Addr (v, _) when (ctx.ask (Queries.IsHeapVar v)) && not (ctx.ask (Queries.IsMultiple v)) -> Some v + | _ -> None + end + | _ -> None) + + let rec get_reachable_mem_from_str_ptr_globals (global_struct_ptr_vars:varinfo list) ctx = + let eval_value_of_heap_var heap_var = + match ctx.ask (Queries.EvalValue (Lval (Var heap_var, NoOffset))) with + | a when not (Queries.VD.is_top a) -> + begin match a with + | Struct s -> + List.fold_left (fun acc f -> + if isPointerType f.ftype then + begin match ValueDomain.Structs.get s f with + | Queries.VD.Address a when not (Queries.AD.is_top a) && Queries.AD.cardinal a = 1 -> + let reachable_from_addr_set = + Queries.AD.fold (fun addr acc -> + match addr with + | Queries.AD.Addr.Addr (v, _) -> (v :: get_reachable_mem_from_str_ptr_globals [v] ctx) @ acc + | _ -> acc + ) a [] + in + reachable_from_addr_set @ acc + | _ -> acc + end + else acc + ) [] (ValueDomain.Structs.keys s) + | _ -> [] + end + | _ -> [] + in + let get_pts_of_non_heap_ptr_var var = + match ctx.ask (Queries.MayPointTo (Lval (Var var, NoOffset))) with + | a when not (Queries.AD.is_top a) && Queries.AD.cardinal a = 1 -> + begin match List.hd @@ Queries.AD.elements a with + | Queries.AD.Addr.Addr (v, _) when (ctx.ask (Queries.IsHeapVar v)) && not (ctx.ask (Queries.IsMultiple v)) -> v :: (eval_value_of_heap_var v) + | Queries.AD.Addr.Addr (v, _) when not (ctx.ask (Queries.IsAllocVar v)) && isPointerType v.vtype -> get_reachable_mem_from_str_ptr_globals [v] ctx + | _ -> [] + end + | _ -> [] + in + global_struct_ptr_vars + |> List.fold_left (fun acc var -> + if ctx.ask (Queries.IsHeapVar var) then (eval_value_of_heap_var var) @ acc + else if not (ctx.ask (Queries.IsAllocVar var)) && isPointerType var.vtype then (get_pts_of_non_heap_ptr_var var) @ acc + else acc + ) [] + + let get_reachable_mem_from_str_non_ptr_globals (global_struct_non_ptr_vars:varinfo list) ctx = + global_struct_non_ptr_vars + (* Filter out global struct vars that don't have pointer fields *) + |> List.filter_map (fun v -> + match ctx.ask (Queries.EvalValue (Lval (Var v, NoOffset))) with + | a when not (Queries.VD.is_top a) -> + begin match a with + | Queries.VD.Struct s -> + let struct_fields = ValueDomain.Structs.keys s in + let ptr_struct_fields = List.filter (fun f -> isPointerType f.ftype) struct_fields in + if ptr_struct_fields = [] then None else Some (s, ptr_struct_fields) + | _ -> None + end + | _ -> None + ) + |> List.fold_left (fun acc_struct (s, fields) -> + let reachable_from_fields = + List.fold_left (fun acc_field field -> + match ValueDomain.Structs.get s field with + | Queries.VD.Address a -> + let reachable_from_addr_set = + Queries.AD.fold (fun addr acc_addr -> + match addr with + | Queries.AD.Addr.Addr (v, _) -> + let reachable_from_v = Queries.AD.of_list (List.map (fun v -> Queries.AD.Addr.Addr (v, `NoOffset)) (get_reachable_mem_from_str_ptr_globals [v] ctx)) in + Queries.AD.join (Queries.AD.add addr reachable_from_v) acc_addr + | _ -> acc_addr + ) a (Queries.AD.empty ()) + in (Queries.AD.to_var_may reachable_from_addr_set) @ acc_field + | _ -> acc_field + ) [] fields + in + reachable_from_fields @ acc_struct + ) [] + + let warn_for_multi_threaded_due_to_abort ctx = + let malloc_called = was_malloc_called ctx in + if not (must_be_single_threaded ctx ~since_start:true) && malloc_called then ( + set_mem_safety_flag InvalidMemTrack; + set_mem_safety_flag InvalidMemcleanup; + M.warn ~category:(Behavior (Undefined MemoryLeak)) ~tags:[CWE 401] "Program aborted while running in multi-threaded mode. A memory leak might occur" + ) + + (* If [is_return] is set to [true], then a thread return occurred, else a thread exit *) + let warn_for_thread_return_or_exit ctx is_return = + if not (ToppedVarInfoSet.is_empty ctx.local) then ( set_mem_safety_flag InvalidMemTrack; set_mem_safety_flag InvalidMemcleanup; - 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" + let current_thread = ctx.ask (Queries.CurrentThreadId) in + 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 ) let check_for_mem_leak ?(assert_exp_imprecise = false) ?(exp = None) ctx = - let state = ctx.local in - if not @@ D.is_empty state then + let allocated_mem = ctx.local in + if not (D.is_empty allocated_mem) then + let reachable_mem_from_non_struct_globals = D.of_list (get_reachable_mem_from_globals (get_global_vars ()) ctx) in + let reachable_mem_from_struct_ptr_globals = D.of_list (get_reachable_mem_from_str_ptr_globals (get_global_struct_ptr_vars ()) ctx) in + let reachable_mem_from_struct_non_ptr_globals = D.of_list (get_reachable_mem_from_str_non_ptr_globals (get_global_struct_non_ptr_vars ()) ctx) in + let reachable_mem_from_struct_globals = D.join reachable_mem_from_struct_ptr_globals reachable_mem_from_struct_non_ptr_globals in + let reachable_mem = D.join reachable_mem_from_non_struct_globals reachable_mem_from_struct_globals in + (* Check and warn if there's unreachable allocated memory at program exit *) + let allocated_and_unreachable_mem = D.diff allocated_mem reachable_mem in + if not (D.is_empty allocated_and_unreachable_mem) then ( + set_mem_safety_flag InvalidMemTrack; + M.warn ~category:(Behavior (Undefined MemoryLeak)) ~tags:[CWE 401] "There is unreachable allocated heap memory at program exit. A memory leak might occur for the alloc vars %a\n" (Pretty.d_list ", " CilType.Varinfo.pretty) (D.elements allocated_and_unreachable_mem) + ); + (* Check and warn if some of the allocated memory is not deallocated at program exit *) match assert_exp_imprecise, exp with | true, Some exp -> - set_mem_safety_flag InvalidMemTrack; set_mem_safety_flag InvalidMemcleanup; - 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 state + 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 | _ -> - set_mem_safety_flag InvalidMemTrack; set_mem_safety_flag InvalidMemcleanup; - M.warn ~category:(Behavior (Undefined MemoryLeak)) ~tags:[CWE 401] "Memory leak detected for heap variables: %a" D.pretty state + M.warn ~category:(Behavior (Undefined MemoryLeak)) ~tags:[CWE 401] "Memory leak detected for heap variables" (* TRANSFER FUNCTIONS *) let return ctx (exp:exp option) (f:fundec) : D.t = + (* Check for a valid-memcleanup and memtrack violation in a multi-threaded setting *) + (* The check for multi-threadedness is to ensure that valid-memtrack and valid-memclenaup are treated separately for single-threaded programs *) + if (ctx.ask (Queries.MayBeThreadReturn) && not (must_be_single_threaded ctx ~since_start:true)) then ( + warn_for_thread_return_or_exit ctx true + ); (* Returning from "main" is one possible program exit => need to check for memory leaks *) - if f.svar.vname = "main" then check_for_mem_leak ctx; + if f.svar.vname = "main" then ( + check_for_mem_leak ctx; + if not (must_be_single_threaded ctx ~since_start:false) && was_malloc_called ctx then begin + set_mem_safety_flag InvalidMemTrack; + set_mem_safety_flag InvalidMemcleanup; + 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." + end + ); ctx.local let special ctx (lval:lval option) (f:varinfo) (arglist:exp list) : D.t = @@ -53,25 +205,27 @@ struct | Malloc _ | Calloc _ | Realloc _ -> - (* Warn about multi-threaded programs as soon as we encounter a dynamic memory allocation function *) - warn_for_multi_threaded ctx; + (ctx.sideg () true; begin match ctx.ask (Queries.AllocVar {on_stack = false}) with - | `Lifted var -> D.add var state + | `Lifted var -> + ToppedVarInfoSet.add var state | _ -> state - end + end) | Free ptr -> begin match ctx.ask (Queries.MayPointTo ptr) with - | ad when not (Queries.AD.is_top ad) && Queries.AD.cardinal ad = 1 -> + | ad when (not (Queries.AD.is_top ad)) && Queries.AD.cardinal ad = 1 -> (* Note: Need to always set "ana.malloc.unique_address_count" to a value > 0 *) begin match Queries.AD.choose ad with - | 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 *) - | _ -> state + | Queries.AD.Addr.Addr (v,_) when ctx.ask (Queries.IsAllocVar v) && ctx.ask (Queries.IsHeapVar v) && not @@ ctx.ask (Queries.IsMultiple v) -> + ToppedVarInfoSet.remove v ctx.local + | _ -> ctx.local end - | _ -> state + | _ -> ctx.local end | Abort -> - (* An "Abort" special function indicates program exit => need to check for memory leaks *) check_for_mem_leak ctx; + (* Upon a call to the "Abort" special function in the multi-threaded case, we give up and conservatively warn *) + warn_for_multi_threaded_due_to_abort ctx; state | Assert { exp; _ } -> let warn_for_assert_exp = @@ -79,20 +233,33 @@ struct | a when Queries.ID.is_bot a -> M.warn ~category:Assert "assert expression %a is bottom" d_exp exp | a -> begin match Queries.ID.to_bool a with - | Some b -> + | Some b -> ( (* If we know for sure that the expression in "assert" is false => need to check for memory leaks *) - if b = false then - check_for_mem_leak ctx - else () - | None -> check_for_mem_leak ctx ~assert_exp_imprecise:true ~exp:(Some exp) + if b = false then ( + warn_for_multi_threaded_due_to_abort ctx; + check_for_mem_leak ctx + ) + else ()) + | None -> + (warn_for_multi_threaded_due_to_abort ctx; + check_for_mem_leak ctx ~assert_exp_imprecise:true ~exp:(Some exp)) end in warn_for_assert_exp; state + | ThreadExit _ -> + begin match ctx.ask (Queries.CurrentThreadId) with + | `Lifted tid -> + warn_for_thread_return_or_exit ctx false + | _ -> () + end; + state | _ -> state let startstate v = D.bot () let exitstate v = D.top () + + let threadenter ctx ~multiple lval f args = [D.bot ()] end let _ = diff --git a/src/analyses/threadAnalysis.ml b/src/analyses/threadAnalysis.ml index 0264f4b700..01c5dd87fa 100644 --- a/src/analyses/threadAnalysis.ml +++ b/src/analyses/threadAnalysis.ml @@ -22,12 +22,15 @@ struct module P = IdentityP (D) (* transfer functions *) - let return ctx (exp:exp option) (f:fundec) : D.t = + let handle_thread_return ctx (exp: exp option) = let tid = ThreadId.get_current (Analyses.ask_of_ctx ctx) in - begin match tid with + match tid with | `Lifted tid -> ctx.sideg tid (false, TS.bot (), not (D.is_empty ctx.local)) | _ -> () - end; + + let return ctx (exp:exp option) _ : D.t = + if ctx.ask Queries.MayBeThreadReturn then + handle_thread_return ctx exp; ctx.local let rec is_not_unique ctx tid = @@ -64,6 +67,9 @@ struct | [t] -> join_thread ctx.local t (* single thread *) | _ -> ctx.local (* if several possible threads are may-joined, none are must-joined *) | exception SetDomain.Unsupported _ -> ctx.local) + | ThreadExit { ret_val } -> + handle_thread_return ctx (Some ret_val); + ctx.local | _ -> ctx.local let query ctx (type a) (q: a Queries.t): a Queries.result = diff --git a/src/autoTune.ml b/src/autoTune.ml index 79f5f51a77..0752d756e6 100644 --- a/src/autoTune.ml +++ b/src/autoTune.ml @@ -186,7 +186,7 @@ let enableAnalyses anas = (*escape is also still enabled, because otherwise we get a warning*) (*does not consider dynamic calls!*) -let notNeccessaryThreadAnalyses = ["race"; "deadlock"; "maylocks"; "symb_locks"; "thread"; "threadid"; "threadJoins"; "threadreturn"] +let notNeccessaryThreadAnalyses = ["race"; "deadlock"; "maylocks"; "symb_locks"; "thread"; "threadid"; "threadJoins"; "threadreturn"; "mhp"; "region"] let reduceThreadAnalyses () = let isThreadCreate = function | LibraryDesc.ThreadCreate _ -> true diff --git a/src/common/util/options.schema.json b/src/common/util/options.schema.json index bbca23cbe1..13ccd687be 100644 --- a/src/common/util/options.schema.json +++ b/src/common/util/options.schema.json @@ -542,9 +542,39 @@ "title": "ana.autotune.activated", "description": "Lists of activated tuning options.", "type": "array", - "items": { "type": "string" }, + "items": { + "type": "string", + "enum": [ + "congruence", + "singleThreaded", + "specification", + "mallocWrappers", + "noRecursiveIntervals", + "enums", + "loopUnrollHeuristic", + "forceLoopUnrollForFewLoops", + "arrayDomain", + "octagon", + "wideningThresholds", + "memsafetySpecification", + "termination", + "tmpSpecialAnalysis" + ] + }, "default": [ - "congruence", "singleThreaded", "specification", "mallocWrappers", "noRecursiveIntervals", "enums", "loopUnrollHeuristic", "arrayDomain", "octagon", "wideningThresholds", "memsafetySpecification", "tmpSpecialAnalysis" + "congruence", + "singleThreaded", + "specification", + "mallocWrappers", + "noRecursiveIntervals", + "enums", + "loopUnrollHeuristic", + "arrayDomain", + "octagon", + "wideningThresholds", + "memsafetySpecification", + "termination", + "tmpSpecialAnalysis" ] } }, @@ -1008,6 +1038,12 @@ "type": "boolean", "default": true }, + "call": { + "title": "ana.race.call", + "description": "Report races for thread-unsafe function calls.", + "type": "boolean", + "default": true + }, "direct-arithmetic": { "title": "ana.race.direct-arithmetic", "description": "Collect and distribute direct (i.e. not in a field) accesses to arithmetic types.", @@ -2164,6 +2200,25 @@ "description": "Output messages in deterministic order. Useful for cram testing.", "type": "boolean", "default": false + }, + "memleak": { + "title": "warn.memleak", + "type":"object", + "properties": { + "memcleanup": { + "title": "warn.memleak.memcleanup", + "description": "Enable memory leak warnings only for violations of the SV-COMP \"valid-memcleanup\" category", + "type": "boolean", + "default": false + }, + "memtrack": { + "title": "warn.memleak.memtrack", + "description": "Enable memory leak warnings only for violations of the SV-COMP \"valid-memtrack\" category", + "type": "boolean", + "default": false + } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -2487,6 +2542,12 @@ "type": "string", "default": "" }, + "strict": { + "title": "witness.yaml.strict", + "description": "", + "type": "boolean", + "default": false + }, "unassume": { "title": "witness.yaml.unassume", "description": "YAML witness input path", diff --git a/src/domains/access.ml b/src/domains/access.ml index 3ba7aaee74..baa9d34220 100644 --- a/src/domains/access.ml +++ b/src/domains/access.ml @@ -444,6 +444,8 @@ let may_race A.{kind; acc; _} A.{kind=kind2; acc=acc2; _} = | Read, Read -> false (* two read/read accesses do not race *) | Free, _ | _, Free when not (get_bool "ana.race.free") -> false + | Call, _ + | _, Call when not (get_bool "ana.race.call") -> false | _, _ -> MCPAccess.A.may_race acc acc2 (* analysis-specific information excludes race *) (** Access sets for race detection and warnings. *) diff --git a/src/maingoblint.ml b/src/maingoblint.ml index 82a19aa4ae..d81d3cfb71 100644 --- a/src/maingoblint.ml +++ b/src/maingoblint.ml @@ -262,6 +262,15 @@ let preprocess_files () = (* Preprocessor flags *) let cppflags = ref (get_string_list "pre.cppflags") in + if get_bool "ana.sv-comp.enabled" then ( + let architecture_flag = match get_string "exp.architecture" with + | "32bit" -> "-m32" + | "64bit" -> "-m64" + | _ -> assert false + in + cppflags := architecture_flag :: !cppflags + ); + (* the base include directory *) (* TODO: any better way? dune executable promotion doesn't add _build sites *) let source_lib_dirs = diff --git a/src/util/std/gobYaml.ml b/src/util/std/gobYaml.ml index a4f3e597aa..131daaaebb 100644 --- a/src/util/std/gobYaml.ml +++ b/src/util/std/gobYaml.ml @@ -1,3 +1,14 @@ +let to_string' ?(len=65535 * 4) ?encoding ?scalar_style ?layout_style v = + assert (len >= 1); + let rec aux len = + match Yaml.to_string ~len ?encoding ?scalar_style ?layout_style v with + | Ok _ as o -> o + | Error (`Msg ("scalar failed" | "doc_end failed")) when len < Sys.max_string_length / 2 -> + aux (len * 2) + | Error (`Msg _) as e -> e + in + aux len + include Yaml.Util include GobResult.Syntax diff --git a/src/witness/witness.ml b/src/witness/witness.ml index 52b1131821..29f337a668 100644 --- a/src/witness/witness.ml +++ b/src/witness/witness.ml @@ -695,11 +695,18 @@ struct | Some false -> print_svcomp_result "ERROR (verify)" | _ -> if get_string "witness.yaml.validate" <> "" then ( - if !YamlWitness.cnt_refuted > 0 then + match get_bool "witness.yaml.strict" with + | true when !YamlWitness.cnt_error > 0 -> + print_svcomp_result "ERROR (witness error)" + | true when !YamlWitness.cnt_unsupported > 0 -> + print_svcomp_result "ERROR (witness unsupported)" + | true when !YamlWitness.cnt_disabled > 0 -> + print_svcomp_result "ERROR (witness disabled)" + | _ when !YamlWitness.cnt_refuted > 0 -> print_svcomp_result (Result.to_string (False None)) - else if !YamlWitness.cnt_unconfirmed > 0 then + | _ when !YamlWitness.cnt_unconfirmed > 0 -> print_svcomp_result (Result.to_string Unknown) - else + | _ -> write entrystates ) else diff --git a/src/witness/yamlWitness.ml b/src/witness/yamlWitness.ml index f8532a1551..635ba4ad72 100644 --- a/src/witness/yamlWitness.ml +++ b/src/witness/yamlWitness.ml @@ -143,12 +143,11 @@ struct } end -let yaml_entries_to_file ?(invariants=0) yaml_entries file = +let yaml_entries_to_file yaml_entries file = let yaml = `A yaml_entries in (* Yaml_unix.to_file_exn file yaml *) (* to_file/to_string uses a fixed-size buffer... *) - (* estimate how big it should be + extra in case empty *) - let text = match Yaml.to_string ~len:((List.length yaml_entries + invariants) * 4096 + 2048) yaml with + let text = match GobYaml.to_string' yaml with | Ok text -> text | Error (`Msg m) -> failwith ("Yaml.to_string: " ^ m) in @@ -171,6 +170,16 @@ struct module FCMap = BatHashtbl.Make (Printable.Prod (CilType.Fundec) (Spec.C)) type con_inv = {node: Node.t; context: Spec.C.t; invariant: Invariant.t; state: Spec.D.t} + (* TODO: fix location hack *) + module LH = BatHashtbl.Make (CilType.Location) + let location2nodes: Node.t list LH.t Lazy.t = lazy ( + let lh = LH.create 113 in + NH.iter (fun n _ -> + LH.modify_def [] (Node.location n) (List.cons n) lh + ) (Lazy.force nh); + lh + ) + let write () = let input_files = GobConfig.get_string_list "files" in let data_model = match GobConfig.get_string "exp.architecture" with @@ -234,16 +243,21 @@ struct (* Generate location invariants (without precondition) *) let entries = if entry_type_enabled YamlWitnessType.LocationInvariant.entry_type then ( - NH.fold (fun n local acc -> - let loc = Node.location n in - if is_invariant_node n then ( - let lvals = local_lvals n local in - match R.ask_local_node n ~local (Invariant {Invariant.default_context with lvals}) with + LH.fold (fun loc ns acc -> + if List.exists is_invariant_node ns then ( + let inv = List.fold_left (fun acc n -> + let local = try NH.find (Lazy.force nh) n with Not_found -> Spec.D.bot () in + let lvals = local_lvals n local in + Invariant.(acc || R.ask_local_node n ~local (Invariant {Invariant.default_context with lvals})) [@coverage off] (* bisect_ppx cannot handle redefined (||) *) + ) (Invariant.bot ()) ns + in + match inv with | `Lifted inv -> + let fundec = Node.find_fundec (List.hd ns) in (* TODO: fix location hack *) + let location_function = fundec.svar.vname in + let location = Entry.location ~location:loc ~location_function in let invs = WitnessUtil.InvariantExp.process_exp inv in List.fold_left (fun acc inv -> - let location_function = (Node.find_fundec n).svar.vname in - let location = Entry.location ~location:loc ~location_function in let invariant = Entry.invariant (CilType.Exp.show inv) in let entry = Entry.location_invariant ~task ~location ~invariant in entry :: acc @@ -253,7 +267,7 @@ struct ) else acc - ) (Lazy.force nh) entries + ) (Lazy.force location2nodes) entries ) else entries @@ -262,15 +276,20 @@ struct (* Generate loop invariants (without precondition) *) let entries = if entry_type_enabled YamlWitnessType.LoopInvariant.entry_type then ( - NH.fold (fun n local acc -> - let loc = Node.location n in - if WitnessInvariant.emit_loop_head && WitnessUtil.NH.mem WitnessInvariant.loop_heads n then ( - match R.ask_local_node n ~local (Invariant Invariant.default_context) with + LH.fold (fun loc ns acc -> + if WitnessInvariant.emit_loop_head && List.exists (WitnessUtil.NH.mem WitnessInvariant.loop_heads) ns then ( + let inv = List.fold_left (fun acc n -> + let local = try NH.find (Lazy.force nh) n with Not_found -> Spec.D.bot () in + Invariant.(acc || R.ask_local_node n ~local (Invariant Invariant.default_context)) [@coverage off] (* bisect_ppx cannot handle redefined (||) *) + ) (Invariant.bot ()) ns + in + match inv with | `Lifted inv -> + let fundec = Node.find_fundec (List.hd ns) in (* TODO: fix location hack *) + let location_function = fundec.svar.vname in + let location = Entry.location ~location:loc ~location_function in let invs = WitnessUtil.InvariantExp.process_exp inv in List.fold_left (fun acc inv -> - let location_function = (Node.find_fundec n).svar.vname in - let location = Entry.location ~location:loc ~location_function in let invariant = Entry.invariant (CilType.Exp.show inv) in let entry = Entry.loop_invariant ~task ~location ~invariant in entry :: acc @@ -280,7 +299,7 @@ struct ) else acc - ) (Lazy.force nh) entries + ) (Lazy.force location2nodes) entries ) else entries @@ -412,23 +431,28 @@ struct in (* Generate invariant set *) - let (entries, invariants) = + let entries = if entry_type_enabled YamlWitnessType.InvariantSet.entry_type then ( let invariants = [] in (* Generate location invariants *) let invariants = if invariant_type_enabled YamlWitnessType.InvariantSet.LocationInvariant.invariant_type then ( - NH.fold (fun n local acc -> - let loc = Node.location n in - if is_invariant_node n then ( - let lvals = local_lvals n local in - match R.ask_local_node n ~local (Invariant {Invariant.default_context with lvals}) with + LH.fold (fun loc ns acc -> + if List.exists is_invariant_node ns then ( + let inv = List.fold_left (fun acc n -> + let local = try NH.find (Lazy.force nh) n with Not_found -> Spec.D.bot () in + let lvals = local_lvals n local in + Invariant.(acc || R.ask_local_node n ~local (Invariant {Invariant.default_context with lvals})) [@coverage off] (* bisect_ppx cannot handle redefined (||) *) + ) (Invariant.bot ()) ns + in + match inv with | `Lifted inv -> + let fundec = Node.find_fundec (List.hd ns) in (* TODO: fix location hack *) + let location_function = fundec.svar.vname in + let location = Entry.location ~location:loc ~location_function in let invs = WitnessUtil.InvariantExp.process_exp inv in List.fold_left (fun acc inv -> - let location_function = (Node.find_fundec n).svar.vname in - let location = Entry.location ~location:loc ~location_function in let invariant = CilType.Exp.show inv in let invariant = Entry.location_invariant' ~location ~invariant in invariant :: acc @@ -438,7 +462,7 @@ struct ) else acc - ) (Lazy.force nh) invariants + ) (Lazy.force location2nodes) invariants ) else invariants @@ -447,15 +471,20 @@ struct (* Generate loop invariants *) let invariants = if invariant_type_enabled YamlWitnessType.InvariantSet.LoopInvariant.invariant_type then ( - NH.fold (fun n local acc -> - let loc = Node.location n in - if WitnessInvariant.emit_loop_head && WitnessUtil.NH.mem WitnessInvariant.loop_heads n then ( - match R.ask_local_node n ~local (Invariant Invariant.default_context) with + LH.fold (fun loc ns acc -> + if WitnessInvariant.emit_loop_head && List.exists (WitnessUtil.NH.mem WitnessInvariant.loop_heads) ns then ( + let inv = List.fold_left (fun acc n -> + let local = try NH.find (Lazy.force nh) n with Not_found -> Spec.D.bot () in + Invariant.(acc || R.ask_local_node n ~local (Invariant Invariant.default_context)) [@coverage off] (* bisect_ppx cannot handle redefined (||) *) + ) (Invariant.bot ()) ns + in + match inv with | `Lifted inv -> + let fundec = Node.find_fundec (List.hd ns) in (* TODO: fix location hack *) + let location_function = fundec.svar.vname in + let location = Entry.location ~location:loc ~location_function in let invs = WitnessUtil.InvariantExp.process_exp inv in List.fold_left (fun acc inv -> - let location_function = (Node.find_fundec n).svar.vname in - let location = Entry.location ~location:loc ~location_function in let invariant = CilType.Exp.show inv in let invariant = Entry.loop_invariant' ~location ~invariant in invariant :: acc @@ -465,7 +494,7 @@ struct ) else acc - ) (Lazy.force nh) invariants + ) (Lazy.force location2nodes) invariants ) else invariants @@ -473,10 +502,10 @@ struct let invariants = List.rev invariants in let entry = Entry.invariant_set ~task ~invariants in - (entry :: entries, List.length invariants) + entry :: entries ) else - (entries, 0) + entries in let yaml_entries = List.rev_map YamlWitnessType.Entry.to_yaml entries in (* reverse to make entries in file in the same order as generation messages *) @@ -485,7 +514,7 @@ struct (Pretty.dprintf "total generation entries: %d" (List.length yaml_entries), None); ]; - yaml_entries_to_file ~invariants yaml_entries (Fpath.v (GobConfig.get_string "witness.yaml.path")) + yaml_entries_to_file yaml_entries (Fpath.v (GobConfig.get_string "witness.yaml.path")) let write () = Timing.wrap "yaml witness" write () diff --git a/src/witness/yamlWitnessType.ml b/src/witness/yamlWitnessType.ml index f9bcf3235f..de9fa151d8 100644 --- a/src/witness/yamlWitnessType.ml +++ b/src/witness/yamlWitnessType.ml @@ -311,13 +311,16 @@ struct } let to_yaml {invariant_type} = - `O ([ - ("type", `String (InvariantType.invariant_type invariant_type)); - ] @ InvariantType.to_yaml' invariant_type) + `O [ + ("invariant", `O ([ + ("type", `String (InvariantType.invariant_type invariant_type)); + ] @ InvariantType.to_yaml' invariant_type) + ) + ] let of_yaml y = let open GobYaml in - let+ invariant_type = y |> InvariantType.of_yaml in + let+ invariant_type = y |> find "invariant" >>= InvariantType.of_yaml in {invariant_type} end diff --git a/sv-comp/archive.sh b/sv-comp/archive.sh index 9bab49f70d..5d8605dc70 100755 --- a/sv-comp/archive.sh +++ b/sv-comp/archive.sh @@ -4,7 +4,7 @@ make clean -git tag -m "SV-COMP 2023" svcomp23 +git tag -m "SV-COMP 2024" svcomp24 dune build --profile=release src/goblint.exe rm -f goblint @@ -18,7 +18,7 @@ cp _opam/share/apron/lib/libapron.so lib/ cp _opam/share/apron/lib/liboctD.so lib/ cp _opam/share/apron/lib/libboxD.so lib/ cp _opam/share/apron/lib/libpolkaMPQ.so lib/ -cp _opam/.opam-switch/sources/apron/COPYING lib/LICENSE.APRON +wget -O lib/LICENSE.APRON https://raw.githubusercontent.com/antoinemine/apron/master/COPYING # done outside to ensure archive contains goblint/ directory cd .. @@ -32,7 +32,8 @@ zip goblint/sv-comp/goblint.zip \ goblint/lib/libboxD.so \ goblint/lib/libpolkaMPQ.so \ goblint/lib/LICENSE.APRON \ - goblint/conf/svcomp23.json \ + goblint/conf/svcomp24.json \ + goblint/conf/svcomp24-validate.json \ goblint/lib/libc/stub/include/assert.h \ goblint/lib/goblint/runtime/include/goblint.h \ goblint/lib/libc/stub/src/stdlib.c \ diff --git a/tests/regression/76-memleak/08-unreachable-mem.c b/tests/regression/76-memleak/08-unreachable-mem.c new file mode 100644 index 0000000000..08e7b4e741 --- /dev/null +++ b/tests/regression/76-memleak/08-unreachable-mem.c @@ -0,0 +1,12 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak +#include + +int *g; + +int main(int argc, char const *argv[]) { + g = malloc(sizeof(int)); + // Reference to g's heap contents is lost here + g = NULL; + + return 0; //WARN +} diff --git a/tests/regression/76-memleak/09-unreachable-with-local-var.c b/tests/regression/76-memleak/09-unreachable-with-local-var.c new file mode 100644 index 0000000000..bc71bb560e --- /dev/null +++ b/tests/regression/76-memleak/09-unreachable-with-local-var.c @@ -0,0 +1,15 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak +#include + +int *g; + +int main(int argc, char const *argv[]) { + g = malloc(sizeof(int)); + // Reference to g's heap contents is lost here + g = NULL; + + // According to `valid-memtrack`, the memory of p is unreachable and we don't have a false positive + int *p = malloc(sizeof(int)); + + return 0; //WARN +} diff --git a/tests/regression/76-memleak/10-global-struct-no-ptr.c b/tests/regression/76-memleak/10-global-struct-no-ptr.c new file mode 100644 index 0000000000..490b2bb443 --- /dev/null +++ b/tests/regression/76-memleak/10-global-struct-no-ptr.c @@ -0,0 +1,16 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak +#include + +typedef struct st { + int *a; + int b; +} st; + +st st_nonptr; + +int main(int argc, char const *argv[]) { + st_nonptr.a = malloc(sizeof(int)); + st_nonptr.a = NULL; + + return 0; //WARN +} diff --git a/tests/regression/76-memleak/11-global-struct-ptr.c b/tests/regression/76-memleak/11-global-struct-ptr.c new file mode 100644 index 0000000000..4ebe1c16b8 --- /dev/null +++ b/tests/regression/76-memleak/11-global-struct-ptr.c @@ -0,0 +1,19 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak +#include + +typedef struct st { + int *a; + int b; +} st; + +st *st_ptr; + +int main(int argc, char const *argv[]) { + st_ptr = malloc(sizeof(st)); + st_ptr->a = malloc(sizeof(int)); + st_ptr->a = NULL; + free(st_ptr); + + // Only st_ptr->a is causing trouble here + return 0; //WARN +} diff --git a/tests/regression/76-memleak/12-global-nested-struct-ptr.c b/tests/regression/76-memleak/12-global-nested-struct-ptr.c new file mode 100644 index 0000000000..e0f5175064 --- /dev/null +++ b/tests/regression/76-memleak/12-global-nested-struct-ptr.c @@ -0,0 +1,25 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak +#include + +typedef struct st { + int *a; + int b; +} st; + +typedef struct st2 { + st *st_ptr; +} st2; + +st2 *st_var; + +int main(int argc, char const *argv[]) { + st_var = malloc(sizeof(st2)); + st_var->st_ptr = malloc(sizeof(st)); + st_var->st_ptr->a = malloc(sizeof(int)); + st_var->st_ptr->a = NULL; + free(st_var->st_ptr); + free(st_var); + + // Only st_var->st_ptr->a is causing trouble here + return 0; //WARN +} diff --git a/tests/regression/76-memleak/13-global-nested-struct-ptr-reachable.c b/tests/regression/76-memleak/13-global-nested-struct-ptr-reachable.c new file mode 100644 index 0000000000..1726625a59 --- /dev/null +++ b/tests/regression/76-memleak/13-global-nested-struct-ptr-reachable.c @@ -0,0 +1,29 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak +#include + +typedef struct st { + int *a; + int b; +} st; + +typedef struct st2 { + st *st_ptr; +} st2; + +st2 *st_var; + +int main(int argc, char const *argv[]) { + st_var = malloc(sizeof(st2)); + st_var->st_ptr = malloc(sizeof(st)); + int *local_ptr = malloc(sizeof(int)); + st_var->st_ptr->a = local_ptr; + local_ptr = NULL; + + free(st_var->st_ptr); + free(st_var); + + // local_ptr's memory is reachable through st_var->st_ptr->a + // It's leaked, because we don't call free() on it + // Hence, there should be a single warning for a memory leak, but not for unreachable memory + return 0; //WARN +} diff --git a/tests/regression/76-memleak/14-global-nested-struct-non-ptr-reachable.c b/tests/regression/76-memleak/14-global-nested-struct-non-ptr-reachable.c new file mode 100644 index 0000000000..1153bd81e0 --- /dev/null +++ b/tests/regression/76-memleak/14-global-nested-struct-non-ptr-reachable.c @@ -0,0 +1,25 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak +#include + +typedef struct st { + int *a; + int b; +} st; + +typedef struct st2 { + st *st_ptr; +} st2; + +st2 st_var; + +int main(int argc, char const *argv[]) { + st_var.st_ptr = malloc(sizeof(st)); + int *local_ptr = malloc(sizeof(int)); + st_var.st_ptr->a = local_ptr; + local_ptr = NULL; + free(st_var.st_ptr); + + // local_ptr's memory is reachable through st_var.st_ptr->a, but it's not freed + // Hence, there should be only a single warning for a memory leak, but not for unreachable memory + return 0; //WARN +} diff --git a/tests/regression/76-memleak/20-invalid-memcleanup-multi-threaded.c b/tests/regression/76-memleak/20-invalid-memcleanup-multi-threaded.c new file mode 100644 index 0000000000..038801f219 --- /dev/null +++ b/tests/regression/76-memleak/20-invalid-memcleanup-multi-threaded.c @@ -0,0 +1,36 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak --set ana.activated[+] thread +#include +#include + +int *g; +int *m1; + +void *f1(void *arg) { + m1 = malloc(sizeof(int)); + // Thread t1 leaks m1 here + pthread_exit(NULL); //WARN +} + +void *f2(void *arg) { + int *m2; + m2 = malloc(sizeof(int)); + free(m2); // No leak for thread t2, since it calls free before exiting + pthread_exit(NULL); //NOWARN +} + +int main(int argc, char const *argv[]) { + g = malloc(sizeof(int)); + pthread_t t1; + pthread_create(&t1, NULL, f1, NULL); + + pthread_t t2; + pthread_create(&t2, NULL, f2, NULL); + + free(g); + + pthread_join(t1, NULL); + pthread_join(t2, NULL); + + // main thread is not leaking anything + return 0; //NOWARN +} diff --git a/tests/regression/76-memleak/21-invalid-memcleanup-multi-threaded-abort.c b/tests/regression/76-memleak/21-invalid-memcleanup-multi-threaded-abort.c new file mode 100644 index 0000000000..eaba1e91b5 --- /dev/null +++ b/tests/regression/76-memleak/21-invalid-memcleanup-multi-threaded-abort.c @@ -0,0 +1,35 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak --set ana.activated[+] thread +#include + +int *g; +int *m1; + +void *f1(void *arg) { + m1 = malloc(sizeof(int)); + // Thread t1 leaks m1 here + exit(2); //WARN +} + +void *f2(void *arg) { + int *m2; + m2 = malloc(sizeof(int)); + free(m2); // No leak for thread t2, since it calls free before exiting + pthread_exit(NULL); //NOWARN +} + +int main(int argc, char const *argv[]) { + g = malloc(sizeof(int)); + pthread_t t1; + pthread_create(&t1, NULL, f1, NULL); + + pthread_t t2; + pthread_create(&t2, NULL, f2, NULL); + + free(g); + + pthread_join(t1, NULL); + pthread_join(t2, NULL); + + // main thread is not leaking anything + return 0; //NOWARN +} diff --git a/tests/regression/76-memleak/22-leak-later.c b/tests/regression/76-memleak/22-leak-later.c new file mode 100644 index 0000000000..6e6e51bbdc --- /dev/null +++ b/tests/regression/76-memleak/22-leak-later.c @@ -0,0 +1,25 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak +#include +#include + +int *g; +int *m1; +int *m2; + +void *f1(void *arg) { + int top; + + // Thread t1 leaks m0 here + exit(2); //WARN +} + +int main(int argc, char const *argv[]) { + pthread_t t1; + pthread_create(&t1, NULL, f1, NULL); + + int* m0 = malloc(sizeof(int)); + free(m0); + + // main thread is not leaking anything + return 0; +} diff --git a/tests/regression/76-memleak/23-leak-later-nested.c b/tests/regression/76-memleak/23-leak-later-nested.c new file mode 100644 index 0000000000..952dc66334 --- /dev/null +++ b/tests/regression/76-memleak/23-leak-later-nested.c @@ -0,0 +1,34 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak +#include +#include + +int *g; +int *m1; +int *m2; + +void *f2(void *arg) { + // Thread t2 leaks m0 and t1_ptr here + quick_exit(2); //WARN +} + +void *f1(void *arg) { + pthread_t t2; + pthread_create(&t2, NULL, f2, NULL); + + int *t1_ptr = malloc(sizeof(int)); + + pthread_join(t2, NULL); + // t1_ptr is leaked, since t2 calls quick_exit() potentially before this program point + free(t1_ptr); +} + +int main(int argc, char const *argv[]) { + pthread_t t1; + pthread_create(&t1, NULL, f1, NULL); + + int* m0 = malloc(sizeof(int)); + free(m0); + + // main thread is not leaking anything + return 0; +} diff --git a/tests/regression/76-memleak/24-multi-threaded-assert.c b/tests/regression/76-memleak/24-multi-threaded-assert.c new file mode 100644 index 0000000000..309a5dde75 --- /dev/null +++ b/tests/regression/76-memleak/24-multi-threaded-assert.c @@ -0,0 +1,34 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak --disable warn.assert +#include +#include +#include + +int *g; +int *m1; +int *m2; + +void *f2(void *arg) { + // Thread t2 leaks m0 and t1_ptr here + assert(0); //WARN +} + +void *f1(void *arg) { + pthread_t t2; + pthread_create(&t2, NULL, f2, NULL); + + int *t1_ptr = malloc(sizeof(int)); + assert(1); //NOWARN + pthread_join(t2, NULL); + free(t1_ptr); +} + +int main(int argc, char const *argv[]) { + pthread_t t1; + pthread_create(&t1, NULL, f1, NULL); + + int* m0 = malloc(sizeof(int)); + free(m0); + + // main thread is not leaking anything + return 0; +} diff --git a/tests/regression/76-memleak/25-assert-unknown-multi-threaded.c b/tests/regression/76-memleak/25-assert-unknown-multi-threaded.c new file mode 100644 index 0000000000..95eb291887 --- /dev/null +++ b/tests/regression/76-memleak/25-assert-unknown-multi-threaded.c @@ -0,0 +1,20 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak --disable warn.assert +#include +#include +#include + +void *f1(void *arg) { + int top; + assert(top); //WARN +} + +int main(int argc, char const *argv[]) { + pthread_t t1; + pthread_create(&t1, NULL, f1, NULL); + + int* m0 = malloc(sizeof(int)); + free(m0); + + // main thread is not leaking anything + return 0; +} diff --git a/tests/regression/76-memleak/26-invalid-memcleanup-multi-threaded-betterpiv.c b/tests/regression/76-memleak/26-invalid-memcleanup-multi-threaded-betterpiv.c new file mode 100644 index 0000000000..9f636ab587 --- /dev/null +++ b/tests/regression/76-memleak/26-invalid-memcleanup-multi-threaded-betterpiv.c @@ -0,0 +1,36 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak --set ana.base.privatization mutex-meet-tid --set ana.path_sens[+] threadflag --set ana.activated[+] thread +#include +#include + +int *g; +int *m1; +int *m2; + +void *f1(void *arg) { + m1 = malloc(sizeof(int)); + // Thread t1 leaks m1 here + pthread_exit(NULL); //WARN +} + +void *f2(void *arg) { + m2 = malloc(sizeof(int)); + free(m2); // No leak for thread t2, since it calls free before exiting + pthread_exit(NULL); //NOWARN +} + +int main(int argc, char const *argv[]) { + g = malloc(sizeof(int)); + pthread_t t1; + pthread_create(&t1, NULL, f1, NULL); + + pthread_t t2; + pthread_create(&t2, NULL, f2, NULL); + + free(g); + + pthread_join(t1, NULL); + pthread_join(t2, NULL); + + // main thread is not leaking anything + return 0; //NOWARN +} diff --git a/tests/regression/76-memleak/27-mem-leak-not-joined-thread.c b/tests/regression/76-memleak/27-mem-leak-not-joined-thread.c new file mode 100644 index 0000000000..15f249ffe1 --- /dev/null +++ b/tests/regression/76-memleak/27-mem-leak-not-joined-thread.c @@ -0,0 +1,19 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak --set ana.activated[+] thread +#include +#include + +int *m1; + +void *f1(void *arg) { + m1 = malloc(sizeof(int)); + while (1); + return NULL; +} + +int main(int argc, char const *argv[]) { + pthread_t t1; + pthread_create(&t1, NULL, f1, NULL); + + // memory from thread f1 which was not joined into main, is not freed + return 0; //WARN +} \ No newline at end of file diff --git a/tests/regression/76-memleak/28-no-mem-leak-thread-exit-main.c b/tests/regression/76-memleak/28-no-mem-leak-thread-exit-main.c new file mode 100644 index 0000000000..f7340d1d4f --- /dev/null +++ b/tests/regression/76-memleak/28-no-mem-leak-thread-exit-main.c @@ -0,0 +1,22 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak --set ana.activated[+] thread +#include +#include + +int *m1; + +void *f1(void *arg) { + m1 = malloc(sizeof(int)); + while (1); + return NULL; +} + +int main(int argc, char const *argv[]) { + pthread_t t1; + pthread_create(&t1, NULL, f1, NULL); + + // A pthread_exit called in main will wait for other threads to finish + // Therefore, no memory leak here + pthread_exit(NULL); // NOWARN + + return 0; // NOWARN (unreachable) +} \ No newline at end of file diff --git a/tests/regression/76-memleak/29-mem-leak-thread-return.c b/tests/regression/76-memleak/29-mem-leak-thread-return.c new file mode 100644 index 0000000000..bec64ca22f --- /dev/null +++ b/tests/regression/76-memleak/29-mem-leak-thread-return.c @@ -0,0 +1,26 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak --set ana.activated[+] thread +#include +#include + +int *m1; + +void *f2(void *arg) { + m1 = malloc(sizeof(int)); + while (1); + return NULL; +} + +void *f1(void *arg) { + pthread_t t2; + pthread_create(&t2, NULL, f2, NULL); + + return NULL; +} + +int main(int argc, char const *argv[]) { + pthread_t t1; + pthread_create(&t1, NULL, f1, NULL); + pthread_join(t1, NULL); + + return 0; // WARN +} \ No newline at end of file diff --git a/tests/regression/76-memleak/30-mem-leak-thread-exit.c b/tests/regression/76-memleak/30-mem-leak-thread-exit.c new file mode 100644 index 0000000000..e98ae3f346 --- /dev/null +++ b/tests/regression/76-memleak/30-mem-leak-thread-exit.c @@ -0,0 +1,27 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak --set ana.activated[+] thread +#include +#include + +int *m1; + +void *f2(void *arg) { + m1 = malloc(sizeof(int)); + while (1); + return NULL; +} + +void *f1(void *arg) { + pthread_t t2; + pthread_create(&t2, NULL, f2, NULL); + + pthread_exit(NULL); + return NULL; +} + +int main(int argc, char const *argv[]) { + pthread_t t1; + pthread_create(&t1, NULL, f1, NULL); + pthread_join(t1, NULL); + + return 0; // WARN +} \ No newline at end of file diff --git a/tests/regression/76-memleak/31-no-mem-leak-return.c b/tests/regression/76-memleak/31-no-mem-leak-return.c new file mode 100644 index 0000000000..70e0c66216 --- /dev/null +++ b/tests/regression/76-memleak/31-no-mem-leak-return.c @@ -0,0 +1,32 @@ +//PARAM: --set ana.malloc.unique_address_count 1 --set ana.activated[+] memLeak --set ana.activated[+] thread +#include +#include + + +void *f2(void *arg) { + int* m1 = malloc(sizeof(int)); + free(m1); + return NULL; +} + +// We check here that the analysis can distinguish between thread returns and normal returns + +void startf2(pthread_t* t){ + pthread_create(t, NULL, f2, NULL); + return; //NOWARN +} + +void *f1(void *arg) { + pthread_t t2; + startf2(&t2); + pthread_join(t2, NULL); + return NULL; // NOWARN +} + +int main(int argc, char const *argv[]) { + pthread_t t1; + pthread_create(&t1, NULL, f1, NULL); + pthread_join(t1, NULL); + + return 0; // NOWARN +} \ No newline at end of file