1
0
mirror of https://github.com/git/git synced 2024-07-02 15:48:44 +00:00

commit: fix leaking parents when calling commit_tree_extended()

When creating commits via `commit_tree_extended()`, the caller passes in
a string list of parents. This call implicitly transfers ownership of
that list to the function, which is quite surprising to begin with. But
to make matters worse, `commit_tree_extended()` doesn't even bother to
free the list of parents in error cases. The result is a memory leak,
and one that the caller cannot fix by themselves because they do not
know whether parts of the string list have already been released.

Refactor the code such that callers can keep ownership of the list of
parents, which is getting indicated by parameter being a constant
pointer now. Free the lists at the calling site and add a common exit
path to those sites as required.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Patrick Steinhardt 2024-06-11 11:20:42 +02:00 committed by Junio C Hamano
parent c6eb58bfb1
commit 63c9bd372e
16 changed files with 59 additions and 37 deletions

View File

@ -1718,6 +1718,7 @@ static void do_commit(const struct am_state *state)
run_hooks("post-applypatch");
free_commit_list(parents);
strbuf_release(&sb);
}

View File

@ -111,6 +111,7 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
OPT_END()
};
int ret;
git_config(git_default_config, NULL);
@ -132,11 +133,15 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
if (commit_tree(buffer.buf, buffer.len, &tree_oid, parents, &commit_oid,
NULL, sign_commit)) {
strbuf_release(&buffer);
return 1;
ret = 1;
goto out;
}
printf("%s\n", oid_to_hex(&commit_oid));
ret = 0;
out:
free_commit_list(parents);
strbuf_release(&buffer);
return 0;
return ret;
}

View File

@ -1848,7 +1848,6 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
rollback_index_files();
die(_("failed to write commit object"));
}
free_commit_extra_headers(extra);
if (update_head_with_reflog(current_head, &oid, reflog_msg, &sb,
&err)) {
@ -1890,6 +1889,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
apply_autostash_ref(the_repository, "MERGE_AUTOSTASH");
cleanup:
free_commit_extra_headers(extra);
free_commit_list(parents);
strbuf_release(&author_ident);
strbuf_release(&err);
strbuf_release(&sb);

View File

@ -895,7 +895,7 @@ static void prepare_to_commit(struct commit_list *remoteheads)
static int merge_trivial(struct commit *head, struct commit_list *remoteheads)
{
struct object_id result_tree, result_commit;
struct commit_list *parents, **pptr = &parents;
struct commit_list *parents = NULL, **pptr = &parents;
if (repo_refresh_and_write_index(the_repository, REFRESH_QUIET,
SKIP_IF_UNCHANGED, 0, NULL, NULL,
@ -911,7 +911,9 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads)
&result_commit, NULL, sign_commit))
die(_("failed to write commit object"));
finish(head, remoteheads, &result_commit, "In-index merge");
remove_merge_branch_state(the_repository);
free_commit_list(parents);
return 0;
}
@ -937,8 +939,10 @@ static int finish_automerge(struct commit *head,
die(_("failed to write commit object"));
strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
finish(head, remoteheads, &result_commit, buf.buf);
strbuf_release(&buf);
remove_merge_branch_state(the_repository);
free_commit_list(parents);
return 0;
}

View File

@ -52,11 +52,11 @@ static struct commit *create_commit(struct tree *tree,
struct commit *parent)
{
struct object_id ret;
struct object *obj;
struct object *obj = NULL;
struct commit_list *parents = NULL;
char *author;
char *sign_commit = NULL; /* FIXME: cli users might want to sign again */
struct commit_extra_header *extra;
struct commit_extra_header *extra = NULL;
struct strbuf msg = STRBUF_INIT;
const char *out_enc = get_commit_output_encoding();
const char *message = repo_logmsg_reencode(the_repository, based_on,
@ -73,12 +73,16 @@ static struct commit *create_commit(struct tree *tree,
if (commit_tree_extended(msg.buf, msg.len, &tree->object.oid, parents,
&ret, author, NULL, sign_commit, extra)) {
error(_("failed to write commit object"));
return NULL;
goto out;
}
free(author);
strbuf_release(&msg);
obj = parse_object(the_repository, &ret);
out:
free_commit_extra_headers(extra);
free_commit_list(parents);
strbuf_release(&msg);
free(author);
return (struct commit *)obj;
}

View File

@ -1416,6 +1416,9 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b
goto done;
}
free_commit_list(parents);
parents = NULL;
if (include_untracked) {
if (save_untracked_files(info, &msg, untracked_files)) {
if (!quiet)
@ -1461,11 +1464,6 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b
else
strbuf_insertf(stash_msg_buf, 0, "On %s: ", branch_name);
/*
* `parents` will be empty after calling `commit_tree()`, so there is
* no need to call `free_commit_list()`
*/
parents = NULL;
if (untracked_commit_option)
commit_list_insert(lookup_commit(the_repository,
&info->u_commit),
@ -1487,6 +1485,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b
strbuf_release(&commit_tree_label);
strbuf_release(&msg);
strbuf_release(&untracked_files);
free_commit_list(parents);
return ret;
}

View File

@ -1262,7 +1262,7 @@ int remove_signature(struct strbuf *buf)
return sigs[0].start != NULL;
}
static void handle_signed_tag(struct commit *parent, struct commit_extra_header ***tail)
static void handle_signed_tag(const struct commit *parent, struct commit_extra_header ***tail)
{
struct merge_remote_desc *desc;
struct commit_extra_header *mergetag;
@ -1359,17 +1359,17 @@ void verify_merge_signature(struct commit *commit, int verbosity,
signature_check_clear(&signature_check);
}
void append_merge_tag_headers(struct commit_list *parents,
void append_merge_tag_headers(const struct commit_list *parents,
struct commit_extra_header ***tail)
{
while (parents) {
struct commit *parent = parents->item;
const struct commit *parent = parents->item;
handle_signed_tag(parent, tail);
parents = parents->next;
}
}
static int convert_commit_extra_headers(struct commit_extra_header *orig,
static int convert_commit_extra_headers(const struct commit_extra_header *orig,
struct commit_extra_header **result)
{
const struct git_hash_algo *compat = the_repository->compat_hash_algo;
@ -1403,7 +1403,7 @@ static int convert_commit_extra_headers(struct commit_extra_header *orig,
}
static void add_extra_header(struct strbuf *buffer,
struct commit_extra_header *extra)
const struct commit_extra_header *extra)
{
strbuf_addstr(buffer, extra->key);
if (extra->len)
@ -1517,7 +1517,7 @@ void free_commit_extra_headers(struct commit_extra_header *extra)
}
int commit_tree(const char *msg, size_t msg_len, const struct object_id *tree,
struct commit_list *parents, struct object_id *ret,
const struct commit_list *parents, struct object_id *ret,
const char *author, const char *sign_commit)
{
struct commit_extra_header *extra = NULL, **tail = &extra;
@ -1649,7 +1649,7 @@ static void write_commit_tree(struct strbuf *buffer, const char *msg, size_t msg
const struct object_id *tree,
const struct object_id *parents, size_t parents_len,
const char *author, const char *committer,
struct commit_extra_header *extra)
const struct commit_extra_header *extra)
{
int encoding_is_utf8;
size_t i;
@ -1690,10 +1690,10 @@ static void write_commit_tree(struct strbuf *buffer, const char *msg, size_t msg
int commit_tree_extended(const char *msg, size_t msg_len,
const struct object_id *tree,
struct commit_list *parents, struct object_id *ret,
const struct commit_list *parents, struct object_id *ret,
const char *author, const char *committer,
const char *sign_commit,
struct commit_extra_header *extra)
const struct commit_extra_header *extra)
{
struct repository *r = the_repository;
int result = 0;
@ -1715,10 +1715,8 @@ int commit_tree_extended(const char *msg, size_t msg_len,
nparents = commit_list_count(parents);
CALLOC_ARRAY(parent_buf, nparents);
i = 0;
while (parents) {
struct commit *parent = pop_commit(&parents);
oidcpy(&parent_buf[i++], &parent->object.oid);
}
for (const struct commit_list *p = parents; p; p = p->next)
oidcpy(&parent_buf[i++], &p->item->object.oid);
write_commit_tree(&buffer, msg, msg_len, tree, parent_buf, nparents, author, committer, extra);
if (sign_commit && sign_commit_to_strbuf(&sig, &buffer, sign_commit)) {
@ -1814,7 +1812,7 @@ int commit_tree_extended(const char *msg, size_t msg_len,
define_commit_slab(merge_desc_slab, struct merge_remote_desc *);
static struct merge_desc_slab merge_desc_slab = COMMIT_SLAB_INIT(1, merge_desc_slab);
struct merge_remote_desc *merge_remote_util(struct commit *commit)
struct merge_remote_desc *merge_remote_util(const struct commit *commit)
{
return *merge_desc_slab_at(&merge_desc_slab, commit);
}

View File

@ -260,19 +260,19 @@ struct commit_extra_header {
size_t len;
};
void append_merge_tag_headers(struct commit_list *parents,
void append_merge_tag_headers(const struct commit_list *parents,
struct commit_extra_header ***tail);
int commit_tree(const char *msg, size_t msg_len,
const struct object_id *tree,
struct commit_list *parents, struct object_id *ret,
const struct commit_list *parents, struct object_id *ret,
const char *author, const char *sign_commit);
int commit_tree_extended(const char *msg, size_t msg_len,
const struct object_id *tree,
struct commit_list *parents, struct object_id *ret,
const struct commit_list *parents, struct object_id *ret,
const char *author, const char *committer,
const char *sign_commit, struct commit_extra_header *);
const char *sign_commit, const struct commit_extra_header *);
struct commit_extra_header *read_commit_extra_headers(struct commit *, const char **);
@ -306,7 +306,7 @@ struct merge_remote_desc {
struct object *obj; /* the named object, could be a tag */
char name[FLEX_ARRAY];
};
struct merge_remote_desc *merge_remote_util(struct commit *);
struct merge_remote_desc *merge_remote_util(const struct commit *);
void set_merge_remote_desc(struct commit *commit,
const char *name, struct object *obj);

View File

@ -661,6 +661,7 @@ int notes_merge(struct notes_merge_options *o,
commit_list_insert(local, &parents);
create_notes_commit(o->repo, local_tree, parents, o->commit_msg.buf,
o->commit_msg.len, result_oid);
free_commit_list(parents);
}
found_result:

View File

@ -9,10 +9,11 @@
void create_notes_commit(struct repository *r,
struct notes_tree *t,
struct commit_list *parents,
const struct commit_list *parents,
const char *msg, size_t msg_len,
struct object_id *result_oid)
{
struct commit_list *parents_to_free = NULL;
struct object_id tree_oid;
assert(t->initialized);
@ -27,7 +28,8 @@ void create_notes_commit(struct repository *r,
struct commit *parent = lookup_commit(r, &parent_oid);
if (repo_parse_commit(r, parent))
die("Failed to find/parse commit %s", t->ref);
commit_list_insert(parent, &parents);
commit_list_insert(parent, &parents_to_free);
parents = parents_to_free;
}
/* else: t->ref points to nothing, assume root/orphan commit */
}
@ -35,6 +37,8 @@ void create_notes_commit(struct repository *r,
if (commit_tree(msg, msg_len, &tree_oid, parents, result_oid, NULL,
NULL))
die("Failed to commit notes tree to database");
free_commit_list(parents_to_free);
}
void commit_notes(struct repository *r, struct notes_tree *t, const char *msg)

View File

@ -20,7 +20,7 @@ struct repository;
*/
void create_notes_commit(struct repository *r,
struct notes_tree *t,
struct commit_list *parents,
const struct commit_list *parents,
const char *msg, size_t msg_len,
struct object_id *result_oid);

View File

@ -1711,6 +1711,7 @@ static int try_to_commit(struct repository *r,
out:
free_commit_extra_headers(extra);
free_commit_list(parents);
strbuf_release(&err);
strbuf_release(&commit_msg);
free(amend_author);

View File

@ -8,6 +8,7 @@ test_description='git rebase --merge --skip tests'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-rebase.sh

View File

@ -2,6 +2,7 @@
test_description='git rebase of commits that start or become empty'
TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup test repository' '

View File

@ -5,6 +5,7 @@ test_description='test cherry-picking an empty 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 '

View File

@ -5,6 +5,7 @@ test_description='prepare-commit-msg hook'
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 'set up commits for rebasing' '