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 <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Patrick Steinhardt committed
Jun 11, 2024 at 11:21 UTC
44ec7c575f914d77787a17cefd094e3c46b8b12b
17 files changed
+54
-29
builtin/merge-tree.c
+1
@@ -482,6 +482,7 @@ static int real_merge(struct merge_tree_options *o,
482
die(_("refusing to merge unrelated histories"));
483
merge_bases = reverse_commit_list(merge_bases);
484
merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);
485
+ free_commit_list(merge_bases);
486
}
487
488
if (result.clean < 0)
builtin/merge.c
+2
@@ -746,6 +746,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
746
else
747
clean = merge_recursive(&o, head, remoteheads->item,
748
reversed, &result);
749
+ free_commit_list(reversed);
750
+
751
if (clean < 0) {
752
rollback_lock_file(&lock);
753
return 2;
commit.c
+1
-1
@@ -680,7 +680,7 @@ unsigned commit_list_count(const struct commit_list *l)
680
return c;
681
}
682
683
-struct commit_list *copy_commit_list(struct commit_list *list)
683
+struct commit_list *copy_commit_list(const struct commit_list *list)
684
{
685
struct commit_list *head = NULL;
686
struct commit_list **pp = &head;
commit.h
+1
-1
@@ -181,7 +181,7 @@ struct commit_list *commit_list_insert_by_date(struct commit *item,
181
void commit_list_sort_by_date(struct commit_list **list);
182
183
/* 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);
185
186
/* Modify list in-place to reverse it, returning new head; list will be tail */
187
struct commit_list *reverse_commit_list(struct commit_list *list);
log-tree.c
+1
@@ -1047,6 +1047,7 @@ static int do_remerge_diff(struct rev_info *opt,
1047
log_tree_diff_flush(opt);
1048
1049
/* Cleanup */
1050
+ free_commit_list(bases);
1051
cleanup_additional_headers(&opt->diffopt);
1052
strbuf_release(&parent1_desc);
1053
strbuf_release(&parent2_desc);
merge-ort-wrappers.c
+1
-1
@@ -48,7 +48,7 @@ int merge_ort_nonrecursive(struct merge_options *opt,
48
int merge_ort_recursive(struct merge_options *opt,
49
struct commit *side1,
50
struct commit *side2,
51
- struct commit_list *merge_bases,
51
+ const struct commit_list *merge_bases,
52
struct commit **result)
53
{
54
struct tree *head = repo_get_commit_tree(opt->repo, side1);
merge-ort-wrappers.h
+1
-1
@@ -19,7 +19,7 @@ int merge_ort_nonrecursive(struct merge_options *opt,
19
int merge_ort_recursive(struct merge_options *opt,
20
struct commit *h1,
21
struct commit *h2,
22
- struct commit_list *ancestors,
22
+ const struct commit_list *ancestors,
23
struct commit **result);
24
25
#endif
merge-ort.c
+8
-4
@@ -5071,11 +5071,12 @@ redo:
5071
* Originally from merge_recursive_internal(); somewhat adapted, though.
5072
*/
5073
static void merge_ort_internal(struct merge_options *opt,
5074
- struct commit_list *merge_bases,
5074
+ const struct commit_list *_merge_bases,
5075
struct commit *h1,
5076
struct commit *h2,
5077
struct merge_result *result)
5078
{
5079
+ struct commit_list *merge_bases = copy_commit_list(_merge_bases);
5080
struct commit *next;
5081
struct commit *merged_merge_bases;
5082
const char *ancestor_name;
@@ -5085,7 +5086,7 @@ static void merge_ort_internal(struct merge_options *opt,
5086
if (repo_get_merge_bases(the_repository, h1, h2,
5087
&merge_bases) < 0) {
5088
result->clean = -1;
5088
- return;
5089
+ goto out;
5090
}
5091
/* See merge-ort.h:merge_incore_recursive() declaration NOTE */
5092
merge_bases = reverse_commit_list(merge_bases);
@@ -5129,7 +5130,7 @@ static void merge_ort_internal(struct merge_options *opt,
5130
opt->branch2 = "Temporary merge branch 2";
5131
merge_ort_internal(opt, NULL, prev, next, result);
5132
if (result->clean < 0)
5132
- return;
5133
+ goto out;
5134
opt->branch1 = saved_b1;
5135
opt->branch2 = saved_b2;
5136
opt->priv->call_depth--;
@@ -5152,6 +5153,9 @@ static void merge_ort_internal(struct merge_options *opt,
5153
result);
5154
strbuf_release(&merge_base_abbrev);
5155
opt->ancestor = NULL; /* avoid accidental re-use of opt->ancestor */
5156
+
5157
+out:
5158
+ free_commit_list(merge_bases);
5159
}
5160
5161
void merge_incore_nonrecursive(struct merge_options *opt,
@@ -5181,7 +5185,7 @@ void merge_incore_nonrecursive(struct merge_options *opt,
5185
}
5186
5187
void merge_incore_recursive(struct merge_options *opt,
5184
- struct commit_list *merge_bases,
5188
+ const struct commit_list *merge_bases,
5189
struct commit *side1,
5190
struct commit *side2,
5191
struct merge_result *result)
merge-ort.h
+1
-1
@@ -59,7 +59,7 @@ struct merge_result {
59
* first", 2006-08-09)
60
*/
61
void merge_incore_recursive(struct merge_options *opt,
62
- struct commit_list *merge_bases,
62
+ const struct commit_list *merge_bases,
63
struct commit *side1,
64
struct commit *side2,
65
struct merge_result *result);
merge-recursive.c
+30
-19
@@ -3633,15 +3633,16 @@ static int merge_trees_internal(struct merge_options *opt,
3633
static int merge_recursive_internal(struct merge_options *opt,
3634
struct commit *h1,
3635
struct commit *h2,
3636
- struct commit_list *merge_bases,
3636
+ const struct commit_list *_merge_bases,
3637
struct commit **result)
3638
{
3639
+ struct commit_list *merge_bases = copy_commit_list(_merge_bases);
3640
struct commit_list *iter;
3641
struct commit *merged_merge_bases;
3642
struct tree *result_tree;
3642
- int clean;
3643
const char *ancestor_name;
3644
struct strbuf merge_base_abbrev = STRBUF_INIT;
3645
+ int ret;
3646
3647
if (show(opt, 4)) {
3648
output(opt, 4, _("Merging:"));
@@ -3651,8 +3652,10 @@ static int merge_recursive_internal(struct merge_options *opt,
3652
3653
if (!merge_bases) {
3654
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
+ }
3659
merge_bases = reverse_commit_list(merge_bases);
3660
}
3661
@@ -3702,14 +3705,18 @@ static int merge_recursive_internal(struct merge_options *opt,
3705
opt->branch1 = "Temporary merge branch 1";
3706
opt->branch2 = "Temporary merge branch 2";
3707
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
+ }
3712
opt->branch1 = saved_b1;
3713
opt->branch2 = saved_b2;
3714
opt->priv->call_depth--;
3715
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
+ }
3720
}
3721
3722
/*
@@ -3726,17 +3733,16 @@ static int merge_recursive_internal(struct merge_options *opt,
3733
repo_read_index(opt->repo);
3734
3735
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);
3742
opt->ancestor = NULL; /* avoid accidental re-use of opt->ancestor */
3737
- if (clean < 0) {
3743
+ if (ret < 0) {
3744
flush_output(opt);
3739
- return clean;
3745
+ goto out;
3746
}
3747
3748
if (opt->priv->call_depth) {
@@ -3745,7 +3751,11 @@ static int merge_recursive_internal(struct merge_options *opt,
3751
commit_list_insert(h1, &(*result)->parents);
3752
commit_list_insert(h2, &(*result)->parents->next);
3753
}
3748
- return clean;
3754
+
3755
+out:
3756
+ strbuf_release(&merge_base_abbrev);
3757
+ free_commit_list(merge_bases);
3758
+ return ret;
3759
}
3760
3761
static int merge_start(struct merge_options *opt, struct tree *head)
@@ -3827,7 +3837,7 @@ int merge_trees(struct merge_options *opt,
3837
int merge_recursive(struct merge_options *opt,
3838
struct commit *h1,
3839
struct commit *h2,
3830
- struct commit_list *merge_bases,
3840
+ const struct commit_list *merge_bases,
3841
struct commit **result)
3842
{
3843
int clean;
@@ -3895,6 +3905,7 @@ int merge_recursive_generic(struct merge_options *opt,
3905
repo_hold_locked_index(opt->repo, &lock, LOCK_DIE_ON_ERROR);
3906
clean = merge_recursive(opt, head_commit, next_commit, ca,
3907
result);
3908
+ free_commit_list(ca);
3909
if (clean < 0) {
3910
rollback_lock_file(&lock);
3911
return clean;
merge-recursive.h
+1
-1
@@ -104,7 +104,7 @@ int merge_trees(struct merge_options *opt,
104
int merge_recursive(struct merge_options *opt,
105
struct commit *h1,
106
struct commit *h2,
107
- struct commit_list *merge_bases,
107
+ const struct commit_list *merge_bases,
108
struct commit **result);
109
110
/*
sequencer.c
+1
@@ -4315,6 +4315,7 @@ leave_merge:
4315
strbuf_release(&ref_name);
4316
rollback_lock_file(&lock);
4317
free_commit_list(to_merge);
4318
+ free_commit_list(bases);
4319
return ret;
4320
}
4321
t/t3430-rebase-merges.sh
+1
@@ -21,6 +21,7 @@ Initial setup:
21
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
22
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
23
24
+TEST_PASSES_SANITIZE_LEAK=true
25
. ./test-lib.sh
26
. "$TEST_DIRECTORY"/lib-rebase.sh
27
. "$TEST_DIRECTORY"/lib-log-graph.sh
t/t6402-merge-rename.sh
+1
@@ -4,6 +4,7 @@ test_description='Merge-recursive merging renames'
4
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
5
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
6
7
+TEST_PASSES_SANITIZE_LEAK=true
8
. ./test-lib.sh
9
10
modify () {
t/t6430-merge-recursive.sh
+1
@@ -5,6 +5,7 @@ test_description='merge-recursive backend test'
5
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
6
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
7
8
+TEST_PASSES_SANITIZE_LEAK=true
9
. ./test-lib.sh
10
. "$TEST_DIRECTORY"/lib-merge.sh
11
t/t6436-merge-overwrite.sh
+1
@@ -7,6 +7,7 @@ Do not overwrite changes.'
7
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
8
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
9
10
+TEST_PASSES_SANITIZE_LEAK=true
11
. ./test-lib.sh
12
13
test_expect_success 'setup' '
t/t7611-merge-abort.sh
+1
@@ -25,6 +25,7 @@ Next, test git merge --abort with the following variables:
25
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
26
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
27
28
+TEST_PASSES_SANITIZE_LEAK=true
29
. ./test-lib.sh
30
31
test_expect_success 'setup' '