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
This commit is contained in:
commit
09595ab381
3
Makefile
3
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
|
||||
|
@ -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 @@ finish:
|
||||
die(_("Unable to write new index file"));
|
||||
}
|
||||
|
||||
UNLEAK(pathspec);
|
||||
UNLEAK(dir);
|
||||
return exit_status;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
14
repository.c
14
repository.c
@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
5
setup.c
5
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;
|
||||
}
|
||||
|
@ -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"
|
||||
|
15
usage.c
15
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
|
||||
|
Loading…
Reference in New Issue
Block a user