[PATCH v2 0/2] This fixes a minor memory leak (detected by LeakSanitizer) in git merge
- From
- Kevin Backhouse via GitGitGadget <gitgitgadget@gmail.com>
- Date
- Aug 24, 2023, 14:12 UTC
- Message-ID
- <pull.1577.v2.git.1692886365.gitgitgadget@gmail.com>
- In-Reply-To
- <pull.1577.git.1692389061490.gitgitgadget@gmail.com>
Hi Junio,
Thank you for your comments. As you suggested, I have added similar fixes in merge-recursive.c and updated the commit message. I have also added a test.
Thanks,
Kev
Kevin Backhouse (2): Regression test for https://github.com/gitgitgadget/git/pull/1577 Fix minor memory leak found by LeakSanitizer.
merge-ort-wrappers.c | 4 +++- merge-ort.c | 4 +++- merge-recursive.c | 32 ++++++++++++++++++++++---------- t/t9904-merge-leak.sh | 40 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 12 deletions(-) create mode 100755 t/t9904-merge-leak.sh
base-commit: f9972720e9a405e4f6924a7cde0ed5880687f4d0 Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1577%2Fkevinbackhouse%2Ffree-merge-bases-v2 Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1577/kevinbackhouse/free-merge-bases-v2 Pull-Request: https://github.com/gitgitgadget/git/pull/1577
Range-diff vs v1:
-: ----------- > 1: f940104a781 Regression test for https://github.com/gitgitgadget/git/pull/1577
1: 64b00e4448d ! 2: 353e1960b44 This fixes a minor memory leak (detected by LeakSanitizer) in git merge.
@@ Metadata
Author: Kevin Backhouse <kevinbackhouse@github.com>
## Commit message ##
- This fixes a minor memory leak (detected by LeakSanitizer) in git merge.
+ Fix minor memory leak found by LeakSanitizer.
- To reproduce (with an ASAN build):
+ The callers of merge_recursive() and merge_ort_recursive() expects the
+ commit list passed in as the merge_bases parameter to be fully
+ consumed by the function and does not free it when the function
+ returns. In normal cases, the commit list does get consumed, but when
+ the function returns early upon encountering an error, it forgets to
+ clean it up.
- ```
- mkdir test
- cd test
- git init
- echo x > x.txt
- git add .
- git commit -m "WIP"
- git checkout -b dev
- echo y > x.txt
- git add .
- git commit -m "WIP"
- git checkout main
- echo z > x.txt
- git add .
- git commit -m "WIP"
- echo a > x.txt
- git add .
- git merge dev
- ```
-
- The fix is to call free_commit_list(merge_bases) when an error occurs.
+ Fix this by freeing the list in the code paths for error returns.
Signed-off-by: Kevin Backhouse <kevinbackhouse@github.com>
@@ merge-ort.c: static void merge_ort_internal(struct merge_options *opt,
opt->branch1 = saved_b1;
opt->branch2 = saved_b2;
opt->priv->call_depth--;
+
+ ## merge-recursive.c ##
+@@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt,
+ opt->branch1 = "Temporary merge branch 1";
+ opt->branch2 = "Temporary merge branch 2";
+ if (merge_recursive_internal(opt, merged_merge_bases, iter->item,
+- NULL, &merged_merge_bases) < 0)
+- return -1;
++ NULL, &merged_merge_bases) < 0) {
++ clean = -1;
++ goto out;
++ }
+ opt->branch1 = saved_b1;
+ opt->branch2 = saved_b2;
+ opt->priv->call_depth--;
+
+- if (!merged_merge_bases)
+- return err(opt, _("merge returned no commit"));
++ if (!merged_merge_bases) {
++ clean = err(opt, _("merge returned no commit"));
++ goto out;
++ }
+ }
+
+ /*
+@@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt,
+ repo_get_commit_tree(opt->repo,
+ merged_merge_bases),
+ &result_tree);
++
++out:
+ strbuf_release(&merge_base_abbrev);
+ opt->ancestor = NULL; /* avoid accidental re-use of opt->ancestor */
++ free_commit_list(merge_bases);
+ if (clean < 0) {
+ flush_output(opt);
+ return clean;
+@@ merge-recursive.c: static int merge_start(struct merge_options *opt, struct tree *head)
+ assert(!opt->record_conflict_msgs_as_headers);
+ assert(!opt->msg_header_prefix);
+
++ CALLOC_ARRAY(opt->priv, 1);
++ string_list_init_dup(&opt->priv->df_conflict_file_set);
++
+ /* Sanity check on repo state; index must match head */
+ if (repo_index_has_changes(opt->repo, head, &sb)) {
+ err(opt, _("Your local changes to the following files would be overwritten by merge:\n %s"),
+@@ merge-recursive.c: static int merge_start(struct merge_options *opt, struct tree *head)
+ return -1;
+ }
+
+- CALLOC_ARRAY(opt->priv, 1);
+- string_list_init_dup(&opt->priv->df_conflict_file_set);
+ return 0;
+ }
+
+ static void merge_finalize(struct merge_options *opt)
+ {
+ flush_output(opt);
+- if (!opt->priv->call_depth && opt->buffer_output < 2)
+- strbuf_release(&opt->obuf);
++ strbuf_release(&opt->obuf);
+ if (show(opt, 2))
+ diff_warn_rename_limit("merge.renamelimit",
+ opt->priv->needed_rename_limit, 0);
+@@ merge-recursive.c: int merge_trees(struct merge_options *opt,
+
+ assert(opt->ancestor != NULL);
+
+- if (merge_start(opt, head))
++ if (merge_start(opt, head)) {
++ merge_finalize(opt);
+ return -1;
++ }
+ clean = merge_trees_internal(opt, head, merge, merge_base, &ignored);
+ merge_finalize(opt);
+
+@@ merge-recursive.c: int merge_recursive(struct merge_options *opt,
+ prepare_repo_settings(opt->repo);
+ opt->repo->settings.command_requires_full_index = 1;
+
+- if (merge_start(opt, repo_get_commit_tree(opt->repo, h1)))
++ if (merge_start(opt, repo_get_commit_tree(opt->repo, h1))) {
++ free_commit_list(merge_bases);
++ merge_finalize(opt);
+ return -1;
++ }
+ clean = merge_recursive_internal(opt, h1, h2, merge_bases, result);
+ merge_finalize(opt);
+ -- gitgitgadget