Skip to content

Commit 9081a42

Browse files
avargitster
authored andcommitted
checkout: fix "branch info" memory leaks
The "checkout" command is one of the main sources of leaks in the test suite, let's fix the common ones by not leaking from the "struct branch_info". Doing this is rather straightforward, albeit verbose, we need to xstrdup() constant strings going into the struct, and free() the ones we clobber as we go along. This also means that we can delete previous partial leak fixes in this area, i.e. the "path_to_free" accounting added by 96ec7b1 (Convert resolve_ref+xstrdup to new resolve_refdup function, 2011-12-13). There was some discussion about whether "we should retain the "const char *" here and cast at free() time, or have it be a "char *". Since this is not a public API with any sort of API boundary let's use "char *", as is already being done for the "refname" member of the same struct. The tests to mark as passing were found with: rm .prove; GIT_SKIP_TESTS=t0027 prove -j8 --state=save t[0-9]*.sh :: --immediate # apply & compile this change prove -j8 --state=failed :: --immediate I.e. the ones that were newly passing when the --state=failed command was run. I left out "t3040-subprojects-basic.sh" and "t4131-apply-fake-ancestor.sh" to to optimization-level related differences similar to the ones noted in[1], except that these would be something the current 'linux-leaks' job would run into. 1. https://lore.kernel.org/git/[email protected]/ Signed-off-by: Ævar Arnfjörð Bjarmason <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent cd3e606 commit 9081a42

36 files changed

+98
-31
lines changed

builtin/checkout.c

Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ struct checkout_opts {
9191
};
9292

9393
struct branch_info {
94-
const char *name; /* The short name used */
95-
const char *path; /* The full name of a real branch */
94+
char *name; /* The short name used */
95+
char *path; /* The full name of a real branch */
9696
struct commit *commit; /* The named commit */
9797
char *refname; /* The full name of the ref being checked out. */
9898
struct object_id oid; /* The object ID of the commit being checked out. */
@@ -103,6 +103,14 @@ struct branch_info {
103103
char *checkout;
104104
};
105105

106+
static void branch_info_release(struct branch_info *info)
107+
{
108+
free(info->name);
109+
free(info->path);
110+
free(info->refname);
111+
free(info->checkout);
112+
}
113+
106114
static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,
107115
int changed)
108116
{
@@ -688,9 +696,12 @@ static void setup_branch_path(struct branch_info *branch)
688696
repo_get_oid_committish(the_repository, branch->name, &branch->oid);
689697

690698
strbuf_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL);
691-
if (strcmp(buf.buf, branch->name))
699+
if (strcmp(buf.buf, branch->name)) {
700+
free(branch->name);
692701
branch->name = xstrdup(buf.buf);
702+
}
693703
strbuf_splice(&buf, 0, 0, "refs/heads/", 11);
704+
free(branch->path);
694705
branch->path = strbuf_detach(&buf, NULL);
695706
}
696707

@@ -894,7 +905,9 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
894905
opts->new_branch_log,
895906
opts->quiet,
896907
opts->track);
897-
new_branch_info->name = opts->new_branch;
908+
free(new_branch_info->name);
909+
free(new_branch_info->refname);
910+
new_branch_info->name = xstrdup(opts->new_branch);
898911
setup_branch_path(new_branch_info);
899912
}
900913

@@ -1062,34 +1075,40 @@ static int switch_branches(const struct checkout_opts *opts,
10621075
struct branch_info *new_branch_info)
10631076
{
10641077
int ret = 0;
1065-
struct branch_info old_branch_info;
1066-
void *path_to_free;
1078+
struct branch_info old_branch_info = { 0 };
10671079
struct object_id rev;
10681080
int flag, writeout_error = 0;
10691081
int do_merge = 1;
10701082

10711083
trace2_cmd_mode("branch");
10721084

10731085
memset(&old_branch_info, 0, sizeof(old_branch_info));
1074-
old_branch_info.path = path_to_free = resolve_refdup("HEAD", 0, &rev, &flag);
1086+
old_branch_info.path = resolve_refdup("HEAD", 0, &rev, &flag);
10751087
if (old_branch_info.path)
10761088
old_branch_info.commit = lookup_commit_reference_gently(the_repository, &rev, 1);
10771089
if (!(flag & REF_ISSYMREF))
1078-
old_branch_info.path = NULL;
1090+
FREE_AND_NULL(old_branch_info.path);
10791091

1080-
if (old_branch_info.path)
1081-
skip_prefix(old_branch_info.path, "refs/heads/", &old_branch_info.name);
1092+
if (old_branch_info.path) {
1093+
const char *const prefix = "refs/heads/";
1094+
const char *p;
1095+
if (skip_prefix(old_branch_info.path, prefix, &p))
1096+
old_branch_info.name = xstrdup(p);
1097+
else
1098+
BUG("should be able to skip past '%s' in '%s'!",
1099+
prefix, old_branch_info.path);
1100+
}
10821101

10831102
if (opts->new_orphan_branch && opts->orphan_from_empty_tree) {
10841103
if (new_branch_info->name)
10851104
BUG("'switch --orphan' should never accept a commit as starting point");
10861105
new_branch_info->commit = NULL;
1087-
new_branch_info->name = "(empty)";
1106+
new_branch_info->name = xstrdup("(empty)");
10881107
do_merge = 1;
10891108
}
10901109

10911110
if (!new_branch_info->name) {
1092-
new_branch_info->name = "HEAD";
1111+
new_branch_info->name = xstrdup("HEAD");
10931112
new_branch_info->commit = old_branch_info.commit;
10941113
if (!new_branch_info->commit)
10951114
die(_("You are on a branch yet to be born"));
@@ -1102,7 +1121,7 @@ static int switch_branches(const struct checkout_opts *opts,
11021121
if (do_merge) {
11031122
ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);
11041123
if (ret) {
1105-
free(path_to_free);
1124+
branch_info_release(&old_branch_info);
11061125
return ret;
11071126
}
11081127
}
@@ -1113,7 +1132,8 @@ static int switch_branches(const struct checkout_opts *opts,
11131132
update_refs_for_switch(opts, &old_branch_info, new_branch_info);
11141133

11151134
ret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);
1116-
free(path_to_free);
1135+
branch_info_release(&old_branch_info);
1136+
11171137
return ret || writeout_error;
11181138
}
11191139

@@ -1145,16 +1165,15 @@ static void setup_new_branch_info_and_source_tree(
11451165
struct tree **source_tree = &opts->source_tree;
11461166
struct object_id branch_rev;
11471167

1148-
new_branch_info->name = arg;
1168+
new_branch_info->name = xstrdup(arg);
11491169
setup_branch_path(new_branch_info);
11501170

11511171
if (!check_refname_format(new_branch_info->path, 0) &&
11521172
!read_ref(new_branch_info->path, &branch_rev))
11531173
oidcpy(rev, &branch_rev);
1154-
else {
1155-
free((char *)new_branch_info->path);
1156-
new_branch_info->path = NULL; /* not an existing branch */
1157-
}
1174+
else
1175+
/* not an existing branch */
1176+
FREE_AND_NULL(new_branch_info->path);
11581177

11591178
new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1);
11601179
if (!new_branch_info->commit) {
@@ -1574,12 +1593,11 @@ static char cb_option = 'b';
15741593

15751594
static int checkout_main(int argc, const char **argv, const char *prefix,
15761595
struct checkout_opts *opts, struct option *options,
1577-
const char * const usagestr[])
1596+
const char * const usagestr[],
1597+
struct branch_info *new_branch_info)
15781598
{
1579-
struct branch_info new_branch_info;
15801599
int parseopt_flags = 0;
15811600

1582-
memset(&new_branch_info, 0, sizeof(new_branch_info));
15831601
opts->overwrite_ignore = 1;
15841602
opts->prefix = prefix;
15851603
opts->show_progress = -1;
@@ -1688,7 +1706,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
16881706
opts->track == BRANCH_TRACK_UNSPECIFIED &&
16891707
!opts->new_branch;
16901708
int n = parse_branchname_arg(argc, argv, dwim_ok,
1691-
&new_branch_info, opts, &rev);
1709+
new_branch_info, opts, &rev);
16921710
argv += n;
16931711
argc -= n;
16941712
} else if (!opts->accept_ref && opts->from_treeish) {
@@ -1697,7 +1715,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
16971715
if (get_oid_mb(opts->from_treeish, &rev))
16981716
die(_("could not resolve %s"), opts->from_treeish);
16991717

1700-
setup_new_branch_info_and_source_tree(&new_branch_info,
1718+
setup_new_branch_info_and_source_tree(new_branch_info,
17011719
opts, &rev,
17021720
opts->from_treeish);
17031721

@@ -1717,7 +1735,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
17171735
* Try to give more helpful suggestion.
17181736
* new_branch && argc > 1 will be caught later.
17191737
*/
1720-
if (opts->new_branch && argc == 1 && !new_branch_info.commit)
1738+
if (opts->new_branch && argc == 1 && !new_branch_info->commit)
17211739
die(_("'%s' is not a commit and a branch '%s' cannot be created from it"),
17221740
argv[0], opts->new_branch);
17231741

@@ -1766,11 +1784,10 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
17661784
strbuf_release(&buf);
17671785
}
17681786

1769-
UNLEAK(opts);
17701787
if (opts->patch_mode || opts->pathspec.nr)
1771-
return checkout_paths(opts, &new_branch_info);
1788+
return checkout_paths(opts, new_branch_info);
17721789
else
1773-
return checkout_branch(opts, &new_branch_info);
1790+
return checkout_branch(opts, new_branch_info);
17741791
}
17751792

17761793
int cmd_checkout(int argc, const char **argv, const char *prefix)
@@ -1789,6 +1806,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
17891806
OPT_END()
17901807
};
17911808
int ret;
1809+
struct branch_info new_branch_info = { 0 };
17921810

17931811
memset(&opts, 0, sizeof(opts));
17941812
opts.dwim_new_local_branch = 1;
@@ -1819,7 +1837,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
18191837
options = add_checkout_path_options(&opts, options);
18201838

18211839
ret = checkout_main(argc, argv, prefix, &opts,
1822-
options, checkout_usage);
1840+
options, checkout_usage, &new_branch_info);
1841+
branch_info_release(&new_branch_info);
1842+
clear_pathspec(&opts.pathspec);
18231843
FREE_AND_NULL(options);
18241844
return ret;
18251845
}
@@ -1840,6 +1860,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
18401860
OPT_END()
18411861
};
18421862
int ret;
1863+
struct branch_info new_branch_info = { 0 };
18431864

18441865
memset(&opts, 0, sizeof(opts));
18451866
opts.dwim_new_local_branch = 1;
@@ -1859,7 +1880,8 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
18591880
cb_option = 'c';
18601881

18611882
ret = checkout_main(argc, argv, prefix, &opts,
1862-
options, switch_branch_usage);
1883+
options, switch_branch_usage, &new_branch_info);
1884+
branch_info_release(&new_branch_info);
18631885
FREE_AND_NULL(options);
18641886
return ret;
18651887
}
@@ -1881,6 +1903,7 @@ int cmd_restore(int argc, const char **argv, const char *prefix)
18811903
OPT_END()
18821904
};
18831905
int ret;
1906+
struct branch_info new_branch_info = { 0 };
18841907

18851908
memset(&opts, 0, sizeof(opts));
18861909
opts.accept_ref = 0;
@@ -1896,7 +1919,8 @@ int cmd_restore(int argc, const char **argv, const char *prefix)
18961919
options = add_checkout_path_options(&opts, options);
18971920

18981921
ret = checkout_main(argc, argv, prefix, &opts,
1899-
options, restore_usage);
1922+
options, restore_usage, &new_branch_info);
1923+
branch_info_release(&new_branch_info);
19001924
FREE_AND_NULL(options);
19011925
return ret;
19021926
}

t/t0020-crlf.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ test_description='CRLF conversion'
55
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
66
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
77

8+
TEST_PASSES_SANITIZE_LEAK=true
89
. ./test-lib.sh
910

1011
has_cr() {

t/t1005-read-tree-reset.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
test_description='read-tree -u --reset'
44

5+
TEST_PASSES_SANITIZE_LEAK=true
56
. ./test-lib.sh
67
. "$TEST_DIRECTORY"/lib-read-tree.sh
78

t/t1008-read-tree-overlay.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ test_description='test multi-tree read-tree without merging'
55
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
66
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
77

8+
TEST_PASSES_SANITIZE_LEAK=true
89
. ./test-lib.sh
910
. "$TEST_DIRECTORY"/lib-read-tree.sh
1011

t/t1403-show-ref.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ test_description='show-ref'
44
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
55
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
66

7+
TEST_PASSES_SANITIZE_LEAK=true
78
. ./test-lib.sh
89

910
test_expect_success setup '

t/t1406-submodule-ref-store.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ test_description='test submodule ref store api'
55
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
66
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
77

8+
TEST_PASSES_SANITIZE_LEAK=true
89
. ./test-lib.sh
910

1011
RUN="test-tool ref-store submodule:sub"

t/t1505-rev-parse-last.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ test_description='test @{-N} syntax'
55
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
66
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
77

8+
TEST_PASSES_SANITIZE_LEAK=true
89
. ./test-lib.sh
910

1011

t/t2007-checkout-symlink.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ test_description='git checkout to switch between branches with symlink<->dir'
77
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
88
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
99

10+
TEST_PASSES_SANITIZE_LEAK=true
1011
. ./test-lib.sh
1112

1213
test_expect_success setup '

t/t2008-checkout-subdir.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
test_description='git checkout from subdirectories'
66

7+
TEST_PASSES_SANITIZE_LEAK=true
78
. ./test-lib.sh
89

910
test_expect_success setup '

t/t2009-checkout-statinfo.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ test_description='checkout should leave clean stat info'
55
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
66
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
77

8+
TEST_PASSES_SANITIZE_LEAK=true
89
. ./test-lib.sh
910

1011
test_expect_success 'setup' '

t/t2010-checkout-ambiguous.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ test_description='checkout and pathspecs/refspecs ambiguities'
55
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
66
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
77

8+
TEST_PASSES_SANITIZE_LEAK=true
89
. ./test-lib.sh
910

1011
test_expect_success 'setup' '

t/t2011-checkout-invalid-head.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ test_description='checkout switching away from an invalid branch'
55
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
66
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
77

8+
TEST_PASSES_SANITIZE_LEAK=true
89
. ./test-lib.sh
910

1011
test_expect_success 'setup' '

t/t2014-checkout-switch.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#!/bin/sh
22

33
test_description='Peter MacMillan'
4+
5+
TEST_PASSES_SANITIZE_LEAK=true
46
. ./test-lib.sh
57

68
test_expect_success setup '

t/t2017-checkout-orphan.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Main Tests for --orphan functionality.'
1010
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
1111
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
1212

13+
TEST_PASSES_SANITIZE_LEAK=true
1314
. ./test-lib.sh
1415

1516
TEST_FILE=foo

t/t2019-checkout-ambiguous-ref.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#!/bin/sh
22

33
test_description='checkout handling of ambiguous (branch/tag) refs'
4+
5+
TEST_PASSES_SANITIZE_LEAK=true
46
. ./test-lib.sh
57

68
test_expect_success 'setup ambiguous refs' '

t/t2021-checkout-overwrite.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#!/bin/sh
22

33
test_description='checkout must not overwrite an untracked objects'
4+
5+
TEST_PASSES_SANITIZE_LEAK=true
46
. ./test-lib.sh
57

68
test_expect_success 'setup' '

t/t2022-checkout-paths.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ test_description='checkout $tree -- $paths'
44
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
55
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
66

7+
TEST_PASSES_SANITIZE_LEAK=true
78
. ./test-lib.sh
89

910
test_expect_success setup '

t/t2026-checkout-pathspec-file.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
test_description='checkout --pathspec-from-file'
44

5+
TEST_PASSES_SANITIZE_LEAK=true
56
. ./test-lib.sh
67

78
test_tick

t/t2106-update-index-assume-unchanged.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
test_description='git update-index --assume-unchanged test.
44
'
55

6+
TEST_PASSES_SANITIZE_LEAK=true
67
. ./test-lib.sh
78

89
test_expect_success 'setup' '

0 commit comments

Comments
 (0)