From 6278b779df5611579ade3cb262600b6deae63b0f Mon Sep 17 00:00:00 2001 From: Dulma Rodriguez Date: Wed, 14 Oct 2015 07:32:33 -0700 Subject: [PATCH] Adding memory leak bucket for cpp Reviewed By: @cristianoc Differential Revision: D2540503 fb-gh-sync-id: 4324009 --- infer/bin/inferlib.py | 16 ++--- infer/src/backend/abs.ml | 13 ++-- infer/src/backend/inferanalyze.ml | 10 +-- infer/src/backend/mleak_buckets.ml | 41 ++++++++---- infer/src/backend/mleak_buckets.mli | 8 ++- .../cpp/errors/memory_leaks/object_leak.cpp | 20 ++++++ .../endtoend/cpp/MemoryLeakCppBucketTest.java | 62 +++++++++++++++++++ infer/tests/endtoend/cpp/MemoryLeakTest.java | 62 +++++++++++++++++++ infer/tests/utils/InferRunner.java | 16 ++++- 9 files changed, 214 insertions(+), 34 deletions(-) create mode 100644 infer/tests/codetoanalyze/cpp/errors/memory_leaks/object_leak.cpp create mode 100644 infer/tests/endtoend/cpp/MemoryLeakCppBucketTest.java create mode 100644 infer/tests/endtoend/cpp/MemoryLeakTest.java diff --git a/infer/bin/inferlib.py b/infer/bin/inferlib.py index 5edd7ff87..2139f0d82 100644 --- a/infer/bin/inferlib.py +++ b/infer/bin/inferlib.py @@ -149,12 +149,12 @@ infer_group.add_argument('--absolute-paths', default=False, help='Report errors with absolute paths') -infer_group.add_argument('--objc_ml_buckets', - dest='objc_ml_buckets', - help='memory leak buckets to be checked, ' - 'separated by commas. The possible ' - 'buckets are cf (Core Foundation), ' - 'arc, narc (No arc)') +infer_group.add_argument('--ml_buckets', + dest='ml_buckets', + help='memory leak buckets to be checked, ' + 'separated by commas. The possible ' + 'buckets are cf (Core Foundation), ' + 'arc, narc (No arc), cpp') infer_group.add_argument('-nt', '--notest', action='store_true', dest='notest', @@ -503,8 +503,8 @@ class Infer: if self.args.infer_cache: infer_options += ['-infer_cache', self.args.infer_cache] - if self.args.objc_ml_buckets: - infer_options += ['-objc_ml_buckets', self.args.objc_ml_buckets] + if self.args.ml_buckets: + infer_options += ['-ml_buckets', self.args.ml_buckets] if self.args.notest: infer_options += ['-notest'] diff --git a/infer/src/backend/abs.ml b/infer/src/backend/abs.ml index 6d7fb2896..744a53bd7 100644 --- a/infer/src/backend/abs.ml +++ b/infer/src/backend/abs.ml @@ -1129,7 +1129,7 @@ let should_raise_objc_leak prop hpred = match hpred with | Sil.Hpointsto(e, Sil.Estruct((fn, Sil.Eexp( (Sil.Const (Sil.Cint i)), _)):: _, _), Sil.Sizeof (typ, _)) when Ident.fieldname_is_hidden fn && Sil.Int.gt i Sil.Int.zero (* counter > 0 *) -> - Mleak_buckets.should_raise_leak typ + Mleak_buckets.should_raise_objc_leak typ | _ -> None let print_retain_cycle _prop = @@ -1293,9 +1293,11 @@ let check_junk ?original_prop pname tenv prop = let resource = match Errdesc.hpred_is_open_resource prop hpred with | Some res -> res | None -> Sil.Rmemory Sil.Mmalloc in - let objc_ml_bucket_opt = + let ml_bucket_opt = match resource with | Sil.Rmemory Sil.Mobjc -> should_raise_objc_leak prop hpred + | Sil.Rmemory Sil.Mnew when !Config.curr_language = Config.C_CPP -> + Mleak_buckets.should_raise_cpp_leak () | _ -> None in let exn_retain_cycle cycle = print_retain_cycle original_prop; @@ -1306,7 +1308,7 @@ let check_junk ?original_prop pname tenv prop = try assert false with Assert_failure x -> x) in let exn_leak = Exceptions.Leak (fp_part, prop, hpred, - Errdesc.explain_leak tenv hpred prop alloc_attribute objc_ml_bucket_opt, + Errdesc.explain_leak tenv hpred prop alloc_attribute ml_bucket_opt, !Absarray.array_abstraction_performed, resource, try assert false with Assert_failure x -> x) in @@ -1320,8 +1322,9 @@ let check_junk ?original_prop pname tenv prop = if cycle_has_weak_or_unretained_or_assign_field cycle then true, exn_retain_cycle cycle else false, exn_retain_cycle cycle - | Some _, Sil.Rmemory Sil.Mobjc -> - objc_ml_bucket_opt = None, exn_leak + | Some _, Sil.Rmemory Sil.Mobjc + | Some _, Sil.Rmemory Sil.Mnew when !Config.curr_language = Config.C_CPP -> + ml_bucket_opt = None, exn_leak | Some _, Sil.Rmemory _ -> !Config.curr_language = Config.Java, exn_leak | Some _, Sil.Rignore -> true, exn_leak | Some _, Sil.Rfile -> false, exn_leak diff --git a/infer/src/backend/inferanalyze.ml b/infer/src/backend/inferanalyze.ml index ca7fb3b78..c21a988cc 100644 --- a/infer/src/backend/inferanalyze.ml +++ b/infer/src/backend/inferanalyze.ml @@ -87,8 +87,8 @@ let excluded_files : string list ref = ref [] (** Absolute path to the project source, used for relative paths in the exclude list *) let source_path = ref "" -(** List of obj memory leak buckets to be checked in objc *) -let objc_ml_buckets_arg = ref "cf" +(** List of obj memory leak buckets to be checked in Objective-C/C++ *) +let ml_buckets_arg = ref "cf" (** Whether specs can be cleaned up before starting analysis *) let allow_specs_cleanup = ref false @@ -148,8 +148,8 @@ let arg_desc = "-version", Arg.Unit print_version, None, "print version information and exit"; "-version_json", Arg.Unit print_version_json, None, "print version json formatted"; "-objcm", Arg.Set Config.objc_memory_model_on, None, "Use ObjC memory model"; - "-objc_ml_buckets", Arg.Set_string objc_ml_buckets_arg, Some "objc_ml_buckets", - "memory leak buckets to be checked, separated by commas. The possible buckets are cf (Core Foundation), arc, narc (No arc)"; + "-ml_buckets", Arg.Set_string ml_buckets_arg, Some "ml_buckets", + "memory leak buckets to be checked, separated by commas. The possible buckets are cf (Core Foundation), arc, narc (No arc), cpp"; ] in Arg2.create_options_desc false "Analysis Options" desc in let reserved_arg = @@ -891,7 +891,7 @@ let () = else !err_file_cmdline in let analyzer_out_of = open_output_file Logging.set_out_formatter analyzer_out_file in let analyzer_err_of = open_output_file Logging.set_err_formatter analyzer_err_file in - if (!Config.curr_language = Config.C_CPP) then Mleak_buckets.init_buckets !objc_ml_buckets_arg; + if (!Config.curr_language = Config.C_CPP) then Mleak_buckets.init_buckets !ml_buckets_arg; process_cluster_cmdline_exit (); diff --git a/infer/src/backend/mleak_buckets.ml b/infer/src/backend/mleak_buckets.ml index 4eac2ec0d..7915c277d 100644 --- a/infer/src/backend/mleak_buckets.ml +++ b/infer/src/backend/mleak_buckets.ml @@ -7,7 +7,8 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. *) -(** This module handles buckets of memory leaks in Objective-C *) + +(** This module handles buckets of memory leaks in Objective-C/C++ *) open Utils @@ -19,14 +20,16 @@ type mleak_bucket = | MLeak_cf | MLeak_arc | MLeak_no_arc + | MLeak_cpp -let objc_ml_buckets = ref [] +let ml_buckets = ref [] let bucket_from_string bucket_s = match bucket_s with | "cf" -> MLeak_cf | "arc" -> MLeak_arc | "narc" -> MLeak_no_arc + | "cpp" -> MLeak_cpp | _ -> assert false let bucket_to_string bucket = @@ -34,12 +37,14 @@ let bucket_to_string bucket = | MLeak_cf -> "Core Foundation" | MLeak_arc -> "Arc" | MLeak_no_arc -> "No arc" + | MLeak_cpp -> "Cpp" let bucket_to_message bucket = match bucket with | MLeak_cf -> "[CF]" | MLeak_arc -> "[ARC]" | MLeak_no_arc -> "[NO ARC]" + | MLeak_cpp -> "[CPP]" let mleak_bucket_compare b1 b2 = match b1, b2 with @@ -50,18 +55,21 @@ let mleak_bucket_compare b1 b2 = | MLeak_arc, _ -> -1 | _, MLeak_arc -> 1 | MLeak_no_arc, MLeak_no_arc -> 0 + | MLeak_no_arc, _ -> -1 + | _, MLeak_no_arc -> 1 + | MLeak_cpp, MLeak_cpp -> 0 let mleak_bucket_eq b1 b2 = mleak_bucket_compare b1 b2 = 0 -let init_buckets objc_ml_buckets_arg = +let init_buckets ml_buckets_arg = let buckets = - Str.split (Str.regexp bucket_delimiter) objc_ml_buckets_arg in + Str.split (Str.regexp bucket_delimiter) ml_buckets_arg in let buckets = match buckets with | ["all"] -> [] | _ -> list_map bucket_from_string buckets in - objc_ml_buckets := buckets + ml_buckets := buckets let contains_cf ml_buckets = list_mem mleak_bucket_eq MLeak_cf ml_buckets @@ -72,28 +80,37 @@ let contains_arc ml_buckets = let contains_narc ml_buckets = list_mem mleak_bucket_eq MLeak_no_arc ml_buckets +let contains_cpp ml_buckets = + list_mem mleak_bucket_eq MLeak_cpp ml_buckets + let should_raise_leak_cf typ = - if contains_cf !objc_ml_buckets then + if contains_cf !ml_buckets then Objc_models.is_core_lib_type typ else false let should_raise_leak_arc () = - if contains_arc !objc_ml_buckets then + if contains_arc !ml_buckets then !Config.arc_mode else false let should_raise_leak_no_arc () = - if contains_narc !objc_ml_buckets then + if contains_narc !ml_buckets then not (!Config.arc_mode) else false -(* Returns whether a memory leak should be raised. If objc_ml_buckets is not there, *) -(* then raise all memory leaks. *) +(* Returns whether a memory leak should be raised for a C++ object.*) +(* If ml_buckets contains cpp, then check leaks from C++ objects. *) +let should_raise_cpp_leak () = + if contains_cpp !ml_buckets then + Some (bucket_to_message MLeak_cpp) + else None + +(* Returns whether a memory leak should be raised. *) (* If cf is passed, then check leaks from Core Foundation. *) (* If arc is passed, check leaks from code that compiles with arc*) (* If no arc is passed check the leaks from code that compiles without arc *) -let should_raise_leak typ = - if list_length !objc_ml_buckets = 0 then Some "" +let should_raise_objc_leak typ = + if list_length !ml_buckets = 0 then Some "" else if should_raise_leak_cf typ then Some (bucket_to_message MLeak_cf) else if should_raise_leak_arc () then Some (bucket_to_message MLeak_arc) diff --git a/infer/src/backend/mleak_buckets.mli b/infer/src/backend/mleak_buckets.mli index 6f3095528..360b15cce 100644 --- a/infer/src/backend/mleak_buckets.mli +++ b/infer/src/backend/mleak_buckets.mli @@ -13,10 +13,12 @@ val objc_arc_flag : string val init_buckets : string -> unit -(* Returns whether a memory leak should be raised. If objc_ml_buckets is not there, *) -(* then raise all memory leaks. *) +(* Returns whether a memory leak should be raised. *) (* If cf is passed, then check leaks from Core Foundation. *) (* If arc is passed, check leaks from code that compiles with arc*) (* If no arc is passed check the leaks from code that compiles without arc *) -val should_raise_leak : Sil.typ -> string option +val should_raise_objc_leak : Sil.typ -> string option +(* Returns whether a memory leak should be raised for a C++ object.*) +(* If ml_buckets contains cpp, then check leaks from C++ objects. *) +val should_raise_cpp_leak : unit -> string option diff --git a/infer/tests/codetoanalyze/cpp/errors/memory_leaks/object_leak.cpp b/infer/tests/codetoanalyze/cpp/errors/memory_leaks/object_leak.cpp new file mode 100644 index 000000000..3d806e915 --- /dev/null +++ b/infer/tests/codetoanalyze/cpp/errors/memory_leaks/object_leak.cpp @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2015 - present Facebook, Inc. +* All rights reserved. +* +* This source code is licensed under the BSD style license found in the +* LICENSE file in the root directory of this source tree. An additional grant +* of patent rights can be found in the PATENTS file in the same directory. +*/ + +class Rectangle { + int width, height; +public: + Rectangle(int x, int y) : width(x), height(y) {} + int area(void) { return width * height; } +}; + +int main() { + Rectangle *bar = new Rectangle (5, 6); + return 0; +} diff --git a/infer/tests/endtoend/cpp/MemoryLeakCppBucketTest.java b/infer/tests/endtoend/cpp/MemoryLeakCppBucketTest.java new file mode 100644 index 000000000..4cf918203 --- /dev/null +++ b/infer/tests/endtoend/cpp/MemoryLeakCppBucketTest.java @@ -0,0 +1,62 @@ +/* +* Copyright (c) 2015 - present Facebook, Inc. +* All rights reserved. +* +* This source code is licensed under the BSD style license found in the +* LICENSE file in the root directory of this source tree. An additional grant +* of patent rights can be found in the PATENTS file in the same directory. +*/ + +package endtoend.cpp; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class MemoryLeakCppBucketTest { + + public static final String FILE = + "infer/tests/codetoanalyze/cpp/errors/memory_leaks/object_leak.cpp"; + + private static ImmutableList inferCmd; + + public static final String MEMORY_LEAK = "MEMORY_LEAK"; + + @ClassRule + public static DebuggableTemporaryFolder folder = + new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createCPPInferCommandWithMLBuckets(folder, FILE, "cpp"); + } + + @Test + public void whenInferRunsOnObject_leakThenMLIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferCPP(inferCmd); + String[] methods = { "main" }; + assertThat( + "Results should contain " + MEMORY_LEAK, + inferResults, + containsExactly( + MEMORY_LEAK, + FILE, + methods + ) + ); + } + +} diff --git a/infer/tests/endtoend/cpp/MemoryLeakTest.java b/infer/tests/endtoend/cpp/MemoryLeakTest.java new file mode 100644 index 000000000..105389b50 --- /dev/null +++ b/infer/tests/endtoend/cpp/MemoryLeakTest.java @@ -0,0 +1,62 @@ +/* +* Copyright (c) 2015 - present Facebook, Inc. +* All rights reserved. +* +* This source code is licensed under the BSD style license found in the +* LICENSE file in the root directory of this source tree. An additional grant +* of patent rights can be found in the PATENTS file in the same directory. +*/ + +package endtoend.cpp; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class MemoryLeakTest { + + public static final String FILE = + "infer/tests/codetoanalyze/cpp/errors/memory_leaks/object_leak.cpp"; + + private static ImmutableList inferCmd; + + public static final String MEMORY_LEAK = "MEMORY_LEAK"; + + @ClassRule + public static DebuggableTemporaryFolder folder = + new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createCPPInferCommand(folder, FILE); + } + + @Test + public void whenInferRunsOnObject_leakThenMLIsNotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferCPP(inferCmd); + String[] methods = { }; // no memory leak + assertThat( + "Results should contain " + MEMORY_LEAK, + inferResults, + containsExactly( + MEMORY_LEAK, + FILE, + methods + ) + ); + } + +} diff --git a/infer/tests/utils/InferRunner.java b/infer/tests/utils/InferRunner.java index d7851bbb9..b9122939b 100644 --- a/infer/tests/utils/InferRunner.java +++ b/infer/tests/utils/InferRunner.java @@ -253,7 +253,7 @@ public class InferRunner { ImmutableList.Builder ml_bucketsOption = new ImmutableList.Builder<>(); ml_bucketsOption - .add("--objc_ml_buckets") + .add("--ml_buckets") .add(ml_buckets == null ? "all" : ml_buckets); ImmutableList inferCmd = new ImmutableList.Builder() .add("infer") @@ -360,6 +360,20 @@ public class InferRunner { false); } + public static ImmutableList createCPPInferCommandWithMLBuckets( + TemporaryFolder folder, + String sourceFile, + String ml_bucket) throws IOException, InterruptedException { + return createClangInferCommand( + folder, + sourceFile, + Language.CPP, + true, + null, + ml_bucket, + false); + } + public static ImmutableList createObjCInferCommand( TemporaryFolder folder, String sourceFile) throws IOException, InterruptedException {