This series fixes various widespread leaks in the sequencer and its
users (rebase, revert, cherry-pick). As a result 18 tests become
leak-free in their entirety.
See the v1 for a longer general summary:
https://lore.kernel.org/git/cover-00.10-00000000000-20221230T071741Z-avarab@xxxxxxxxx/
Changes since v1:
* I think this addresses all the outstanding feedback, thanks all.
* Most significantly, the replay_opts_release() is now moved out of
sequencer_remove_state() as suggested.
* There's a prep change for renaming "squash_onto_name", per the
discussion in v1.
* Just do "goto leave" rather than being paranoid and introdungi
"goto cleanup", thanks Phillip!
* Various other small changes, see the range-diff.
A branch & passing CI for this are at:
https://github.com/avar/git/tree/avar/leak-fixes-sequencer-rebase-2
Ævar Arnfjörð Bjarmason (9):
rebase: use "cleanup" pattern in do_interactive_rebase()
sequencer.c: split up sequencer_remove_state()
rebase & sequencer API: fix get_replay_opts() leak in "rebase"
builtin/revert.c: move free-ing of "revs" to replay_opts_release()
builtin/rebase.c: rename "squash_onto_name" to "to_free"
builtin/rebase.c: fix "options.onto_name" leak
sequencer.c: always free() the "msgbuf" in do_pick_commit()
builtin/rebase.c: free() "options.strategy_opts"
commit.c: free() revs.commit in get_fork_point()
builtin/rebase.c | 27 +++++++++-------
builtin/revert.c | 8 ++---
commit.c | 1 +
sequencer.c | 43 ++++++++++++++++----------
sequencer.h | 1 +
t/t3405-rebase-malformed.sh | 1 +
t/t3412-rebase-root.sh | 1 +
t/t3416-rebase-onto-threedots.sh | 1 +
t/t3419-rebase-patch-id.sh | 1 +
t/t3423-rebase-reword.sh | 1 +
t/t3425-rebase-topology-merges.sh | 2 ++
t/t3431-rebase-fork-point.sh | 1 +
t/t3432-rebase-fast-forward.sh | 1 +
t/t3437-rebase-fixup-options.sh | 1 +
t/t3438-rebase-broken-files.sh | 2 ++
t/t3501-revert-cherry-pick.sh | 1 +
t/t3502-cherry-pick-merge.sh | 1 +
t/t3503-cherry-pick-root.sh | 1 +
t/t3506-cherry-pick-ff.sh | 1 +
t/t3511-cherry-pick-x.sh | 1 +
t/t7402-submodule-rebase.sh | 1 +
t/t9106-git-svn-commit-diff-clobber.sh | 1 -
t/t9164-git-svn-dcommit-concurrent.sh | 1 -
23 files changed, 64 insertions(+), 36 deletions(-)
Range-diff against v1:
1: f3a4ed79c7d ! 1: d0a0524f3d4 rebase: use "cleanup" pattern in do_interactive_rebase()
@@ Commit message
rebase: use "cleanup" pattern in do_interactive_rebase()
Use a "goto cleanup" pattern in do_interactive_rebase(). This
- eliminates some duplicated free() code added in 0609b741a43 (rebase
- -i: combine rebase--interactive.c with rebase.c, 2019-04-17), and sets
- us up for a subsequent commit which'll make further use of the
- "cleanup" label.
+ eliminates some duplicated free() code added in 53bbcfbde7c (rebase
+ -i: implement the main part of interactive rebase as a builtin,
+ 2018-09-27), and sets us up for a subsequent commit which'll make
+ further use of the "cleanup" label.
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@xxxxxxxxx>
2: 4994940a0a9 ! 2: c4eaa8dfef4 sequencer.c: split up sequencer_remove_state()
@@ Commit message
function, which will be adjusted and called independent of the other
code in sequencer_remove_state() in a subsequent commit.
- The only functional changes here are:
-
- * Changing the "int" to a "size_t", which is the correct type, as
- "xopts_nr" is a "size_t".
-
- * Calling the free() before the "if (is_rebase_i(opts) && ...)",
- which is OK, and makes a subsequent change smaller.
+ The only functional change here is changing the "int" to a "size_t",
+ which is the correct type, as "xopts_nr" is a "size_t".
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@xxxxxxxxx>
@@ sequencer.c: static const char *gpg_sign_opt_quoted(struct replay_opts *opts)
struct strbuf buf = STRBUF_INIT;
- int i, ret = 0;
+ int ret = 0;
-+
-+ replay_opts_release(opts);
if (is_rebase_i(opts) &&
strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) {
@@ sequencer.c: int sequencer_remove_state(struct replay_opts *opts)
- free(opts->xopts[i]);
- free(opts->xopts);
- strbuf_release(&opts->current_fixups);
--
++ replay_opts_release(opts);
+
strbuf_reset(&buf);
strbuf_addstr(&buf, get_dir(opts));
- if (remove_dir_recursively(&buf, 0))
3: 3e9c4df61fe ! 3: f06f565ceaf rebase & sequencer API: fix get_replay_opts() leak in "rebase"
@@ builtin/rebase.c: static int run_sequencer_rebase(struct rebase_options *opts)
break;
}
case ACTION_EDIT_TODO:
+@@ builtin/rebase.c: static int finish_rebase(struct rebase_options *opts)
+
+ replay.action = REPLAY_INTERACTIVE_REBASE;
+ ret = sequencer_remove_state(&replay);
++ replay_opts_release(&replay);
+ } else {
+ strbuf_addstr(&dir, opts->state_dir);
+ if (remove_dir_recursively(&dir, 0))
+@@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)
+
+ replay.action = REPLAY_INTERACTIVE_REBASE;
+ ret = sequencer_remove_state(&replay);
++ replay_opts_release(&replay);
+ } else {
+ strbuf_reset(&buf);
+ strbuf_addstr(&buf, options.state_dir);
+
+ ## builtin/revert.c ##
+@@ builtin/revert.c: int cmd_revert(int argc, const char **argv, const char *prefix)
+ if (opts.revs)
+ release_revisions(opts.revs);
+ free(opts.revs);
++ replay_opts_release(&opts);
+ return res;
+ }
+
+@@ builtin/revert.c: int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
+ free(opts.revs);
+ if (res < 0)
+ die(_("cherry-pick failed"));
++ replay_opts_release(&opts);
+ return res;
+ }
## sequencer.c ##
@@ sequencer.c: static const char *gpg_sign_opt_quoted(struct replay_opts *opts)
@@ sequencer.c: static const char *gpg_sign_opt_quoted(struct replay_opts *opts)
-static void replay_opts_release(struct replay_opts *opts)
+void replay_opts_release(struct replay_opts *opts)
{
-- free(opts->gpg_sign);
-- free(opts->reflog_action);
-- free(opts->default_strategy);
-- free(opts->strategy);
-+ FREE_AND_NULL(opts->gpg_sign);
-+ FREE_AND_NULL(opts->reflog_action);
-+ FREE_AND_NULL(opts->default_strategy);
-+ FREE_AND_NULL(opts->strategy);
+ free(opts->gpg_sign);
+ free(opts->reflog_action);
+@@ sequencer.c: static void replay_opts_release(struct replay_opts *opts)
+ free(opts->strategy);
for (size_t i = 0; i < opts->xopts_nr; i++)
free(opts->xopts[i]);
-- free(opts->xopts);
+ opts->xopts_nr = 0;
-+ FREE_AND_NULL(opts->xopts);
+ free(opts->xopts);
strbuf_release(&opts->current_fixups);
}
+@@ sequencer.c: int sequencer_remove_state(struct replay_opts *opts)
+ }
+ }
+- replay_opts_release(opts);
+-
+ strbuf_reset(&buf);
+ strbuf_addstr(&buf, get_dir(opts));
+ if (remove_dir_recursively(&buf, 0))
## sequencer.h ##
@@ sequencer.h: int sequencer_pick_revisions(struct repository *repo,
@@ t/t3412-rebase-root.sh: Tests if git rebase --root --onto <newparent> can rebase
log_with_names () {
+ ## t/t3419-rebase-patch-id.sh ##
+@@ t/t3419-rebase-patch-id.sh: test_description='git rebase - test patch id computation'
+ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
++TEST_PASSES_SANITIZE_LEAK=true
+ . ./test-lib.sh
+
+ scramble () {
+
## t/t3423-rebase-reword.sh ##
@@
@@ t/t3423-rebase-reword.sh
. "$TEST_DIRECTORY"/lib-rebase.sh
+ ## t/t3425-rebase-topology-merges.sh ##
+@@
+ #!/bin/sh
+
+ test_description='rebase topology tests with merges'
++
++TEST_PASSES_SANITIZE_LEAK=true
+ . ./test-lib.sh
+ . "$TEST_DIRECTORY"/lib-rebase.sh
+
+
## t/t3437-rebase-fixup-options.sh ##
@@ t/t3437-rebase-fixup-options.sh: to the "fixup" command that works with "fixup!", "fixup -C" works with
"amend!" upon --autosquash.
@@ t/t3438-rebase-broken-files.sh
test_expect_success 'set up conflicting branches' '
+ ## t/t3501-revert-cherry-pick.sh ##
+@@ t/t3501-revert-cherry-pick.sh: test_description='test cherry-pick and revert with renames
+ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
++TEST_PASSES_SANITIZE_LEAK=true
+ . ./test-lib.sh
+
+ test_expect_success setup '
+
+ ## t/t3502-cherry-pick-merge.sh ##
+@@ t/t3502-cherry-pick-merge.sh: test_description='cherry picking and reverting a merge
+ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
++TEST_PASSES_SANITIZE_LEAK=true
+ . ./test-lib.sh
+
+ test_expect_success setup '
+
+ ## t/t3503-cherry-pick-root.sh ##
+@@ t/t3503-cherry-pick-root.sh: test_description='test cherry-picking (and reverting) a root commit'
+ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
++TEST_PASSES_SANITIZE_LEAK=true
+ . ./test-lib.sh
+
+ test_expect_success setup '
+
+ ## t/t3506-cherry-pick-ff.sh ##
+@@ t/t3506-cherry-pick-ff.sh: test_description='test cherry-picking with --ff option'
+ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
++TEST_PASSES_SANITIZE_LEAK=true
+ . ./test-lib.sh
+
+ test_expect_success setup '
+
+ ## t/t3511-cherry-pick-x.sh ##
+@@
+
+ test_description='Test cherry-pick -x and -s'
+
++TEST_PASSES_SANITIZE_LEAK=true
+ . ./test-lib.sh
+
+ pristine_detach () {
+
## t/t7402-submodule-rebase.sh ##
@@
4: 1e4e504c533 < -: ----------- builtin/revert.c: refactor run_sequencer() return pattern
5: e2895bb9795 < -: ----------- builtin/revert.c: fix common leak by using replay_opts_release()
6: 21eea8eb802 ! 4: e83bdfab046 builtin/revert.c: move free-ing of "revs" to replay_opts_release()
@@ builtin/revert.c: int cmd_revert(int argc, const char **argv, const char *prefix
- if (opts.revs)
- release_revisions(opts.revs);
- free(opts.revs);
-+ replay_opts_release(&opts);
+ replay_opts_release(&opts);
return res;
}
-
@@ builtin/revert.c: int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
opts.action = REPLAY_PICK;
sequencer_init_config(&opts);
@@ builtin/revert.c: int cmd_cherry_pick(int argc, const char **argv, const char *p
- if (opts.revs)
- release_revisions(opts.revs);
- free(opts.revs);
-+ replay_opts_release(&opts);
if (res < 0)
die(_("cherry-pick failed"));
- return res;
+ replay_opts_release(&opts);
## sequencer.c ##
@@ sequencer.c: void replay_opts_release(struct replay_opts *opts)
opts->xopts_nr = 0;
- FREE_AND_NULL(opts->xopts);
+ free(opts->xopts);
strbuf_release(&opts->current_fixups);
+ if (opts->revs)
+ release_revisions(opts->revs);
-+ FREE_AND_NULL(opts->revs);
++ free(opts->revs);
}
int sequencer_remove_state(struct replay_opts *opts)
-: ----------- > 5: 4fea2b77c6d builtin/rebase.c: rename "squash_onto_name" to "to_free"
7: 484ebbfd6d1 ! 6: 898bb7698fc builtin/rebase.c: fix "options.onto_name" leak
@@ Commit message
In [1] we started saving away the earlier xstrdup()'d
"options.onto_name" assignment to free() it, but when [2] added this
- "keep_base" branch it didn't free() the already assigned
- "squash_onto_name" before re-assigning to "options.onto_name". Let's
- do that, and fix the memory leak.
+ "keep_base" branch it didn't free() the already assigned value before
+ re-assigning to "options.onto_name". Let's do that, and fix the memory
+ leak.
1. 9dba809a69a (builtin rebase: support --root, 2018-09-04)
2. 414d924beb4 (rebase: teach rebase --keep-base, 2019-08-27)
@@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix
strbuf_addstr(&buf, "...");
strbuf_addstr(&buf, branch_name);
- options.onto_name = xstrdup(buf.buf);
-+ free(squash_onto_name);
-+ options.onto_name = squash_onto_name = xstrdup(buf.buf);
++ free(to_free);
++ options.onto_name = to_free = xstrdup(buf.buf);
} else if (!options.onto_name)
options.onto_name = options.upstream_name;
if (strstr(options.onto_name, "...")) {
8: d607dbac38e ! 7: fb38dc873f9 sequencer.c: always free() the "msgbuf" in do_pick_commit()
@@ Commit message
we'd return before the strbuf_release(&msgbuf).
Then when the "fixup" support was added in [3] this leak got worse, as
- we added another place where we'd "return" before reaching the
- strbuf_release().
+ in this error case we added another place where we'd "return" before
+ reaching the strbuf_release().
- Let's move it to a "cleanup" label, and use an appropriate "goto". It
- may or may not be safe to combine the existing "leave" and "cleanup"
- labels, but this change doesn't attempt to answer that question. Let's
- instead avoid calling update_abort_safety_file() in these cases, as we
- didn't do so before.
+ This changes the behavior so that we'll call
+ update_abort_safety_file() in these cases where we'd previously
+ "return", but as noted in [4] "update_abort_safety_file() is a no-op
+ when rebasing and you're changing code that is only run when
+ rebasing.". Here "no-op" refers to the early return in
+ update_abort_safety_file() if git_path_seq_dir() doesn't exist.
1. 452202c74b8 (sequencer: stop releasing the strbuf in
write_message(), 2016-10-21)
@@ Commit message
2016-07-26)
3. 6e98de72c03 (sequencer (rebase -i): add support for the 'fixup' and
'squash' commands, 2017-01-02)
+ 4. https://lore.kernel.org/git/bcace50b-a4c3-c468-94a3-4fe0c62b3671@xxxxxxxxxxxxx/
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@xxxxxxxxx>
@@ sequencer.c: static int do_pick_commit(struct repository *r,
- return -1;
+ opts, item->flags)) {
+ res = -1;
-+ goto cleanup;
++ goto leave;
+ }
flags |= AMEND_MSG;
if (!final_fixup)
@@ sequencer.c: static int do_pick_commit(struct repository *r,
+ if (copy_file(dest, rebase_path_squash_msg(), 0666)) {
+ res = error(_("could not rename '%s' to '%s'"),
+ rebase_path_squash_msg(), dest);
-+ goto cleanup;
++ goto leave;
+ }
unlink(git_path_merge_msg(r));
msg_file = dest;
@@ sequencer.c: static int do_pick_commit(struct repository *r,
/*
* If the merge was clean or if it failed due to conflict, we write
@@ sequencer.c: static int do_pick_commit(struct repository *r,
- }
-
leave:
-+ update_abort_safety_file();
-+cleanup:
free_message(commit, &msg);
free(author);
-- update_abort_safety_file();
+ strbuf_release(&msgbuf);
+ update_abort_safety_file();
return res;
- }
9: cd0489a2384 ! 8: d4b0e2a5c83 builtin/rebase.c: free() "options.strategy_opts"
@@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix
free(options.strategy);
+ free(options.strategy_opts);
strbuf_release(&options.git_format_patch_opt);
- free(squash_onto_name);
+ free(to_free);
string_list_clear(&exec, 0);
10: eb3678b4667 = 9: fd9c7a5547c commit.c: free() revs.commit in get_fork_point()