diff --git a/infer/src/absint/AbstractDomain.ml b/infer/src/absint/AbstractDomain.ml index f263f5220..3756da823 100644 --- a/infer/src/absint/AbstractDomain.ml +++ b/infer/src/absint/AbstractDomain.ml @@ -647,6 +647,8 @@ module SafeInvertedMap (Key : PrettyPrintable.PrintableOrderedType) (ValueDomain let of_seq = M.of_seq + let to_seq = M.to_seq + let mapi f m = let tops = ref [] in let f k v = diff --git a/infer/src/istd/ISeq.ml b/infer/src/istd/ISeq.ml new file mode 100644 index 000000000..1c7518998 --- /dev/null +++ b/infer/src/istd/ISeq.ml @@ -0,0 +1,11 @@ +(* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +open! IStd + +let fold_result seq ~init ~f = + Seq.fold_left (fun acc_result x -> Result.bind acc_result ~f:(fun acc -> f acc x)) (Ok init) seq diff --git a/infer/src/istd/ISeq.mli b/infer/src/istd/ISeq.mli new file mode 100644 index 000000000..f4ee8c599 --- /dev/null +++ b/infer/src/istd/ISeq.mli @@ -0,0 +1,11 @@ +(* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +open! IStd + +val fold_result : + 'a Seq.t -> init:'accum -> f:('accum -> 'a -> ('accum, 'e) result) -> ('accum, 'e) result diff --git a/infer/src/istd/PrettyPrintable.ml b/infer/src/istd/PrettyPrintable.ml index b70df295b..445610928 100644 --- a/infer/src/istd/PrettyPrintable.ml +++ b/infer/src/istd/PrettyPrintable.ml @@ -128,6 +128,8 @@ module type MonoMap = sig val fold_mapi : t -> init:'a -> f:(key -> 'a -> value -> 'a * value) -> 'a * t val of_seq : (key * value) Seq.t -> t + + val to_seq : t -> (key * value) Seq.t end module type PPMap = sig diff --git a/infer/src/istd/PrettyPrintable.mli b/infer/src/istd/PrettyPrintable.mli index 373124edf..6e3b31fad 100644 --- a/infer/src/istd/PrettyPrintable.mli +++ b/infer/src/istd/PrettyPrintable.mli @@ -130,6 +130,8 @@ module type MonoMap = sig val fold_mapi : t -> init:'a -> f:(key -> 'a -> value -> 'a * value) -> 'a * t val of_seq : (key * value) Seq.t -> t + + val to_seq : t -> (key * value) Seq.t end module type PPMap = sig diff --git a/infer/src/istd/RecencyMap.ml b/infer/src/istd/RecencyMap.ml index 80a163c92..1930e338c 100644 --- a/infer/src/istd/RecencyMap.ml +++ b/infer/src/istd/RecencyMap.ml @@ -46,6 +46,8 @@ module type S = sig val mem : t -> key -> bool val union_left_biased : t -> t -> t + + val to_seq : t -> (key * value) Seq.t end module Make @@ -139,14 +141,16 @@ module Make else {count_new= next_count_new; new_= (key, value) :: new_without_key; old= map.old} - let fold map ~init ~f = - let acc = List.fold map.new_ ~init ~f in - (* this is quadratic time but the lists are at most [Config.limit] long, assumed small *) - List.fold map.old ~init:acc ~f:(fun acc binding -> - if List.Assoc.mem ~equal:Key.equal map.new_ (fst binding) then acc else f acc binding ) + let to_seq map = + Seq.append (Caml.List.to_seq map.new_) + ( (* this is quadratic time but the lists are at most [Config.limit] long, assumed small *) + Caml.List.to_seq map.old + |> Seq.filter (fun binding -> not (List.Assoc.mem ~equal:Key.equal map.new_ (fst binding))) ) + + let fold map ~init ~f = Seq.fold_left f init (to_seq map) - let bindings map = fold ~init:[] ~f:(fun bindings binding -> binding :: bindings) map + let bindings map = to_seq map |> Caml.List.of_seq let exists map ~f = List.exists map.new_ ~f diff --git a/infer/src/istd/RecencyMap.mli b/infer/src/istd/RecencyMap.mli index 499add971..7852447ab 100644 --- a/infer/src/istd/RecencyMap.mli +++ b/infer/src/istd/RecencyMap.mli @@ -49,6 +49,8 @@ module type S = sig val mem : t -> key -> bool val union_left_biased : t -> t -> t + + val to_seq : t -> (key * value) Seq.t end module Make diff --git a/infer/src/pulse/Pulse.ml b/infer/src/pulse/Pulse.ml index 27fc3fe38..e211c33ae 100644 --- a/infer/src/pulse/Pulse.ml +++ b/infer/src/pulse/Pulse.ml @@ -409,7 +409,8 @@ module PulseTransferFunctions = struct | LatentInvalidAccess _ -> [astate] | ContinueProgram astate -> - [ContinueProgram (PulseOperations.remove_vars vars location astate)] ) + PulseOperations.remove_vars vars location astate + |> PulseReport.report_result tenv proc_desc err_log location ) in if Procname.is_java (Procdesc.get_proc_name proc_desc) then remove_vars vars [ContinueProgram astate] diff --git a/infer/src/pulse/PulseAbductiveDomain.ml b/infer/src/pulse/PulseAbductiveDomain.ml index c4a600150..afc8b111a 100644 --- a/infer/src/pulse/PulseAbductiveDomain.ml +++ b/infer/src/pulse/PulseAbductiveDomain.ml @@ -273,6 +273,10 @@ module AddressAttributes = struct map_post_attrs astate ~f:(BaseAddressAttributes.allocate procname address location) + let get_allocation addr astate = + BaseAddressAttributes.get_allocation addr (astate.post :> base_domain).attrs + + let add_dynamic_type typ address astate = map_post_attrs astate ~f:(BaseAddressAttributes.add_dynamic_type typ address) @@ -619,7 +623,7 @@ let check_memory_leaks unreachable_addrs astate = addr Procname.pp procname ; Error (procname, trace) in - List.fold_result unreachable_addrs ~init:() ~f:(fun () addr -> + ISeq.fold_result unreachable_addrs ~init:() ~f:(fun () addr -> match AddressAttributes.find_opt addr astate with | Some unreachable_attrs -> check_memory_leak addr unreachable_attrs @@ -950,7 +954,7 @@ let summary_of_post tenv pdesc location astate = in match error with | None -> ( - match check_memory_leaks dead_addresses astate_before_filter with + match check_memory_leaks (Caml.List.to_seq dead_addresses) astate_before_filter with | Ok () -> Ok (invalidate_locals pdesc astate) | Error (proc_name, trace) -> diff --git a/infer/src/pulse/PulseAbductiveDomain.mli b/infer/src/pulse/PulseAbductiveDomain.mli index 7bec085b0..af5ecc87f 100644 --- a/infer/src/pulse/PulseAbductiveDomain.mli +++ b/infer/src/pulse/PulseAbductiveDomain.mli @@ -132,6 +132,8 @@ module AddressAttributes : sig val remove_allocation_attr : AbstractValue.t -> t -> t + val get_allocation : AbstractValue.t -> t -> (Procname.t * Trace.t) option + val get_closure_proc_name : AbstractValue.t -> t -> Procname.t option val is_end_of_collection : AbstractValue.t -> t -> bool @@ -156,6 +158,10 @@ val is_local : Var.t -> t -> bool val find_post_cell_opt : AbstractValue.t -> t -> BaseDomain.cell option +val check_memory_leaks : AbstractValue.t Seq.t -> t -> (unit, Procname.t * Trace.t) result +(** [check_memory_leaks unreachable_addrs astate] is an [Error (proc_name, trace)] if one of the + addresses in [unreachable_addrs] was allocated by [proc_name] *) + val discard_unreachable : t -> t (** garbage collect unreachable addresses in the state to make it smaller and return the new state *) diff --git a/infer/src/pulse/PulseBaseAddressAttributes.ml b/infer/src/pulse/PulseBaseAddressAttributes.ml index 02948b84b..0653619f6 100644 --- a/infer/src/pulse/PulseBaseAddressAttributes.ml +++ b/infer/src/pulse/PulseBaseAddressAttributes.ml @@ -130,6 +130,8 @@ let initialize address attrs = else attrs +let get_allocation = get_attribute Attributes.get_allocation + let get_closure_proc_name = get_attribute Attributes.get_closure_proc_name let get_invalid = get_attribute Attributes.get_invalid diff --git a/infer/src/pulse/PulseBaseAddressAttributes.mli b/infer/src/pulse/PulseBaseAddressAttributes.mli index c13b63a88..9f0b522c1 100644 --- a/infer/src/pulse/PulseBaseAddressAttributes.mli +++ b/infer/src/pulse/PulseBaseAddressAttributes.mli @@ -35,6 +35,8 @@ val check_initialized : AbstractValue.t -> t -> (unit, unit) result val invalidate : AbstractValue.t * ValueHistory.t -> Invalidation.t -> Location.t -> t -> t +val get_allocation : AbstractValue.t -> t -> (Procname.t * Trace.t) option + val get_closure_proc_name : AbstractValue.t -> t -> Procname.t option val get_invalid : AbstractValue.t -> t -> (Invalidation.t * Trace.t) option diff --git a/infer/src/pulse/PulseBaseDomain.ml b/infer/src/pulse/PulseBaseDomain.ml index 191a418d9..df7efecd8 100644 --- a/infer/src/pulse/PulseBaseDomain.ml +++ b/infer/src/pulse/PulseBaseDomain.ml @@ -205,7 +205,7 @@ module GraphVisit : sig reached and the access path from that variable to the address. *) val fold_from_addresses : - AbstractValue.t list + AbstractValue.t Seq.t -> t -> init:'accum -> f: @@ -284,17 +284,14 @@ end = struct let fold_from_addresses from astate = - fold_common from astate ~fold:List.fold ~filter:(fun _ -> true) ~visit:visit_address + let seq_fold seq ~init ~f = Seq.fold_left f init seq in + fold_common from astate ~fold:seq_fold ~filter:(fun _ -> true) ~visit:visit_address end include GraphComparison -let reachable_addresses astate = - GraphVisit.fold astate - ~var_filter:(fun _ -> true) - ~init:() ~finish:Fn.id - ~f:(fun _ () _ _ -> Continue ()) - |> fst +let reachable_addresses ?(var_filter = fun _ -> true) astate = + GraphVisit.fold astate ~var_filter ~init:() ~finish:Fn.id ~f:(fun _ () _ _ -> Continue ()) |> fst let reachable_addresses_from addresses astate = diff --git a/infer/src/pulse/PulseBaseDomain.mli b/infer/src/pulse/PulseBaseDomain.mli index ef981bb55..e861d734b 100644 --- a/infer/src/pulse/PulseBaseDomain.mli +++ b/infer/src/pulse/PulseBaseDomain.mli @@ -7,6 +7,7 @@ open! IStd open PulseBasicInterface +module Memory = PulseBaseMemory module F = Format type t = {heap: PulseBaseMemory.t; stack: PulseBaseStack.t; attrs: PulseBaseAddressAttributes.t} @@ -16,11 +17,11 @@ type cell = PulseBaseMemory.Edges.t * Attributes.t val empty : t -val reachable_addresses : t -> AbstractValue.Set.t +val reachable_addresses : ?var_filter:(Var.t -> bool) -> t -> AbstractValue.Set.t (** compute the set of abstract addresses that are "used" in the abstract state, i.e. reachable from the stack variables *) -val reachable_addresses_from : AbstractValue.t list -> t -> AbstractValue.Set.t +val reachable_addresses_from : AbstractValue.t Seq.t -> t -> AbstractValue.Set.t (** compute the set of abstract addresses that are reachable from given abstract addresses *) type mapping @@ -40,3 +41,36 @@ val find_cell_opt : AbstractValue.t -> t -> cell option val pp : F.formatter -> t -> unit val subst_var : AbstractValue.t * AbstractValue.t -> t -> t SatUnsat.t + +module GraphVisit : sig + val fold : + var_filter:(Var.t -> bool) + -> t + -> init:'accum + -> f: + ( Var.t + -> 'accum + -> AbstractValue.t + -> Memory.Access.t list + -> ('accum, 'final) Base.Continue_or_stop.t) + -> finish:('accum -> 'final) + -> AbstractValue.Set.t * 'final + (** Generic graph traversal of the memory starting from each variable in the stack that pass + [var_filter], in order. Returns the result of folding over every address in the graph and the + set of addresses that have been visited before [f] returned [Stop] or all reachable addresses + were seen. [f] is passed each address together with the variable from which the address was + reached and the access path from that variable to the address. *) + + val fold_from_addresses : + AbstractValue.t Seq.t + -> t + -> init:'accum + -> f: + ( 'accum + -> AbstractValue.t + -> Memory.Access.t list + -> ('accum, 'final) Base.Continue_or_stop.t) + -> finish:('accum -> 'final) + -> AbstractValue.Set.t * 'final + (** Similar to [fold], but start from given addresses, instead of stack variables. *) +end diff --git a/infer/src/pulse/PulseCallOperations.ml b/infer/src/pulse/PulseCallOperations.ml index 4c4e44155..1f7de9ef2 100644 --- a/infer/src/pulse/PulseCallOperations.ml +++ b/infer/src/pulse/PulseCallOperations.ml @@ -188,7 +188,9 @@ let apply_callee tenv ~caller_proc_desc callee_pname call_loc callee_exec_state let conservatively_initialize_args arg_values ({AbductiveDomain.post} as astate) = - let reachable_values = BaseDomain.reachable_addresses_from arg_values (post :> BaseDomain.t) in + let reachable_values = + BaseDomain.reachable_addresses_from (Caml.List.to_seq arg_values) (post :> BaseDomain.t) + in AbstractValue.Set.fold AbductiveDomain.initialize reachable_values astate @@ -238,7 +240,6 @@ let call_aux tenv caller_proc_desc call_loc callee_pname ret actuals callee_proc let call tenv ~caller_proc_desc ~(callee_data : (Procdesc.t * PulseSummary.t) option) call_loc callee_pname ~ret ~actuals ~formals_opt (astate : AbductiveDomain.t) = - let get_arg_values () = List.map actuals ~f:(fun ((value, _), _) -> value) in (* a special case for objc nil messaging *) let unknown_objc_nil_messaging astate_unknown procdesc = let result_unknown = @@ -267,8 +268,9 @@ let call tenv ~caller_proc_desc ~(callee_data : (Procdesc.t * PulseSummary.t) op | None -> (* no spec found for some reason (unknown function, ...) *) L.d_printfln "No spec found for %a@\n" Procname.pp callee_pname ; + let arg_values = List.map actuals ~f:(fun ((value, _), _) -> value) in let astate_unknown = - conservatively_initialize_args (get_arg_values ()) astate + conservatively_initialize_args arg_values astate |> unknown_call tenv call_loc (SkippedKnownCall callee_pname) ~ret ~actuals ~formals_opt in let callee_procdesc_opt = AnalysisCallbacks.get_proc_desc callee_pname in diff --git a/infer/src/pulse/PulseOperations.ml b/infer/src/pulse/PulseOperations.ml index ea24da943..776201807 100644 --- a/infer/src/pulse/PulseOperations.ml +++ b/infer/src/pulse/PulseOperations.ml @@ -570,9 +570,87 @@ let get_dynamic_type_unreachable_values vars astate = List.map ~f:(fun (var, _, typ) -> (var, typ)) res -let remove_vars vars location orig_astate = +(** raised by [filter_live_addresses] to stop early when looking for leaks *) +exception NoLeak + +let filter_live_addresses ~is_dead_root potential_leak_addrs astate = + (* stop as soon as we find out that all locations that could potentially cause a leak are still + live *) + if AbstractValue.Set.is_empty potential_leak_addrs then raise NoLeak ; + let potential_leaks = ref potential_leak_addrs in + let mark_reachable addr = + potential_leaks := AbstractValue.Set.remove addr !potential_leaks ; + if AbstractValue.Set.is_empty !potential_leaks then raise NoLeak + in + let pre = (astate.AbductiveDomain.pre :> BaseDomain.t) in + let post = (astate.AbductiveDomain.post :> BaseDomain.t) in + (* filter out addresses live in the post *) + ignore + (BaseDomain.GraphVisit.fold + ~var_filter:(fun var -> not (is_dead_root var)) + post ~init:() + ~f:(fun _ () addr _ -> + mark_reachable addr ; + Continue () ) + ~finish:(fun () -> ())) ; + let collect_reachable_from addrs base_state = + BaseDomain.GraphVisit.fold_from_addresses addrs base_state ~init:() + ~f:(fun () addr _ -> + mark_reachable addr ; + Continue () ) + ~finish:(fun () -> ()) + |> fst + in + (* any address reachable in the pre-condition is not dead as callers can still be holding on to + them; so any address reachable from anything reachable from the precondition is live *) + let reachable_in_pre = + (* start from the *values* of variables, not their addresses; addresses of formals are + meaningless for callers so are not reachable outside the current function *) + let formal_values = + BaseStack.to_seq pre.stack + |> Seq.flat_map (fun (_, (formal_addr, _)) -> + match BaseMemory.find_opt formal_addr pre.heap with + | None -> + Seq.empty + | Some edges -> + BaseMemory.Edges.to_seq edges + |> Seq.map (fun (_access, (value, _)) -> + mark_reachable value ; + value ) ) + in + collect_reachable_from formal_values pre + in + let reachable_from_reachable_in_pre = + collect_reachable_from (AbstractValue.Set.to_seq reachable_in_pre) post + in + ignore reachable_from_reachable_in_pre ; + !potential_leaks + + +let detect_leaks location ~dead_roots astate = + let is_dead_root var = List.mem ~equal:Var.equal dead_roots var in + (* only consider locations that could actually cause a leak if unreachable *) + let allocated_reachable_from_dead_root = + BaseDomain.reachable_addresses ~var_filter:is_dead_root + (astate.AbductiveDomain.post :> BaseDomain.t) + |> AbstractValue.Set.filter (fun addr -> + AddressAttributes.get_allocation addr astate |> Option.is_some ) + in + match filter_live_addresses ~is_dead_root allocated_reachable_from_dead_root astate with + | exception NoLeak -> + Ok () + | potential_leaks -> + AbductiveDomain.check_memory_leaks (AbstractValue.Set.to_seq potential_leaks) astate + |> Result.map_error ~f:(fun (procname, allocation_trace) -> + AccessResult.ReportableError + {astate; diagnostic= MemoryLeak {procname; allocation_trace; location}} ) + + +let remove_vars vars location astate : t AccessResult.t = + let+ () = detect_leaks location ~dead_roots:vars astate in + (* remember addresses that will marked invalid later *) let astate = - List.fold vars ~init:orig_astate ~f:(fun astate var -> + List.fold vars ~init:astate ~f:(fun astate var -> match Stack.find_opt var astate with | Some (address, history) -> let astate = diff --git a/infer/src/pulse/PulseOperations.mli b/infer/src/pulse/PulseOperations.mli index 790f05652..03355dfaf 100644 --- a/infer/src/pulse/PulseOperations.mli +++ b/infer/src/pulse/PulseOperations.mli @@ -256,7 +256,7 @@ val get_dynamic_type_unreachable_values : Var.t list -> t -> (Var.t * Typ.t) lis (** Given a list of variables, computes the unreachable values if the variables were removed from the stack, then return the dynamic types of those values if they are available *) -val remove_vars : Var.t list -> Location.t -> t -> t +val remove_vars : Var.t list -> Location.t -> t -> t AccessResult.t val check_address_escape : Location.t -> Procdesc.t -> AbstractValue.t -> ValueHistory.t -> t -> t AccessResult.t diff --git a/infer/tests/codetoanalyze/c/pulse/issues.exp b/infer/tests/codetoanalyze/c/pulse/issues.exp index ef4ac5f37..1087745e4 100644 --- a/infer/tests/codetoanalyze/c/pulse/issues.exp +++ b/infer/tests/codetoanalyze/c/pulse/issues.exp @@ -8,28 +8,31 @@ codetoanalyze/c/pulse/interprocedural.c, if_freed_invalid_latent, 3, USE_AFTER_F codetoanalyze/c/pulse/interprocedural.c, latent, 3, NULLPTR_DEREFERENCE, no_bucket, ERROR, [*** LATENT ***,is the null pointer,assigned,invalid access occurs here] codetoanalyze/c/pulse/interprocedural.c, make_latent_manifest, 0, NULLPTR_DEREFERENCE, no_bucket, ERROR, [calling context starts here,in call to `propagate_latent_3_latent`,in call to `propagate_latent_2_latent`,in call to `propagate_latent_1_latent`,in call to `latent`,null pointer dereference part of the trace starts here,is the null pointer,assigned,invalid access occurs here] codetoanalyze/c/pulse/interprocedural.c, test_modified_value_then_error_bad, 4, NULLPTR_DEREFERENCE, no_bucket, ERROR, [is the null pointer,assigned,invalid access occurs here] -codetoanalyze/c/pulse/interprocedural.c, trace_correctly_through_wrappers_bad, 5, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,when calling `malloc_wrapper_2` here,when calling `malloc_wrapper_1` here,allocated by `malloc` here,memory becomes unreachable here] +codetoanalyze/c/pulse/interprocedural.c, trace_correctly_through_wrappers_bad, 4, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,when calling `malloc_wrapper_2` here,when calling `malloc_wrapper_1` here,allocated by `malloc` here,memory becomes unreachable here] codetoanalyze/c/pulse/latent.c, FN_nonlatent_use_after_free_bad, 6, USE_AFTER_FREE, no_bucket, ERROR, [*** LATENT ***,invalidation part of the trace starts here,parameter `x` of FN_nonlatent_use_after_free_bad,was invalidated by call to `free()`,use-after-lifetime part of the trace starts here,parameter `x` of FN_nonlatent_use_after_free_bad,invalid access occurs here] codetoanalyze/c/pulse/latent.c, latent_use_after_free, 4, NULLPTR_DEREFERENCE, no_bucket, ERROR, [*** LATENT ***,source of the null value part of the trace starts here,is the null pointer,null pointer dereference part of the trace starts here,parameter `x` of latent_use_after_free,invalid access occurs here] codetoanalyze/c/pulse/latent.c, latent_use_after_free, 4, USE_AFTER_FREE, no_bucket, ERROR, [*** LATENT ***,invalidation part of the trace starts here,parameter `x` of latent_use_after_free,was invalidated by call to `free()`,use-after-lifetime part of the trace starts here,parameter `x` of latent_use_after_free,invalid access occurs here] codetoanalyze/c/pulse/latent.c, main, 3, USE_AFTER_FREE, no_bucket, ERROR, [calling context starts here,in call to `latent_use_after_free`,invalidation part of the trace starts here,parameter `x` of latent_use_after_free,was invalidated by call to `free()`,use-after-lifetime part of the trace starts here,parameter `x` of latent_use_after_free,invalid access occurs here] codetoanalyze/c/pulse/latent.c, manifest_use_after_free, 0, USE_AFTER_FREE, no_bucket, ERROR, [calling context starts here,in call to `latent_use_after_free`,invalidation part of the trace starts here,parameter `x` of latent_use_after_free,was invalidated by call to `free()`,use-after-lifetime part of the trace starts here,parameter `x` of latent_use_after_free,invalid access occurs here] -codetoanalyze/c/pulse/memory_leak.c, alias_ptr_free_FP, 9, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `malloc` here,memory becomes unreachable here] +codetoanalyze/c/pulse/memory_leak.c, alias_ptr_free_FP, 10, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `malloc` here,memory becomes unreachable here] codetoanalyze/c/pulse/memory_leak.c, malloc_formal_leak_bad, 0, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `malloc` here,memory becomes unreachable here] codetoanalyze/c/pulse/memory_leak.c, malloc_interproc_no_free_bad, 0, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,when calling `create_p` here,allocated by `malloc` here,memory becomes unreachable here] -codetoanalyze/c/pulse/memory_leak.c, malloc_interproc_no_free_bad2, 5, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `malloc` here,memory becomes unreachable here] +codetoanalyze/c/pulse/memory_leak.c, malloc_interproc_no_free_bad2, 4, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `malloc` here,memory becomes unreachable here] codetoanalyze/c/pulse/memory_leak.c, malloc_no_free_bad, 0, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `malloc` here,memory becomes unreachable here] +codetoanalyze/c/pulse/memory_leak.c, malloc_out_parameter_local_mutation_bad, 3, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `malloc` here,memory becomes unreachable here] codetoanalyze/c/pulse/memory_leak.c, malloc_ptr_leak_bad, 0, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,when calling `malloc_via_ptr` here,allocated by `malloc` here,memory becomes unreachable here] +codetoanalyze/c/pulse/memory_leak.c, malloc_ptr_no_check_leak_bad, 2, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,when calling `malloc_via_ptr` here,allocated by `malloc` here,memory becomes unreachable here] codetoanalyze/c/pulse/memory_leak.c, malloc_ptr_no_check_leak_bad, 2, NULLPTR_DEREFERENCE, no_bucket, ERROR, [in call to `malloc_via_ptr`,is the null pointer,returned,return from call to `malloc_via_ptr`,assigned,invalid access occurs here] -codetoanalyze/c/pulse/memory_leak.c, malloc_ptr_no_check_leak_bad, 3, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,when calling `malloc_via_ptr` here,allocated by `malloc` here,memory becomes unreachable here] +codetoanalyze/c/pulse/memory_leak.c, report_leak_in_correct_line_bad, 2, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `malloc` here,memory becomes unreachable here] codetoanalyze/c/pulse/nullptr.c, bug_after_abduction_bad, 3, NULLPTR_DEREFERENCE, no_bucket, ERROR, [is the null pointer,assigned,invalid access occurs here] -codetoanalyze/c/pulse/nullptr.c, bug_after_malloc_result_test_bad, 4, NULLPTR_DEREFERENCE, no_bucket, ERROR, [is the null pointer,assigned,invalid access occurs here] +codetoanalyze/c/pulse/nullptr.c, bug_after_malloc_result_test_bad, 2, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `malloc` here,memory becomes unreachable here] +codetoanalyze/c/pulse/nullptr.c, bug_with_allocation_bad, 1, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `malloc` here,memory becomes unreachable here] codetoanalyze/c/pulse/nullptr.c, bug_with_allocation_bad, 3, NULLPTR_DEREFERENCE, no_bucket, ERROR, [is the null pointer,assigned,invalid access occurs here] codetoanalyze/c/pulse/nullptr.c, create_null_path2_latent, 8, NULLPTR_DEREFERENCE, no_bucket, ERROR, [*** LATENT ***,source of the null value part of the trace starts here,assigned,is the null pointer,null pointer dereference part of the trace starts here,parameter `p` of create_null_path2_latent,invalid access occurs here] codetoanalyze/c/pulse/nullptr.c, malloc_no_check_bad, 2, NULLPTR_DEREFERENCE, no_bucket, ERROR, [in call to `malloc` (modelled),is the null pointer,assigned,invalid access occurs here] codetoanalyze/c/pulse/nullptr.c, malloc_then_call_create_null_path_then_deref_unconditionally_latent, 7, NULLPTR_DEREFERENCE, no_bucket, ERROR, [*** LATENT ***,source of the null value part of the trace starts here,in call to `malloc` (modelled),is the null pointer,null pointer dereference part of the trace starts here,parameter `p` of malloc_then_call_create_null_path_then_deref_unconditionally_latent,invalid access occurs here] +codetoanalyze/c/pulse/nullptr.c, null_alias_bad, 3, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `malloc` here,memory becomes unreachable here] codetoanalyze/c/pulse/nullptr.c, null_alias_bad, 3, NULLPTR_DEREFERENCE, no_bucket, ERROR, [in call to `malloc` (modelled),is the null pointer,assigned,invalid access occurs here] -codetoanalyze/c/pulse/nullptr.c, null_alias_bad, 4, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `malloc` here,memory becomes unreachable here] codetoanalyze/c/pulse/nullptr.c, nullptr_deref_young_bad, 5, NULLPTR_DEREFERENCE, no_bucket, ERROR, [is the null pointer,assigned,invalid access occurs here] codetoanalyze/c/pulse/nullptr.c, user_malloc_leak_bad, 0, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `a_malloc` here,memory becomes unreachable here] codetoanalyze/c/pulse/traces.c, access_null_deref_bad, 2, NULLPTR_DEREFERENCE, no_bucket, ERROR, [is the null pointer,assigned,invalid access occurs here] diff --git a/infer/tests/codetoanalyze/c/pulse/issues.exp-isl b/infer/tests/codetoanalyze/c/pulse/issues.exp-isl index 1590d7843..43d635216 100644 --- a/infer/tests/codetoanalyze/c/pulse/issues.exp-isl +++ b/infer/tests/codetoanalyze/c/pulse/issues.exp-isl @@ -8,7 +8,7 @@ codetoanalyze/c/pulse/interprocedural.c, if_freed_invalid_latent, 3, USE_AFTER_F codetoanalyze/c/pulse/interprocedural.c, latent, 3, NULLPTR_DEREFERENCE, no_bucket, ERROR, [*** LATENT ***,is the null pointer,assigned,invalid access occurs here] codetoanalyze/c/pulse/interprocedural.c, make_latent_manifest, 0, NULLPTR_DEREFERENCE, no_bucket, ERROR, [calling context starts here,in call to `propagate_latent_3_latent`,in call to `propagate_latent_2_latent`,in call to `propagate_latent_1_latent`,in call to `latent`,null pointer dereference part of the trace starts here,is the null pointer,assigned,invalid access occurs here] codetoanalyze/c/pulse/interprocedural.c, test_modified_value_then_error_bad, 4, NULLPTR_DEREFERENCE, no_bucket, ERROR, [is the null pointer,assigned,invalid access occurs here] -codetoanalyze/c/pulse/interprocedural.c, trace_correctly_through_wrappers_bad, 5, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,when calling `malloc_wrapper_2` here,when calling `malloc_wrapper_1` here,allocated by `malloc` here,memory becomes unreachable here] +codetoanalyze/c/pulse/interprocedural.c, trace_correctly_through_wrappers_bad, 4, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,when calling `malloc_wrapper_2` here,when calling `malloc_wrapper_1` here,allocated by `malloc` here,memory becomes unreachable here] codetoanalyze/c/pulse/latent.c, FN_nonlatent_use_after_free_bad, 6, USE_AFTER_FREE, no_bucket, ERROR, [*** LATENT ***,invalidation part of the trace starts here,was invalidated by call to `free()`,use-after-lifetime part of the trace starts here,invalid access occurs here] codetoanalyze/c/pulse/latent.c, latent_use_after_free, 4, NULLPTR_DEREFERENCE, no_bucket, ERROR, [*** LATENT ***,source of the null value part of the trace starts here,is the null pointer,null pointer dereference part of the trace starts here,invalid access occurs here] codetoanalyze/c/pulse/latent.c, latent_use_after_free, 4, USE_AFTER_FREE, no_bucket, ERROR, [*** LATENT ***,invalidation part of the trace starts here,was invalidated by call to `free()`,use-after-lifetime part of the trace starts here,invalid access occurs here] @@ -16,19 +16,22 @@ codetoanalyze/c/pulse/latent.c, main, 3, USE_AFTER_FREE, no_bucket, ERROR, [call codetoanalyze/c/pulse/latent.c, manifest_use_after_free, 0, USE_AFTER_FREE, no_bucket, ERROR, [calling context starts here,in call to `latent_use_after_free`,invalidation part of the trace starts here,was invalidated by call to `free()`,use-after-lifetime part of the trace starts here,invalid access occurs here] codetoanalyze/c/pulse/memory_leak.c, malloc_formal_leak_bad, 0, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `malloc` here,memory becomes unreachable here] codetoanalyze/c/pulse/memory_leak.c, malloc_interproc_no_free_bad, 0, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,when calling `create_p` here,allocated by `malloc` here,memory becomes unreachable here] -codetoanalyze/c/pulse/memory_leak.c, malloc_interproc_no_free_bad2, 5, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `malloc` here,memory becomes unreachable here] +codetoanalyze/c/pulse/memory_leak.c, malloc_interproc_no_free_bad2, 4, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `malloc` here,memory becomes unreachable here] codetoanalyze/c/pulse/memory_leak.c, malloc_no_free_bad, 0, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `malloc` here,memory becomes unreachable here] +codetoanalyze/c/pulse/memory_leak.c, malloc_out_parameter_local_mutation_bad, 3, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `malloc` here,memory becomes unreachable here] codetoanalyze/c/pulse/memory_leak.c, malloc_ptr_leak_bad, 0, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,when calling `malloc_via_ptr` here,allocated by `malloc` here,memory becomes unreachable here] +codetoanalyze/c/pulse/memory_leak.c, malloc_ptr_no_check_leak_bad, 2, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,when calling `malloc_via_ptr` here,allocated by `malloc` here,memory becomes unreachable here] codetoanalyze/c/pulse/memory_leak.c, malloc_ptr_no_check_leak_bad, 2, NULLPTR_DEREFERENCE, no_bucket, ERROR, [in call to `malloc_via_ptr`,is the null pointer,returned,return from call to `malloc_via_ptr`,assigned,invalid access occurs here] -codetoanalyze/c/pulse/memory_leak.c, malloc_ptr_no_check_leak_bad, 3, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,when calling `malloc_via_ptr` here,allocated by `malloc` here,memory becomes unreachable here] +codetoanalyze/c/pulse/memory_leak.c, report_leak_in_correct_line_bad, 2, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `malloc` here,memory becomes unreachable here] codetoanalyze/c/pulse/nullptr.c, bug_after_abduction_bad, 3, NULLPTR_DEREFERENCE, no_bucket, ERROR, [is the null pointer,assigned,invalid access occurs here] -codetoanalyze/c/pulse/nullptr.c, bug_after_malloc_result_test_bad, 4, NULLPTR_DEREFERENCE, no_bucket, ERROR, [is the null pointer,assigned,invalid access occurs here] +codetoanalyze/c/pulse/nullptr.c, bug_after_malloc_result_test_bad, 2, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `malloc` here,memory becomes unreachable here] +codetoanalyze/c/pulse/nullptr.c, bug_with_allocation_bad, 1, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `malloc` here,memory becomes unreachable here] codetoanalyze/c/pulse/nullptr.c, bug_with_allocation_bad, 3, NULLPTR_DEREFERENCE, no_bucket, ERROR, [is the null pointer,assigned,invalid access occurs here] codetoanalyze/c/pulse/nullptr.c, create_null_path2_latent, 8, NULLPTR_DEREFERENCE, no_bucket, ERROR, [*** LATENT ***,source of the null value part of the trace starts here,assigned,is the null pointer,null pointer dereference part of the trace starts here,invalid access occurs here] codetoanalyze/c/pulse/nullptr.c, malloc_no_check_bad, 2, NULLPTR_DEREFERENCE, no_bucket, ERROR, [in call to `malloc` (modelled),is the null pointer,assigned,invalid access occurs here] codetoanalyze/c/pulse/nullptr.c, malloc_then_call_create_null_path_then_deref_unconditionally_latent, 7, NULLPTR_DEREFERENCE, no_bucket, ERROR, [*** LATENT ***,source of the null value part of the trace starts here,in call to `malloc` (modelled),is the null pointer,null pointer dereference part of the trace starts here,invalid access occurs here] +codetoanalyze/c/pulse/nullptr.c, null_alias_bad, 3, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `malloc` here,memory becomes unreachable here] codetoanalyze/c/pulse/nullptr.c, null_alias_bad, 3, NULLPTR_DEREFERENCE, no_bucket, ERROR, [in call to `malloc` (modelled),is the null pointer,assigned,invalid access occurs here] -codetoanalyze/c/pulse/nullptr.c, null_alias_bad, 4, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `malloc` here,memory becomes unreachable here] codetoanalyze/c/pulse/nullptr.c, nullptr_deref_young_bad, 5, NULLPTR_DEREFERENCE, no_bucket, ERROR, [is the null pointer,assigned,invalid access occurs here] codetoanalyze/c/pulse/nullptr.c, user_malloc_leak_bad, 0, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `a_malloc` here,memory becomes unreachable here] codetoanalyze/c/pulse/traces.c, access_null_deref_bad, 2, NULLPTR_DEREFERENCE, no_bucket, ERROR, [is the null pointer,assigned,invalid access occurs here] diff --git a/infer/tests/codetoanalyze/c/pulse/memory_leak.c b/infer/tests/codetoanalyze/c/pulse/memory_leak.c index bfae6b959..501ca07c5 100644 --- a/infer/tests/codetoanalyze/c/pulse/memory_leak.c +++ b/infer/tests/codetoanalyze/c/pulse/memory_leak.c @@ -13,6 +13,18 @@ int* malloc_returned_ok() { return p; } +void malloc_out_parameter_ok(int** x) { *x = (int*)malloc(sizeof(int)); } + +void malloc_out_parameter_local_mutation_ok(int** x) { + *x = (int*)malloc(sizeof(int)); + x = NULL; // not visible from the outside +} + +void malloc_out_parameter_local_mutation_bad(int** x) { + *x = (int*)malloc(sizeof(int)); + *x = NULL; +} + void malloc_then_free_ok() { int* p = malloc(sizeof(p)); if (p) { @@ -77,6 +89,15 @@ void alias_ptr_free_FP(int* out, int flag) { } else { y = out; } - if (y && y != out) + if (y && y != out) { free(y); + } +} + +void report_leak_in_correct_line_bad(int* x) { + x = (int*)malloc(sizeof(int)); + if (x != NULL) { + return; // should report leak at this line + } + free(x); } diff --git a/infer/tests/codetoanalyze/objc/pulse/issues.exp b/infer/tests/codetoanalyze/objc/pulse/issues.exp index 6e7e5baca..4f44be83e 100644 --- a/infer/tests/codetoanalyze/objc/pulse/issues.exp +++ b/infer/tests/codetoanalyze/objc/pulse/issues.exp @@ -1,12 +1,12 @@ -codetoanalyze/objc/pulse/DeallocCalls.m, memory_leak_raii_leak_bad, 2, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,when calling `BufferContainer2.init` here,allocated by `malloc_no_fail` here,memory becomes unreachable here] +codetoanalyze/objc/pulse/DeallocCalls.m, memory_leak_raii_leak_bad, 1, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,when calling `BufferContainer2.init` here,allocated by `malloc_no_fail` here,memory becomes unreachable here] codetoanalyze/objc/pulse/MallocInObjC.m, leak_bad, 0, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `malloc_no_fail` here,memory becomes unreachable here] -codetoanalyze/objc/pulse/MemoryLeaks.m, MemoryLeaks.call_no_bridge_leak_bad, 2, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,when calling `MemoryLeaks.ret_no_bridge` here,allocated by `CFLocaleCreate` here,memory becomes unreachable here] -codetoanalyze/objc/pulse/MemoryLeaks.m, MemoryLeaks.cg_path_create_mutable_leak_bad:, 3, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `CGPathCreateMutable` here,memory becomes unreachable here] -codetoanalyze/objc/pulse/MemoryLeaks.m, MemoryLeaks.cg_path_create_with_rect_leak_bad, 4, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `CGPathCreateWithRect` here,memory becomes unreachable here] -codetoanalyze/objc/pulse/MemoryLeaks.m, MemoryLeaks.no_bridge_leak_bad, 2, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `CFLocaleCreate` here,memory becomes unreachable here] -codetoanalyze/objc/pulse/MemoryLeaks.m, call_bridge_interproc_leak_ok_FP, 3, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `CFLocaleCreate` here,memory becomes unreachable here] -codetoanalyze/objc/pulse/MemoryLeaks.m, call_cfrelease_interproc_leak_ok_FP, 3, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `CFLocaleCreate` here,memory becomes unreachable here] -codetoanalyze/objc/pulse/MemoryLeaksInBlocks.m, block_captured_var_leak_bad, 7, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `malloc_no_fail` here,memory becomes unreachable here] +codetoanalyze/objc/pulse/MemoryLeaks.m, MemoryLeaks.call_no_bridge_leak_bad, 1, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,when calling `MemoryLeaks.ret_no_bridge` here,allocated by `CFLocaleCreate` here,memory becomes unreachable here] +codetoanalyze/objc/pulse/MemoryLeaks.m, MemoryLeaks.cg_path_create_mutable_leak_bad:, 2, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `CGPathCreateMutable` here,memory becomes unreachable here] +codetoanalyze/objc/pulse/MemoryLeaks.m, MemoryLeaks.cg_path_create_with_rect_leak_bad, 3, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `CGPathCreateWithRect` here,memory becomes unreachable here] +codetoanalyze/objc/pulse/MemoryLeaks.m, MemoryLeaks.no_bridge_leak_bad, 1, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `CFLocaleCreate` here,memory becomes unreachable here] +codetoanalyze/objc/pulse/MemoryLeaks.m, call_bridge_interproc_leak_ok_FP, 2, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `CFLocaleCreate` here,memory becomes unreachable here] +codetoanalyze/objc/pulse/MemoryLeaks.m, call_cfrelease_interproc_leak_ok_FP, 2, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `CFLocaleCreate` here,memory becomes unreachable here] +codetoanalyze/objc/pulse/MemoryLeaksInBlocks.m, block_captured_var_leak_bad, 6, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `malloc_no_fail` here,memory becomes unreachable here] codetoanalyze/objc/pulse/NPEBlocks.m, Singleton.dispatch_once_captured_vars_bad, 8, NULLPTR_DEREFERENCE, no_bucket, ERROR, [is the null pointer,assigned,in call to `objc_blockSingleton.dispatch_once_captured_vars_bad_2`,parameter `x` of objc_blockSingleton.dispatch_once_captured_vars_bad_2,assigned,return from call to `objc_blockSingleton.dispatch_once_captured_vars_bad_2`,invalid access occurs here] codetoanalyze/objc/pulse/NPEBlocks.m, captured_npe_bad, 5, NULLPTR_DEREFERENCE, no_bucket, ERROR, [is the null pointer,assigned,when calling `objc_blockcaptured_npe_bad_4` here,parameter `x` of objc_blockcaptured_npe_bad_4,invalid access occurs here] codetoanalyze/objc/pulse/NPENilBlocks.m, BlockA.assignNilBad, 5, NIL_BLOCK_CALL, no_bucket, ERROR, [is the null pointer,assigned,invalid access occurs here] diff --git a/infer/tests/codetoanalyze/objcpp/pulse/issues.exp b/infer/tests/codetoanalyze/objcpp/pulse/issues.exp index 3f88bfa03..911718a69 100644 --- a/infer/tests/codetoanalyze/objcpp/pulse/issues.exp +++ b/infer/tests/codetoanalyze/objcpp/pulse/issues.exp @@ -1,4 +1,4 @@ -codetoanalyze/objcpp/pulse/AllocPatternMemLeak.mm, A.create_no_release_leak_bad, 2, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `ABFDataCreate` here,memory becomes unreachable here] +codetoanalyze/objcpp/pulse/AllocPatternMemLeak.mm, A.create_no_release_leak_bad, 1, MEMORY_LEAK, no_bucket, ERROR, [allocation part of the trace starts here,allocated by `ABFDataCreate` here,memory becomes unreachable here] codetoanalyze/objcpp/pulse/NPEBasic.mm, addNilInArrayBad, 0, NIL_INSERTION_INTO_COLLECTION, no_bucket, ERROR, [is the null pointer,in call to `NSMutableArray.addObject:` (modelled),invalid access occurs here] codetoanalyze/objcpp/pulse/NPEBasic.mm, addNilInDictBad, 2, NIL_INSERTION_INTO_COLLECTION, no_bucket, ERROR, [is the null pointer,assigned,in call to `NSMutableDictionary.setObject:forKey:` (modelled),invalid access occurs here] codetoanalyze/objcpp/pulse/NPEBasic.mm, addNilKeyInDictBracketsBad, 2, NIL_INSERTION_INTO_COLLECTION, no_bucket, ERROR, [is the null pointer,assigned,in call to `mutableDictionary[someKey] = value` (modelled),invalid access occurs here]