Skip to content

Commit 09595ab

Browse files
committed
Merge branch 'jk/leak-checkers'
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
2 parents df80c57 + 0e5bba5 commit 09595ab

File tree

15 files changed

+92
-24
lines changed

15 files changed

+92
-24
lines changed

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,6 +1041,9 @@ BASIC_CFLAGS += -fno-omit-frame-pointer
10411041
ifneq ($(filter undefined,$(SANITIZERS)),)
10421042
BASIC_CFLAGS += -DNO_UNALIGNED_LOADS
10431043
endif
1044+
ifneq ($(filter leak,$(SANITIZERS)),)
1045+
BASIC_CFLAGS += -DSUPPRESS_ANNOTATED_LEAKS
1046+
endif
10441047
endif
10451048

10461049
ifndef sysconfdir

builtin/add.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ int add_files_to_cache(const char *prefix,
119119
rev.diffopt.flags |= DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG;
120120
rev.max_count = 0; /* do not compare unmerged paths with stage #2 */
121121
run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
122+
clear_pathspec(&rev.prune_data);
122123
return !!data.add_errors;
123124
}
124125

@@ -514,5 +515,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
514515
die(_("Unable to write new index file"));
515516
}
516517

518+
UNLEAK(pathspec);
519+
UNLEAK(dir);
517520
return exit_status;
518521
}

builtin/commit.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1818,6 +1818,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
18181818
if (!quiet)
18191819
print_summary(prefix, &oid, !current_head);
18201820

1821-
strbuf_release(&err);
1821+
UNLEAK(err);
1822+
UNLEAK(sb);
18221823
return 0;
18231824
}

builtin/config.c

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -518,10 +518,13 @@ int cmd_config(int argc, const char **argv, const char *prefix)
518518
die("$HOME not set");
519519

520520
if (access_or_warn(user_config, R_OK, 0) &&
521-
xdg_config && !access_or_warn(xdg_config, R_OK, 0))
521+
xdg_config && !access_or_warn(xdg_config, R_OK, 0)) {
522522
given_config_source.file = xdg_config;
523-
else
523+
free(user_config);
524+
} else {
524525
given_config_source.file = user_config;
526+
free(xdg_config);
527+
}
525528
}
526529
else if (use_system_config)
527530
given_config_source.file = git_etc_gitconfig();
@@ -628,6 +631,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
628631
check_write();
629632
check_argc(argc, 2, 2);
630633
value = normalize_value(argv[0], argv[1]);
634+
UNLEAK(value);
631635
ret = git_config_set_in_file_gently(given_config_source.file, argv[0], value);
632636
if (ret == CONFIG_NOTHING_SET)
633637
error(_("cannot overwrite multiple values with a single value\n"
@@ -638,13 +642,15 @@ int cmd_config(int argc, const char **argv, const char *prefix)
638642
check_write();
639643
check_argc(argc, 2, 3);
640644
value = normalize_value(argv[0], argv[1]);
645+
UNLEAK(value);
641646
return git_config_set_multivar_in_file_gently(given_config_source.file,
642647
argv[0], value, argv[2], 0);
643648
}
644649
else if (actions == ACTION_ADD) {
645650
check_write();
646651
check_argc(argc, 2, 2);
647652
value = normalize_value(argv[0], argv[1]);
653+
UNLEAK(value);
648654
return git_config_set_multivar_in_file_gently(given_config_source.file,
649655
argv[0], value,
650656
CONFIG_REGEX_NONE, 0);
@@ -653,6 +659,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
653659
check_write();
654660
check_argc(argc, 2, 3);
655661
value = normalize_value(argv[0], argv[1]);
662+
UNLEAK(value);
656663
return git_config_set_multivar_in_file_gently(given_config_source.file,
657664
argv[0], value, argv[2], 1);
658665
}

builtin/init-db.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
579579
set_git_work_tree(work_tree);
580580
}
581581

582+
UNLEAK(real_git_dir);
583+
582584
flags |= INIT_DB_EXIST_OK;
583585
return init_db(git_dir, real_git_dir, template_dir, flags);
584586
}

builtin/ls-files.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,5 +673,6 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
673673
return bad ? 1 : 0;
674674
}
675675

676+
UNLEAK(dir);
676677
return 0;
677678
}

builtin/reset.c

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,11 @@ static inline int is_merge(void)
4444

4545
static int reset_index(const struct object_id *oid, int reset_type, int quiet)
4646
{
47-
int nr = 1;
47+
int i, nr = 0;
4848
struct tree_desc desc[2];
4949
struct tree *tree;
5050
struct unpack_trees_options opts;
51+
int ret = -1;
5152

5253
memset(&opts, 0, sizeof(opts));
5354
opts.head_idx = 1;
@@ -75,23 +76,32 @@ static int reset_index(const struct object_id *oid, int reset_type, int quiet)
7576
struct object_id head_oid;
7677
if (get_oid("HEAD", &head_oid))
7778
return error(_("You do not have a valid HEAD."));
78-
if (!fill_tree_descriptor(desc, &head_oid))
79+
if (!fill_tree_descriptor(desc + nr, &head_oid))
7980
return error(_("Failed to find tree of HEAD."));
8081
nr++;
8182
opts.fn = twoway_merge;
8283
}
8384

84-
if (!fill_tree_descriptor(desc + nr - 1, oid))
85-
return error(_("Failed to find tree of %s."), oid_to_hex(oid));
85+
if (!fill_tree_descriptor(desc + nr, oid)) {
86+
error(_("Failed to find tree of %s."), oid_to_hex(oid));
87+
goto out;
88+
}
89+
nr++;
90+
8691
if (unpack_trees(nr, desc, &opts))
87-
return -1;
92+
goto out;
8893

8994
if (reset_type == MIXED || reset_type == HARD) {
9095
tree = parse_tree_indirect(oid);
9196
prime_cache_tree(&the_index, tree);
9297
}
9398

94-
return 0;
99+
ret = 0;
100+
101+
out:
102+
for (i = 0; i < nr; i++)
103+
free((void *)desc[i].buffer);
104+
return ret;
95105
}
96106

97107
static void print_new_head_line(struct commit *commit)

builtin/update-index.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,10 @@ static int add_one_path(const struct cache_entry *old, const char *path, int len
287287
}
288288
option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
289289
option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
290-
if (add_cache_entry(ce, option))
290+
if (add_cache_entry(ce, option)) {
291+
free(ce);
291292
return error("%s: cannot add to the index - missing --add option?", path);
293+
}
292294
return 0;
293295
}
294296

builtin/worktree.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,8 @@ static int add(int ac, const char **av, const char *prefix)
381381
branch = opts.new_branch;
382382
}
383383

384+
UNLEAK(path);
385+
UNLEAK(opts);
384386
return add_worktree(path, branch, &opts);
385387
}
386388

environment.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ int ignore_untracked_cache_config;
9797
/* This is set by setup_git_dir_gently() and/or git_default_config() */
9898
char *git_work_tree_cfg;
9999

100-
static const char *namespace;
100+
static char *namespace;
101101

102102
static const char *super_prefix;
103103

@@ -152,8 +152,10 @@ void setup_git_env(void)
152152
if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
153153
check_replace_refs = 0;
154154
replace_ref_base = getenv(GIT_REPLACE_REF_BASE_ENVIRONMENT);
155+
free(git_replace_ref_base);
155156
git_replace_ref_base = xstrdup(replace_ref_base ? replace_ref_base
156157
: "refs/replace/");
158+
free(namespace);
157159
namespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));
158160
shallow_file = getenv(GIT_SHALLOW_FILE_ENVIRONMENT);
159161
if (shallow_file)

git-compat-util.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,4 +1169,24 @@ static inline int is_missing_file_error(int errno_)
11691169

11701170
extern int cmd_main(int, const char **);
11711171

1172+
/*
1173+
* You can mark a stack variable with UNLEAK(var) to avoid it being
1174+
* reported as a leak by tools like LSAN or valgrind. The argument
1175+
* should generally be the variable itself (not its address and not what
1176+
* it points to). It's safe to use this on pointers which may already
1177+
* have been freed, or on pointers which may still be in use.
1178+
*
1179+
* Use this _only_ for a variable that leaks by going out of scope at
1180+
* program exit (so only from cmd_* functions or their direct helpers).
1181+
* Normal functions, especially those which may be called multiple
1182+
* times, should actually free their memory. This is only meant as
1183+
* an annotation, and does nothing in non-leak-checking builds.
1184+
*/
1185+
#ifdef SUPPRESS_ANNOTATED_LEAKS
1186+
extern void unleak_memory(const void *ptr, size_t len);
1187+
#define UNLEAK(var) unleak_memory(&(var), sizeof(var));
1188+
#else
1189+
#define UNLEAK(var)
1190+
#endif
1191+
11721192
#endif

repository.c

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,28 +40,28 @@ static void repo_setup_env(struct repository *repo)
4040

4141
repo->different_commondir = find_common_dir(&sb, repo->gitdir,
4242
!repo->ignore_env);
43+
free(repo->commondir);
4344
repo->commondir = strbuf_detach(&sb, NULL);
45+
free(repo->objectdir);
4446
repo->objectdir = git_path_from_env(DB_ENVIRONMENT, repo->commondir,
4547
"objects", !repo->ignore_env);
48+
free(repo->graft_file);
4649
repo->graft_file = git_path_from_env(GRAFT_ENVIRONMENT, repo->commondir,
4750
"info/grafts", !repo->ignore_env);
51+
free(repo->index_file);
4852
repo->index_file = git_path_from_env(INDEX_ENVIRONMENT, repo->gitdir,
4953
"index", !repo->ignore_env);
5054
}
5155

5256
void repo_set_gitdir(struct repository *repo, const char *path)
5357
{
5458
const char *gitfile = read_gitfile(path);
59+
char *old_gitdir = repo->gitdir;
5560

56-
/*
57-
* NEEDSWORK: Eventually we want to be able to free gitdir and the rest
58-
* of the environment before reinitializing it again, but we have some
59-
* crazy code paths where we try to set gitdir with the current gitdir
60-
* and we don't want to free gitdir before copying the passed in value.
61-
*/
6261
repo->gitdir = xstrdup(gitfile ? gitfile : path);
63-
6462
repo_setup_env(repo);
63+
64+
free(old_gitdir);
6565
}
6666

6767
/*

setup.c

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -399,11 +399,6 @@ void setup_work_tree(void)
399399
if (getenv(GIT_WORK_TREE_ENVIRONMENT))
400400
setenv(GIT_WORK_TREE_ENVIRONMENT, ".", 1);
401401

402-
/*
403-
* NEEDSWORK: this call can essentially be set_git_dir(get_git_dir())
404-
* which can cause some problems when trying to free the old value of
405-
* gitdir.
406-
*/
407402
set_git_dir(remove_leading_path(git_dir, work_tree));
408403
initialized = 1;
409404
}

t/test-lib.sh

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ GIT_BUILD_DIR="$TEST_DIRECTORY"/..
4444
: ${ASAN_OPTIONS=detect_leaks=0:abort_on_error=1}
4545
export ASAN_OPTIONS
4646

47+
# If LSAN is in effect we _do_ want leak checking, but we still
48+
# want to abort so that we notice the problems.
49+
: ${LSAN_OPTIONS=abort_on_error=1}
50+
export LSAN_OPTIONS
51+
4752
################################################################
4853
# It appears that people try to run tests without building...
4954
"$GIT_BUILD_DIR/git" >/dev/null
@@ -274,7 +279,7 @@ then
274279
test -z "$verbose" && verbose_only="$valgrind_only"
275280
elif test -n "$valgrind"
276281
then
277-
verbose=t
282+
test -z "$verbose_log" && verbose=t
278283
fi
279284

280285
if test -n "$color"

usage.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,3 +241,18 @@ NORETURN void BUG(const char *fmt, ...)
241241
va_end(ap);
242242
}
243243
#endif
244+
245+
#ifdef SUPPRESS_ANNOTATED_LEAKS
246+
void unleak_memory(const void *ptr, size_t len)
247+
{
248+
static struct suppressed_leak_root {
249+
struct suppressed_leak_root *next;
250+
char data[FLEX_ARRAY];
251+
} *suppressed_leaks;
252+
struct suppressed_leak_root *root;
253+
254+
FLEX_ALLOC_MEM(root, data, ptr, len);
255+
root->next = suppressed_leaks;
256+
suppressed_leaks = root;
257+
}
258+
#endif

0 commit comments

Comments
 (0)