Skip to content

Commit 44ec7c5

Browse files
pks-tgitster
authored andcommitted
merge: fix leaking merge bases
When calling either the recursive or the ORT merge machineries we need to provide a list of merge bases. The ownership of that parameter is then implicitly transferred to the callee, which is somewhat fishy. Furthermore, that list may leak in some cases where the merge machinery runs into an error, thus causing a memory leak. Refactor the code such that we stop transferring ownership. Instead, the merge machinery will now create its own local copies of the passed in list as required if they need to modify the list. Free the list at the callsites as required. Signed-off-by: Patrick Steinhardt <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 77241a6 commit 44ec7c5

17 files changed

+54
-29
lines changed

builtin/merge-tree.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,7 @@ static int real_merge(struct merge_tree_options *o,
482482
die(_("refusing to merge unrelated histories"));
483483
merge_bases = reverse_commit_list(merge_bases);
484484
merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);
485+
free_commit_list(merge_bases);
485486
}
486487

487488
if (result.clean < 0)

builtin/merge.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
746746
else
747747
clean = merge_recursive(&o, head, remoteheads->item,
748748
reversed, &result);
749+
free_commit_list(reversed);
750+
749751
if (clean < 0) {
750752
rollback_lock_file(&lock);
751753
return 2;

commit.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -680,7 +680,7 @@ unsigned commit_list_count(const struct commit_list *l)
680680
return c;
681681
}
682682

683-
struct commit_list *copy_commit_list(struct commit_list *list)
683+
struct commit_list *copy_commit_list(const struct commit_list *list)
684684
{
685685
struct commit_list *head = NULL;
686686
struct commit_list **pp = &head;

commit.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ struct commit_list *commit_list_insert_by_date(struct commit *item,
181181
void commit_list_sort_by_date(struct commit_list **list);
182182

183183
/* Shallow copy of the input list */
184-
struct commit_list *copy_commit_list(struct commit_list *list);
184+
struct commit_list *copy_commit_list(const struct commit_list *list);
185185

186186
/* Modify list in-place to reverse it, returning new head; list will be tail */
187187
struct commit_list *reverse_commit_list(struct commit_list *list);

log-tree.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,6 +1047,7 @@ static int do_remerge_diff(struct rev_info *opt,
10471047
log_tree_diff_flush(opt);
10481048

10491049
/* Cleanup */
1050+
free_commit_list(bases);
10501051
cleanup_additional_headers(&opt->diffopt);
10511052
strbuf_release(&parent1_desc);
10521053
strbuf_release(&parent2_desc);

merge-ort-wrappers.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ int merge_ort_nonrecursive(struct merge_options *opt,
4848
int merge_ort_recursive(struct merge_options *opt,
4949
struct commit *side1,
5050
struct commit *side2,
51-
struct commit_list *merge_bases,
51+
const struct commit_list *merge_bases,
5252
struct commit **result)
5353
{
5454
struct tree *head = repo_get_commit_tree(opt->repo, side1);

merge-ort-wrappers.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ int merge_ort_nonrecursive(struct merge_options *opt,
1919
int merge_ort_recursive(struct merge_options *opt,
2020
struct commit *h1,
2121
struct commit *h2,
22-
struct commit_list *ancestors,
22+
const struct commit_list *ancestors,
2323
struct commit **result);
2424

2525
#endif

merge-ort.c

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5071,11 +5071,12 @@ static void merge_ort_nonrecursive_internal(struct merge_options *opt,
50715071
* Originally from merge_recursive_internal(); somewhat adapted, though.
50725072
*/
50735073
static void merge_ort_internal(struct merge_options *opt,
5074-
struct commit_list *merge_bases,
5074+
const struct commit_list *_merge_bases,
50755075
struct commit *h1,
50765076
struct commit *h2,
50775077
struct merge_result *result)
50785078
{
5079+
struct commit_list *merge_bases = copy_commit_list(_merge_bases);
50795080
struct commit *next;
50805081
struct commit *merged_merge_bases;
50815082
const char *ancestor_name;
@@ -5085,7 +5086,7 @@ static void merge_ort_internal(struct merge_options *opt,
50855086
if (repo_get_merge_bases(the_repository, h1, h2,
50865087
&merge_bases) < 0) {
50875088
result->clean = -1;
5088-
return;
5089+
goto out;
50895090
}
50905091
/* See merge-ort.h:merge_incore_recursive() declaration NOTE */
50915092
merge_bases = reverse_commit_list(merge_bases);
@@ -5129,7 +5130,7 @@ static void merge_ort_internal(struct merge_options *opt,
51295130
opt->branch2 = "Temporary merge branch 2";
51305131
merge_ort_internal(opt, NULL, prev, next, result);
51315132
if (result->clean < 0)
5132-
return;
5133+
goto out;
51335134
opt->branch1 = saved_b1;
51345135
opt->branch2 = saved_b2;
51355136
opt->priv->call_depth--;
@@ -5152,6 +5153,9 @@ static void merge_ort_internal(struct merge_options *opt,
51525153
result);
51535154
strbuf_release(&merge_base_abbrev);
51545155
opt->ancestor = NULL; /* avoid accidental re-use of opt->ancestor */
5156+
5157+
out:
5158+
free_commit_list(merge_bases);
51555159
}
51565160

51575161
void merge_incore_nonrecursive(struct merge_options *opt,
@@ -5181,7 +5185,7 @@ void merge_incore_nonrecursive(struct merge_options *opt,
51815185
}
51825186

51835187
void merge_incore_recursive(struct merge_options *opt,
5184-
struct commit_list *merge_bases,
5188+
const struct commit_list *merge_bases,
51855189
struct commit *side1,
51865190
struct commit *side2,
51875191
struct merge_result *result)

merge-ort.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ struct merge_result {
5959
* first", 2006-08-09)
6060
*/
6161
void merge_incore_recursive(struct merge_options *opt,
62-
struct commit_list *merge_bases,
62+
const struct commit_list *merge_bases,
6363
struct commit *side1,
6464
struct commit *side2,
6565
struct merge_result *result);

merge-recursive.c

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3633,15 +3633,16 @@ static int merge_trees_internal(struct merge_options *opt,
36333633
static int merge_recursive_internal(struct merge_options *opt,
36343634
struct commit *h1,
36353635
struct commit *h2,
3636-
struct commit_list *merge_bases,
3636+
const struct commit_list *_merge_bases,
36373637
struct commit **result)
36383638
{
3639+
struct commit_list *merge_bases = copy_commit_list(_merge_bases);
36393640
struct commit_list *iter;
36403641
struct commit *merged_merge_bases;
36413642
struct tree *result_tree;
3642-
int clean;
36433643
const char *ancestor_name;
36443644
struct strbuf merge_base_abbrev = STRBUF_INIT;
3645+
int ret;
36453646

36463647
if (show(opt, 4)) {
36473648
output(opt, 4, _("Merging:"));
@@ -3651,8 +3652,10 @@ static int merge_recursive_internal(struct merge_options *opt,
36513652

36523653
if (!merge_bases) {
36533654
if (repo_get_merge_bases(the_repository, h1, h2,
3654-
&merge_bases) < 0)
3655-
return -1;
3655+
&merge_bases) < 0) {
3656+
ret = -1;
3657+
goto out;
3658+
}
36563659
merge_bases = reverse_commit_list(merge_bases);
36573660
}
36583661

@@ -3702,14 +3705,18 @@ static int merge_recursive_internal(struct merge_options *opt,
37023705
opt->branch1 = "Temporary merge branch 1";
37033706
opt->branch2 = "Temporary merge branch 2";
37043707
if (merge_recursive_internal(opt, merged_merge_bases, iter->item,
3705-
NULL, &merged_merge_bases) < 0)
3706-
return -1;
3708+
NULL, &merged_merge_bases) < 0) {
3709+
ret = -1;
3710+
goto out;
3711+
}
37073712
opt->branch1 = saved_b1;
37083713
opt->branch2 = saved_b2;
37093714
opt->priv->call_depth--;
37103715

3711-
if (!merged_merge_bases)
3712-
return err(opt, _("merge returned no commit"));
3716+
if (!merged_merge_bases) {
3717+
ret = err(opt, _("merge returned no commit"));
3718+
goto out;
3719+
}
37133720
}
37143721

37153722
/*
@@ -3726,17 +3733,16 @@ static int merge_recursive_internal(struct merge_options *opt,
37263733
repo_read_index(opt->repo);
37273734

37283735
opt->ancestor = ancestor_name;
3729-
clean = merge_trees_internal(opt,
3730-
repo_get_commit_tree(opt->repo, h1),
3731-
repo_get_commit_tree(opt->repo, h2),
3732-
repo_get_commit_tree(opt->repo,
3733-
merged_merge_bases),
3734-
&result_tree);
3735-
strbuf_release(&merge_base_abbrev);
3736+
ret = merge_trees_internal(opt,
3737+
repo_get_commit_tree(opt->repo, h1),
3738+
repo_get_commit_tree(opt->repo, h2),
3739+
repo_get_commit_tree(opt->repo,
3740+
merged_merge_bases),
3741+
&result_tree);
37363742
opt->ancestor = NULL; /* avoid accidental re-use of opt->ancestor */
3737-
if (clean < 0) {
3743+
if (ret < 0) {
37383744
flush_output(opt);
3739-
return clean;
3745+
goto out;
37403746
}
37413747

37423748
if (opt->priv->call_depth) {
@@ -3745,7 +3751,11 @@ static int merge_recursive_internal(struct merge_options *opt,
37453751
commit_list_insert(h1, &(*result)->parents);
37463752
commit_list_insert(h2, &(*result)->parents->next);
37473753
}
3748-
return clean;
3754+
3755+
out:
3756+
strbuf_release(&merge_base_abbrev);
3757+
free_commit_list(merge_bases);
3758+
return ret;
37493759
}
37503760

37513761
static int merge_start(struct merge_options *opt, struct tree *head)
@@ -3827,7 +3837,7 @@ int merge_trees(struct merge_options *opt,
38273837
int merge_recursive(struct merge_options *opt,
38283838
struct commit *h1,
38293839
struct commit *h2,
3830-
struct commit_list *merge_bases,
3840+
const struct commit_list *merge_bases,
38313841
struct commit **result)
38323842
{
38333843
int clean;
@@ -3895,6 +3905,7 @@ int merge_recursive_generic(struct merge_options *opt,
38953905
repo_hold_locked_index(opt->repo, &lock, LOCK_DIE_ON_ERROR);
38963906
clean = merge_recursive(opt, head_commit, next_commit, ca,
38973907
result);
3908+
free_commit_list(ca);
38983909
if (clean < 0) {
38993910
rollback_lock_file(&lock);
39003911
return clean;

merge-recursive.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ int merge_trees(struct merge_options *opt,
104104
int merge_recursive(struct merge_options *opt,
105105
struct commit *h1,
106106
struct commit *h2,
107-
struct commit_list *merge_bases,
107+
const struct commit_list *merge_bases,
108108
struct commit **result);
109109

110110
/*

sequencer.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4315,6 +4315,7 @@ static int do_merge(struct repository *r,
43154315
strbuf_release(&ref_name);
43164316
rollback_lock_file(&lock);
43174317
free_commit_list(to_merge);
4318+
free_commit_list(bases);
43184319
return ret;
43194320
}
43204321

t/t3430-rebase-merges.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Initial setup:
2121
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
2222
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
2323

24+
TEST_PASSES_SANITIZE_LEAK=true
2425
. ./test-lib.sh
2526
. "$TEST_DIRECTORY"/lib-rebase.sh
2627
. "$TEST_DIRECTORY"/lib-log-graph.sh

t/t6402-merge-rename.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ test_description='Merge-recursive merging renames'
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
modify () {

t/t6430-merge-recursive.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ test_description='merge-recursive backend test'
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-merge.sh
1011

t/t6436-merge-overwrite.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Do not overwrite changes.'
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/t7611-merge-abort.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Next, test git merge --abort with the following variables:
2525
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
2626
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
2727

28+
TEST_PASSES_SANITIZE_LEAK=true
2829
. ./test-lib.sh
2930

3031
test_expect_success 'setup' '

0 commit comments

Comments
 (0)