Merge branch 'jk/leak-checkers' into next
authorJunio C Hamano <gitster@pobox.com>
Thu, 14 Sep 2017 08:40:38 +0000 (17:40 +0900)
committerJunio C Hamano <gitster@pobox.com>
Thu, 14 Sep 2017 08:40:38 +0000 (17:40 +0900)
Many of our programs consider that it is OK to release dynamic
storage that is used throughout the life of the program by simply
exiting, but this makes it harder to leak detection tools to avoid
reporting false positives. Plug many existing leaks and introduce
a mechanism for developers to mark that the region of memory
pointed by a pointer is not lost/leaking to help these tools.

* jk/leak-checkers:
add UNLEAK annotation for reducing leak false positives
set_git_dir: handle feeding gitdir to itself
repository: free fields before overwriting them
reset: free allocated tree buffers
reset: make tree counting less confusing
config: plug user_config leak
update-index: fix cache entry leak in add_one_file()
add: free leaked pathspec after add_files_to_cache()
test-lib: set LSAN_OPTIONS to abort by default
test-lib: --valgrind should not override --verbose-log

15 files changed:
Makefile
builtin/add.c
builtin/commit.c
builtin/config.c
builtin/init-db.c
builtin/ls-files.c
builtin/reset.c
builtin/update-index.c
builtin/worktree.c
environment.c
git-compat-util.h
repository.c
setup.c
t/test-lib.sh
usage.c
index 68948dfbf3f29531d41be591daa9a8ba8df72708..ed5960e6b378a982fb770f428324ba8fec0ba8c6 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1041,6 +1041,9 @@ BASIC_CFLAGS += -fno-omit-frame-pointer
 ifneq ($(filter undefined,$(SANITIZERS)),)
 BASIC_CFLAGS += -DNO_UNALIGNED_LOADS
 endif
+ifneq ($(filter leak,$(SANITIZERS)),)
+BASIC_CFLAGS += -DSUPPRESS_ANNOTATED_LEAKS
+endif
 endif
 
 ifndef sysconfdir
index c20548e4f545116c6e3bff75e59e953be603cc2a..a648cf4c56c9f56eb60a97ca50ccf0b191dc50ff 100644 (file)
@@ -119,6 +119,7 @@ int add_files_to_cache(const char *prefix,
        rev.diffopt.flags |= DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG;
        rev.max_count = 0; /* do not compare unmerged paths with stage #2 */
        run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
+       clear_pathspec(&rev.prune_data);
        return !!data.add_errors;
 }
 
@@ -514,5 +515,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
                        die(_("Unable to write new index file"));
        }
 
+       UNLEAK(pathspec);
+       UNLEAK(dir);
        return exit_status;
 }
index b3b04f5dd3a94d1661e877c5019cc56ac46854ef..58f9747c2f2d99d2ab4d78f62f5e9c0928a12f1f 100644 (file)
@@ -1818,6 +1818,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
        if (!quiet)
                print_summary(prefix, &oid, !current_head);
 
-       strbuf_release(&err);
+       UNLEAK(err);
+       UNLEAK(sb);
        return 0;
 }
index 70ff231e9c5bce550e7af20ddfe91b8b68ee32ae..d13daeeb55927758ceec816f39412123d2ce5846 100644 (file)
@@ -518,10 +518,13 @@ int cmd_config(int argc, const char **argv, const char *prefix)
                        die("$HOME not set");
 
                if (access_or_warn(user_config, R_OK, 0) &&
-                   xdg_config && !access_or_warn(xdg_config, R_OK, 0))
+                   xdg_config && !access_or_warn(xdg_config, R_OK, 0)) {
                        given_config_source.file = xdg_config;
-               else
+                       free(user_config);
+               } else {
                        given_config_source.file = user_config;
+                       free(xdg_config);
+               }
        }
        else if (use_system_config)
                given_config_source.file = git_etc_gitconfig();
@@ -628,6 +631,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
                check_write();
                check_argc(argc, 2, 2);
                value = normalize_value(argv[0], argv[1]);
+               UNLEAK(value);
                ret = git_config_set_in_file_gently(given_config_source.file, argv[0], value);
                if (ret == CONFIG_NOTHING_SET)
                        error(_("cannot overwrite multiple values with a single value\n"
@@ -638,6 +642,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
                check_write();
                check_argc(argc, 2, 3);
                value = normalize_value(argv[0], argv[1]);
+               UNLEAK(value);
                return git_config_set_multivar_in_file_gently(given_config_source.file,
                                                              argv[0], value, argv[2], 0);
        }
@@ -645,6 +650,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
                check_write();
                check_argc(argc, 2, 2);
                value = normalize_value(argv[0], argv[1]);
+               UNLEAK(value);
                return git_config_set_multivar_in_file_gently(given_config_source.file,
                                                              argv[0], value,
                                                              CONFIG_REGEX_NONE, 0);
@@ -653,6 +659,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
                check_write();
                check_argc(argc, 2, 3);
                value = normalize_value(argv[0], argv[1]);
+               UNLEAK(value);
                return git_config_set_multivar_in_file_gently(given_config_source.file,
                                                              argv[0], value, argv[2], 1);
        }
index 47823f9aa4452edfa684b042dd5fbaa935df1d39..c9b7946bade9e10f799942137480e71ee3233701 100644 (file)
@@ -579,6 +579,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
                        set_git_work_tree(work_tree);
        }
 
+       UNLEAK(real_git_dir);
+
        flags |= INIT_DB_EXIST_OK;
        return init_db(git_dir, real_git_dir, template_dir, flags);
 }
index e1339e6d17d2bf592b800d3e5a686cbc91c12331..8c713c47acccf0a3a50ec8a0b822aafae8d6bada 100644 (file)
@@ -673,5 +673,6 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
                return bad ? 1 : 0;
        }
 
+       UNLEAK(dir);
        return 0;
 }
index f1af9345e4c72fc6af411037ede717399ea3a4e1..9cd89b23059f768c4651725f2c26c5b08c35f650 100644 (file)
@@ -44,10 +44,11 @@ static inline int is_merge(void)
 
 static int reset_index(const struct object_id *oid, int reset_type, int quiet)
 {
-       int nr = 1;
+       int i, nr = 0;
        struct tree_desc desc[2];
        struct tree *tree;
        struct unpack_trees_options opts;
+       int ret = -1;
 
        memset(&opts, 0, sizeof(opts));
        opts.head_idx = 1;
@@ -75,23 +76,32 @@ static int reset_index(const struct object_id *oid, int reset_type, int quiet)
                struct object_id head_oid;
                if (get_oid("HEAD", &head_oid))
                        return error(_("You do not have a valid HEAD."));
-               if (!fill_tree_descriptor(desc, &head_oid))
+               if (!fill_tree_descriptor(desc + nr, &head_oid))
                        return error(_("Failed to find tree of HEAD."));
                nr++;
                opts.fn = twoway_merge;
        }
 
-       if (!fill_tree_descriptor(desc + nr - 1, oid))
-               return error(_("Failed to find tree of %s."), oid_to_hex(oid));
+       if (!fill_tree_descriptor(desc + nr, oid)) {
+               error(_("Failed to find tree of %s."), oid_to_hex(oid));
+               goto out;
+       }
+       nr++;
+
        if (unpack_trees(nr, desc, &opts))
-               return -1;
+               goto out;
 
        if (reset_type == MIXED || reset_type == HARD) {
                tree = parse_tree_indirect(oid);
                prime_cache_tree(&the_index, tree);
        }
 
-       return 0;
+       ret = 0;
+
+out:
+       for (i = 0; i < nr; i++)
+               free((void *)desc[i].buffer);
+       return ret;
 }
 
 static void print_new_head_line(struct commit *commit)
index 655e6d60d3f7e0525d5b88c66803e7b993168443..bf7420b808fd1e0314d981bf65073df62f7578a4 100644 (file)
@@ -287,8 +287,10 @@ static int add_one_path(const struct cache_entry *old, const char *path, int len
        }
        option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
        option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
-       if (add_cache_entry(ce, option))
+       if (add_cache_entry(ce, option)) {
+               free(ce);
                return error("%s: cannot add to the index - missing --add option?", path);
+       }
        return 0;
 }
 
index c98e2ce5f57c1f41ccaf90041a1a22233ae75551..de26849f5551a12aac16e4121c7024ebb40502d2 100644 (file)
@@ -381,6 +381,8 @@ static int add(int ac, const char **av, const char *prefix)
                branch = opts.new_branch;
        }
 
+       UNLEAK(path);
+       UNLEAK(opts);
        return add_worktree(path, branch, &opts);
 }
 
index 3fd4b1084590d0118cad5e7ae32fb37e570e7523..f1f934b6fddd101191a17b1bc1883da287793319 100644 (file)
@@ -97,7 +97,7 @@ int ignore_untracked_cache_config;
 /* This is set by setup_git_dir_gently() and/or git_default_config() */
 char *git_work_tree_cfg;
 
-static const char *namespace;
+static char *namespace;
 
 static const char *super_prefix;
 
@@ -152,8 +152,10 @@ void setup_git_env(void)
        if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
                check_replace_refs = 0;
        replace_ref_base = getenv(GIT_REPLACE_REF_BASE_ENVIRONMENT);
+       free(git_replace_ref_base);
        git_replace_ref_base = xstrdup(replace_ref_base ? replace_ref_base
                                                          : "refs/replace/");
+       free(namespace);
        namespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));
        shallow_file = getenv(GIT_SHALLOW_FILE_ENVIRONMENT);
        if (shallow_file)
index 6678b488cc73851d468094deb5352d311ab310d0..003e444c46edce65f04413d732c22d71d708fd4f 100644 (file)
@@ -1169,4 +1169,24 @@ static inline int is_missing_file_error(int errno_)
 
 extern int cmd_main(int, const char **);
 
+/*
+ * You can mark a stack variable with UNLEAK(var) to avoid it being
+ * reported as a leak by tools like LSAN or valgrind. The argument
+ * should generally be the variable itself (not its address and not what
+ * it points to). It's safe to use this on pointers which may already
+ * have been freed, or on pointers which may still be in use.
+ *
+ * Use this _only_ for a variable that leaks by going out of scope at
+ * program exit (so only from cmd_* functions or their direct helpers).
+ * Normal functions, especially those which may be called multiple
+ * times, should actually free their memory. This is only meant as
+ * an annotation, and does nothing in non-leak-checking builds.
+ */
+#ifdef SUPPRESS_ANNOTATED_LEAKS
+extern void unleak_memory(const void *ptr, size_t len);
+#define UNLEAK(var) unleak_memory(&(var), sizeof(var));
+#else
+#define UNLEAK(var)
+#endif
+
 #endif
index f107af7d763e848648ace5da0010be4df2e0d4dc..97c732bd48ca8776203ae0936fe8ee61bb330ad9 100644 (file)
@@ -40,11 +40,15 @@ static void repo_setup_env(struct repository *repo)
 
        repo->different_commondir = find_common_dir(&sb, repo->gitdir,
                                                    !repo->ignore_env);
+       free(repo->commondir);
        repo->commondir = strbuf_detach(&sb, NULL);
+       free(repo->objectdir);
        repo->objectdir = git_path_from_env(DB_ENVIRONMENT, repo->commondir,
                                            "objects", !repo->ignore_env);
+       free(repo->graft_file);
        repo->graft_file = git_path_from_env(GRAFT_ENVIRONMENT, repo->commondir,
                                             "info/grafts", !repo->ignore_env);
+       free(repo->index_file);
        repo->index_file = git_path_from_env(INDEX_ENVIRONMENT, repo->gitdir,
                                             "index", !repo->ignore_env);
 }
@@ -52,16 +56,12 @@ static void repo_setup_env(struct repository *repo)
 void repo_set_gitdir(struct repository *repo, const char *path)
 {
        const char *gitfile = read_gitfile(path);
+       char *old_gitdir = repo->gitdir;
 
-       /*
-        * NEEDSWORK: Eventually we want to be able to free gitdir and the rest
-        * of the environment before reinitializing it again, but we have some
-        * crazy code paths where we try to set gitdir with the current gitdir
-        * and we don't want to free gitdir before copying the passed in value.
-        */
        repo->gitdir = xstrdup(gitfile ? gitfile : path);
-
        repo_setup_env(repo);
+
+       free(old_gitdir);
 }
 
 /*
diff --git a/setup.c b/setup.c
index 23950173fc01268320d2e23c36ef80a1b1231a5e..6d8380acd2b66ee7d8206639d4b03933afb1816e 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -399,11 +399,6 @@ void setup_work_tree(void)
        if (getenv(GIT_WORK_TREE_ENVIRONMENT))
                setenv(GIT_WORK_TREE_ENVIRONMENT, ".", 1);
 
-       /*
-        * NEEDSWORK: this call can essentially be set_git_dir(get_git_dir())
-        * which can cause some problems when trying to free the old value of
-        * gitdir.
-        */
        set_git_dir(remove_leading_path(git_dir, work_tree));
        initialized = 1;
 }
index 5fbd8d4a90b3b88cf57ca53c6b1fe99d5a957460..a738540ef2582654faee4cf24769800d24eac3ac 100644 (file)
@@ -44,6 +44,11 @@ GIT_BUILD_DIR="$TEST_DIRECTORY"/..
 : ${ASAN_OPTIONS=detect_leaks=0:abort_on_error=1}
 export ASAN_OPTIONS
 
+# If LSAN is in effect we _do_ want leak checking, but we still
+# want to abort so that we notice the problems.
+: ${LSAN_OPTIONS=abort_on_error=1}
+export LSAN_OPTIONS
+
 ################################################################
 # It appears that people try to run tests without building...
 "$GIT_BUILD_DIR/git" >/dev/null
@@ -274,7 +279,7 @@ then
        test -z "$verbose" && verbose_only="$valgrind_only"
 elif test -n "$valgrind"
 then
-       verbose=t
+       test -z "$verbose_log" && verbose=t
 fi
 
 if test -n "$color"
diff --git a/usage.c b/usage.c
index 1ea7df9a202339972ee59f35a5ba8852502c915f..cdd534c9dfc4bd38ce112da62643ab2dc7b4fbe9 100644 (file)
--- a/usage.c
+++ b/usage.c
@@ -241,3 +241,18 @@ NORETURN void BUG(const char *fmt, ...)
        va_end(ap);
 }
 #endif
+
+#ifdef SUPPRESS_ANNOTATED_LEAKS
+void unleak_memory(const void *ptr, size_t len)
+{
+       static struct suppressed_leak_root {
+               struct suppressed_leak_root *next;
+               char data[FLEX_ARRAY];
+       } *suppressed_leaks;
+       struct suppressed_leak_root *root;
+
+       FLEX_ALLOC_MEM(root, data, ptr, len);
+       root->next = suppressed_leaks;
+       suppressed_leaks = root;
+}
+#endif