diff --git a/Makefile b/Makefile index f2bb7f2f63..c052f09bba 100644 --- a/Makefile +++ b/Makefile @@ -1036,6 +1036,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 diff --git a/builtin/add.c b/builtin/add.c index ef625e3fb8..a648cf4c56 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -515,5 +515,7 @@ finish: die(_("Unable to write new index file")); } + UNLEAK(pathspec); + UNLEAK(dir); return exit_status; } diff --git a/builtin/commit.c b/builtin/commit.c index b3b04f5dd3..58f9747c2f 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -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; } diff --git a/builtin/config.c b/builtin/config.c index 52a4606243..d13daeeb55 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -631,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" @@ -641,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); } @@ -648,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); @@ -656,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); } diff --git a/builtin/init-db.c b/builtin/init-db.c index 47823f9aa4..c9b7946bad 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -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); } diff --git a/builtin/ls-files.c b/builtin/ls-files.c index e1339e6d17..8c713c47ac 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -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; } diff --git a/builtin/worktree.c b/builtin/worktree.c index c98e2ce5f5..de26849f55 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -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); } diff --git a/git-compat-util.h b/git-compat-util.h index 6678b488cc..003e444c46 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -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 diff --git a/usage.c b/usage.c index 1ea7df9a20..cdd534c9df 100644 --- 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