Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix some old memory leaks in merge-ort and builtin/merge #1200

Closed
wants to merge 2 commits into from

Conversation

newren
Copy link
Contributor

@newren newren commented Jan 20, 2022

This series fixes three memory leaks.

The first comes from a report at [1]. It's a leak in merge-ort that pre-dates the remerge-diff topic (technically traceable back 15.5 years ago across merge-recursive.c history) and is triggerable from a variety of testcases. I think I may have overlooked it previously just because there's other leaks in revision walking and this looks like one of those.

The next two leaks were ones in builtin/merge.c found while fixing the above leak; they are fixed together in the second patch.

[1] https://lore.kernel.org/git/220119.86pmonn2mb.gmgdl@evledraar.gmail.com/

cc: Ævar Arnfjörð Bjarmason avarab@gmail.com

@gitgitgadget-git
Copy link

There are issues in commit a341d7a:
merge-ort: fix memory leak in merge_ort_internal()
Lines in the body of the commit messages should be wrapped between 60 and 76 characters.

@gitgitgadget-git
Copy link

There are issues in commit 0f0cf35:
merge: fix memory leaks in cmd_merge()
Lines in the body of the commit messages should be wrapped between 60 and 76 characters.

@newren newren changed the title Fix some memory leaks in merge-ort and builtin/merge Fix some old memory leaks in merge-ort and builtin/merge Jan 20, 2022
The documentation for merge_incore_recursive(), modelled after
merge_recursive(), notes that

   merge_bases will be consumed (emptied) so make a copy if you need it

However, in merge_ort_internal() (which merge_incore_recursive() calls),
it runs

   merged_merge_bases = pop_commit(&merge_bases);
   ...
   for (iter = merge_bases; iter; iter = iter->next) {
      ...
   }

In other words, it only consumes the *first* entry of merge_bases, and
the rest it iterates through.  If it iterated through all of them, the
caller could be responsible for free'ing the memory.  If it consumed all
of them, the current documentation would be correct and the callers
would need to do nothing.  The current middle ground makes it impossible
for callers to avoid memory leaks, since any attempt to use the
merge_bases it passes in would result in a use-after-free.

It turns out this part of the code was copied from merge-recursive.c,
which has had the same bug for 15.5 years.  However, since we are trying
to keep merge-recursive.c stable as we sunset it, let's just fix the
leak in in merge_ort_internal() by having it actually consume all the
elements of the merge_bases commit_list.

Testing this commit against t6404 (the first testcase specifically
about recursive merges) under valgrind shows that this patch fixes
the following leak:

    32 (16 direct, 16 indirect) bytes in 1 blocks are definitely lost \
    in loss record 49 of 126
       at 0x484086F: malloc (vg_replace_malloc.c:380)
       by 0x69FFEB: do_xmalloc (wrapper.c:41)
       by 0x6A0073: xmalloc (wrapper.c:62)
       by 0x52A72D: commit_list_insert (commit.c:556)
       by 0x47EC86: try_merge_strategy (merge.c:751)
       by 0x48143B: cmd_merge (merge.c:1679)
       by 0x40686E: run_builtin (git.c:464)
       by 0x406C51: handle_builtin (git.c:716)
       by 0x406E96: run_argv (git.c:783)
       by 0x40730A: cmd_main (git.c:914)
       by 0x4E7DFA: main (common-main.c:56)

Reported-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Elijah Newren <newren@gmail.com>
There were two commit_lists created in cmd_merge() that were only
conditionally free()'d.  Add a quick conditional call to
free_commit_list() for each of them at the end of the function.

Testing this commit against t6404 under valgrind shows that this patch
fixes the following two leaks:

    16 bytes in 1 blocks are definitely lost in loss record 16 of 126
       at 0x484086F: malloc (vg_replace_malloc.c:380)
       by 0x69FFEB: do_xmalloc (wrapper.c:41)
       by 0x6A0073: xmalloc (wrapper.c:62)
       by 0x52A72D: commit_list_insert (commit.c:556)
       by 0x47FC93: reduce_parents (merge.c:1114)
       by 0x4801EE: collect_parents (merge.c:1214)
       by 0x480B56: cmd_merge (merge.c:1465)
       by 0x40686E: run_builtin (git.c:464)
       by 0x406C51: handle_builtin (git.c:716)
       by 0x406E96: run_argv (git.c:783)
       by 0x40730A: cmd_main (git.c:914)
       by 0x4E7DFA: main (common-main.c:56)

    8 (16 direct, 32 indirect) bytes in 1 blocks are definitely lost in \
    loss record 61 of 126
       at 0x484086F: malloc (vg_replace_malloc.c:380)
       by 0x69FFEB: do_xmalloc (wrapper.c:41)
       by 0x6A0073: xmalloc (wrapper.c:62)
       by 0x52A72D: commit_list_insert (commit.c:556)
       by 0x52A8F2: commit_list_insert_by_date (commit.c:620)
       by 0x5270AC: get_merge_bases_many_0 (commit-reach.c:413)
       by 0x52716C: repo_get_merge_bases (commit-reach.c:438)
       by 0x480E5A: cmd_merge (merge.c:1520)
       by 0x40686E: run_builtin (git.c:464)
       by 0x406C51: handle_builtin (git.c:716)
       by 0x406E96: run_argv (git.c:783)
       by 0x40730A: cmd_main (git.c:914)

There are still 3 leaks in chdir_notify_register() after this, but
chdir_notify_register() has been brought up on the list before and folks
were not a fan of fixing those, so I'm not touching them.

Signed-off-by: Elijah Newren <newren@gmail.com>
@newren
Copy link
Contributor Author

newren commented Jan 20, 2022

/submit

@gitgitgadget-git
Copy link

Submitted as pull.1200.git.git.1642664835.gitgitgadget@gmail.com

To fetch this version into FETCH_HEAD:

git fetch https://github.com/gitgitgadget/git/ pr-git-1200/newren/merge-leaks-v1

To fetch this version to local tag pr-git-1200/newren/merge-leaks-v1:

git fetch --no-tags https://github.com/gitgitgadget/git/ tag pr-git-1200/newren/merge-leaks-v1

@@ -1273,7 +1273,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the Git mailing list, Junio C Hamano wrote (reply to this):

"Elijah Newren via GitGitGadget" <gitgitgadget@gmail.com> writes:

>  done:
> +	if (!automerge_was_ok) {
> +		free_commit_list(common);
> +		free_commit_list(remoteheads);
> +	}

I wondered what happens upon a successful automerge.

We call finish_automerge() and come here, and I do see a call to
free_commit_list(common) in finish_automerge(), but the way
remoteheads is used looked a bit iffy.

In finish_automerge(), we use a temporary variable parents to point
remoteheads at it, and conditionally prepend the current HEAD at the
beginning of the parents list.  This is passed to commit_tree(),
which does pop_commit() to consume all commits on the list.

So after commit_tree() returns, all commit_list instances on
remoteheads list, and possibly the one finish_automerge() prepended
for the current HEAD, are all consumed, and there is no need to
call, and it would be wrong to call, free_commit_list(), at this
point.

So, I agree that this conditional freeing is correct.  It was just
it was a bit hard to see.

Thanks.


@gitgitgadget-git
Copy link

This branch is now known as en/plug-leaks-in-merge.

@gitgitgadget-git
Copy link

This patch series was integrated into seen via beb78a6.

@gitgitgadget-git
Copy link

There was a status update in the "New Topics" section about the branch en/plug-leaks-in-merge on the Git mailing list:

Leakfix.

Will merge to 'next'.
source: <pull.1200.git.git.1642664835.gitgitgadget@gmail.com>

@gitgitgadget-git
Copy link

This patch series was integrated into seen via d330798.

@gitgitgadget-git
Copy link

This patch series was integrated into seen via 7620cc5.

@gitgitgadget-git
Copy link

This patch series was integrated into seen via dc7e36e.

@gitgitgadget-git
Copy link

There was a status update in the "Cooking" section about the branch en/plug-leaks-in-merge on the Git mailing list:

Leakfix.

Will merge to 'next'.
source: <pull.1200.git.git.1642664835.gitgitgadget@gmail.com>

@gitgitgadget-git
Copy link

This patch series was integrated into seen via 722e1df.

@gitgitgadget-git
Copy link

This patch series was integrated into seen via 3faf353.

@gitgitgadget-git
Copy link

This patch series was integrated into next via 0cf6aa0.

@gitgitgadget-git
Copy link

This patch series was integrated into seen via 8e50a26.

@gitgitgadget-git
Copy link

This patch series was integrated into seen via 904138c.

@gitgitgadget-git
Copy link

This patch series was integrated into seen via e2a6438.

@gitgitgadget-git
Copy link

There was a status update in the "Cooking" section about the branch en/plug-leaks-in-merge on the Git mailing list:

Leakfix.

Will merge to 'master'.
source: <pull.1200.git.git.1642664835.gitgitgadget@gmail.com>

@gitgitgadget-git
Copy link

This patch series was integrated into seen via a1d47d4.

@gitgitgadget-git
Copy link

This patch series was integrated into seen via 38e0f0f.

@gitgitgadget-git
Copy link

This patch series was integrated into seen via 3aa8df7.

@gitgitgadget-git
Copy link

This patch series was integrated into seen via c70b5e7.

@gitgitgadget-git
Copy link

This patch series was integrated into next via c70b5e7.

@gitgitgadget-git
Copy link

This patch series was integrated into master via c70b5e7.

@gitgitgadget-git
Copy link

Closed via c70b5e7.

@gitgitgadget-git gitgitgadget-git bot closed this Feb 9, 2022
@newren newren deleted the merge-leaks branch February 13, 2022 05:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
1 participant