Git fork

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>

authored by

Patrick Steinhardt and committed by
Junio C Hamano
63c9bd37 c6eb58bf

+59 -37
+1
builtin/am.c
··· 1718 1718 1719 1719 run_hooks("post-applypatch"); 1720 1720 1721 + free_commit_list(parents); 1721 1722 strbuf_release(&sb); 1722 1723 } 1723 1724
+8 -3
builtin/commit-tree.c
··· 111 111 N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, 112 112 OPT_END() 113 113 }; 114 + int ret; 114 115 115 116 git_config(git_default_config, NULL); 116 117 ··· 132 133 133 134 if (commit_tree(buffer.buf, buffer.len, &tree_oid, parents, &commit_oid, 134 135 NULL, sign_commit)) { 135 - strbuf_release(&buffer); 136 - return 1; 136 + ret = 1; 137 + goto out; 137 138 } 138 139 139 140 printf("%s\n", oid_to_hex(&commit_oid)); 141 + ret = 0; 142 + 143 + out: 144 + free_commit_list(parents); 140 145 strbuf_release(&buffer); 141 - return 0; 146 + return ret; 142 147 }
+2 -1
builtin/commit.c
··· 1848 1848 rollback_index_files(); 1849 1849 die(_("failed to write commit object")); 1850 1850 } 1851 - free_commit_extra_headers(extra); 1852 1851 1853 1852 if (update_head_with_reflog(current_head, &oid, reflog_msg, &sb, 1854 1853 &err)) { ··· 1890 1889 apply_autostash_ref(the_repository, "MERGE_AUTOSTASH"); 1891 1890 1892 1891 cleanup: 1892 + free_commit_extra_headers(extra); 1893 + free_commit_list(parents); 1893 1894 strbuf_release(&author_ident); 1894 1895 strbuf_release(&err); 1895 1896 strbuf_release(&sb);
+5 -1
builtin/merge.c
··· 895 895 static int merge_trivial(struct commit *head, struct commit_list *remoteheads) 896 896 { 897 897 struct object_id result_tree, result_commit; 898 - struct commit_list *parents, **pptr = &parents; 898 + struct commit_list *parents = NULL, **pptr = &parents; 899 899 900 900 if (repo_refresh_and_write_index(the_repository, REFRESH_QUIET, 901 901 SKIP_IF_UNCHANGED, 0, NULL, NULL, ··· 911 911 &result_commit, NULL, sign_commit)) 912 912 die(_("failed to write commit object")); 913 913 finish(head, remoteheads, &result_commit, "In-index merge"); 914 + 914 915 remove_merge_branch_state(the_repository); 916 + free_commit_list(parents); 915 917 return 0; 916 918 } 917 919 ··· 937 939 die(_("failed to write commit object")); 938 940 strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy); 939 941 finish(head, remoteheads, &result_commit, buf.buf); 942 + 940 943 strbuf_release(&buf); 941 944 remove_merge_branch_state(the_repository); 945 + free_commit_list(parents); 942 946 return 0; 943 947 } 944 948
+9 -5
builtin/replay.c
··· 52 52 struct commit *parent) 53 53 { 54 54 struct object_id ret; 55 - struct object *obj; 55 + struct object *obj = NULL; 56 56 struct commit_list *parents = NULL; 57 57 char *author; 58 58 char *sign_commit = NULL; /* FIXME: cli users might want to sign again */ 59 - struct commit_extra_header *extra; 59 + struct commit_extra_header *extra = NULL; 60 60 struct strbuf msg = STRBUF_INIT; 61 61 const char *out_enc = get_commit_output_encoding(); 62 62 const char *message = repo_logmsg_reencode(the_repository, based_on, ··· 73 73 if (commit_tree_extended(msg.buf, msg.len, &tree->object.oid, parents, 74 74 &ret, author, NULL, sign_commit, extra)) { 75 75 error(_("failed to write commit object")); 76 - return NULL; 76 + goto out; 77 77 } 78 - free(author); 79 - strbuf_release(&msg); 80 78 81 79 obj = parse_object(the_repository, &ret); 80 + 81 + out: 82 + free_commit_extra_headers(extra); 83 + free_commit_list(parents); 84 + strbuf_release(&msg); 85 + free(author); 82 86 return (struct commit *)obj; 83 87 } 84 88
+4 -5
builtin/stash.c
··· 1416 1416 goto done; 1417 1417 } 1418 1418 1419 + free_commit_list(parents); 1420 + parents = NULL; 1421 + 1419 1422 if (include_untracked) { 1420 1423 if (save_untracked_files(info, &msg, untracked_files)) { 1421 1424 if (!quiet) ··· 1461 1464 else 1462 1465 strbuf_insertf(stash_msg_buf, 0, "On %s: ", branch_name); 1463 1466 1464 - /* 1465 - * `parents` will be empty after calling `commit_tree()`, so there is 1466 - * no need to call `free_commit_list()` 1467 - */ 1468 - parents = NULL; 1469 1467 if (untracked_commit_option) 1470 1468 commit_list_insert(lookup_commit(the_repository, 1471 1469 &info->u_commit), ··· 1487 1485 strbuf_release(&commit_tree_label); 1488 1486 strbuf_release(&msg); 1489 1487 strbuf_release(&untracked_files); 1488 + free_commit_list(parents); 1490 1489 return ret; 1491 1490 } 1492 1491
+12 -14
commit.c
··· 1262 1262 return sigs[0].start != NULL; 1263 1263 } 1264 1264 1265 - static void handle_signed_tag(struct commit *parent, struct commit_extra_header ***tail) 1265 + static void handle_signed_tag(const struct commit *parent, struct commit_extra_header ***tail) 1266 1266 { 1267 1267 struct merge_remote_desc *desc; 1268 1268 struct commit_extra_header *mergetag; ··· 1359 1359 signature_check_clear(&signature_check); 1360 1360 } 1361 1361 1362 - void append_merge_tag_headers(struct commit_list *parents, 1362 + void append_merge_tag_headers(const struct commit_list *parents, 1363 1363 struct commit_extra_header ***tail) 1364 1364 { 1365 1365 while (parents) { 1366 - struct commit *parent = parents->item; 1366 + const struct commit *parent = parents->item; 1367 1367 handle_signed_tag(parent, tail); 1368 1368 parents = parents->next; 1369 1369 } 1370 1370 } 1371 1371 1372 - static int convert_commit_extra_headers(struct commit_extra_header *orig, 1372 + static int convert_commit_extra_headers(const struct commit_extra_header *orig, 1373 1373 struct commit_extra_header **result) 1374 1374 { 1375 1375 const struct git_hash_algo *compat = the_repository->compat_hash_algo; ··· 1403 1403 } 1404 1404 1405 1405 static void add_extra_header(struct strbuf *buffer, 1406 - struct commit_extra_header *extra) 1406 + const struct commit_extra_header *extra) 1407 1407 { 1408 1408 strbuf_addstr(buffer, extra->key); 1409 1409 if (extra->len) ··· 1517 1517 } 1518 1518 1519 1519 int commit_tree(const char *msg, size_t msg_len, const struct object_id *tree, 1520 - struct commit_list *parents, struct object_id *ret, 1520 + const struct commit_list *parents, struct object_id *ret, 1521 1521 const char *author, const char *sign_commit) 1522 1522 { 1523 1523 struct commit_extra_header *extra = NULL, **tail = &extra; ··· 1649 1649 const struct object_id *tree, 1650 1650 const struct object_id *parents, size_t parents_len, 1651 1651 const char *author, const char *committer, 1652 - struct commit_extra_header *extra) 1652 + const struct commit_extra_header *extra) 1653 1653 { 1654 1654 int encoding_is_utf8; 1655 1655 size_t i; ··· 1690 1690 1691 1691 int commit_tree_extended(const char *msg, size_t msg_len, 1692 1692 const struct object_id *tree, 1693 - struct commit_list *parents, struct object_id *ret, 1693 + const struct commit_list *parents, struct object_id *ret, 1694 1694 const char *author, const char *committer, 1695 1695 const char *sign_commit, 1696 - struct commit_extra_header *extra) 1696 + const struct commit_extra_header *extra) 1697 1697 { 1698 1698 struct repository *r = the_repository; 1699 1699 int result = 0; ··· 1715 1715 nparents = commit_list_count(parents); 1716 1716 CALLOC_ARRAY(parent_buf, nparents); 1717 1717 i = 0; 1718 - while (parents) { 1719 - struct commit *parent = pop_commit(&parents); 1720 - oidcpy(&parent_buf[i++], &parent->object.oid); 1721 - } 1718 + for (const struct commit_list *p = parents; p; p = p->next) 1719 + oidcpy(&parent_buf[i++], &p->item->object.oid); 1722 1720 1723 1721 write_commit_tree(&buffer, msg, msg_len, tree, parent_buf, nparents, author, committer, extra); 1724 1722 if (sign_commit && sign_commit_to_strbuf(&sig, &buffer, sign_commit)) { ··· 1814 1812 define_commit_slab(merge_desc_slab, struct merge_remote_desc *); 1815 1813 static struct merge_desc_slab merge_desc_slab = COMMIT_SLAB_INIT(1, merge_desc_slab); 1816 1814 1817 - struct merge_remote_desc *merge_remote_util(struct commit *commit) 1815 + struct merge_remote_desc *merge_remote_util(const struct commit *commit) 1818 1816 { 1819 1817 return *merge_desc_slab_at(&merge_desc_slab, commit); 1820 1818 }
+5 -5
commit.h
··· 260 260 size_t len; 261 261 }; 262 262 263 - void append_merge_tag_headers(struct commit_list *parents, 263 + void append_merge_tag_headers(const struct commit_list *parents, 264 264 struct commit_extra_header ***tail); 265 265 266 266 int commit_tree(const char *msg, size_t msg_len, 267 267 const struct object_id *tree, 268 - struct commit_list *parents, struct object_id *ret, 268 + const struct commit_list *parents, struct object_id *ret, 269 269 const char *author, const char *sign_commit); 270 270 271 271 int commit_tree_extended(const char *msg, size_t msg_len, 272 272 const struct object_id *tree, 273 - struct commit_list *parents, struct object_id *ret, 273 + const struct commit_list *parents, struct object_id *ret, 274 274 const char *author, const char *committer, 275 - const char *sign_commit, struct commit_extra_header *); 275 + const char *sign_commit, const struct commit_extra_header *); 276 276 277 277 struct commit_extra_header *read_commit_extra_headers(struct commit *, const char **); 278 278 ··· 306 306 struct object *obj; /* the named object, could be a tag */ 307 307 char name[FLEX_ARRAY]; 308 308 }; 309 - struct merge_remote_desc *merge_remote_util(struct commit *); 309 + struct merge_remote_desc *merge_remote_util(const struct commit *); 310 310 void set_merge_remote_desc(struct commit *commit, 311 311 const char *name, struct object *obj); 312 312
+1
notes-merge.c
··· 661 661 commit_list_insert(local, &parents); 662 662 create_notes_commit(o->repo, local_tree, parents, o->commit_msg.buf, 663 663 o->commit_msg.len, result_oid); 664 + free_commit_list(parents); 664 665 } 665 666 666 667 found_result:
+6 -2
notes-utils.c
··· 9 9 10 10 void create_notes_commit(struct repository *r, 11 11 struct notes_tree *t, 12 - struct commit_list *parents, 12 + const struct commit_list *parents, 13 13 const char *msg, size_t msg_len, 14 14 struct object_id *result_oid) 15 15 { 16 + struct commit_list *parents_to_free = NULL; 16 17 struct object_id tree_oid; 17 18 18 19 assert(t->initialized); ··· 27 28 struct commit *parent = lookup_commit(r, &parent_oid); 28 29 if (repo_parse_commit(r, parent)) 29 30 die("Failed to find/parse commit %s", t->ref); 30 - commit_list_insert(parent, &parents); 31 + commit_list_insert(parent, &parents_to_free); 32 + parents = parents_to_free; 31 33 } 32 34 /* else: t->ref points to nothing, assume root/orphan commit */ 33 35 } ··· 35 37 if (commit_tree(msg, msg_len, &tree_oid, parents, result_oid, NULL, 36 38 NULL)) 37 39 die("Failed to commit notes tree to database"); 40 + 41 + free_commit_list(parents_to_free); 38 42 } 39 43 40 44 void commit_notes(struct repository *r, struct notes_tree *t, const char *msg)
+1 -1
notes-utils.h
··· 20 20 */ 21 21 void create_notes_commit(struct repository *r, 22 22 struct notes_tree *t, 23 - struct commit_list *parents, 23 + const struct commit_list *parents, 24 24 const char *msg, size_t msg_len, 25 25 struct object_id *result_oid); 26 26
+1
sequencer.c
··· 1711 1711 1712 1712 out: 1713 1713 free_commit_extra_headers(extra); 1714 + free_commit_list(parents); 1714 1715 strbuf_release(&err); 1715 1716 strbuf_release(&commit_msg); 1716 1717 free(amend_author);
+1
t/t3403-rebase-skip.sh
··· 8 8 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main 9 9 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME 10 10 11 + TEST_PASSES_SANITIZE_LEAK=true 11 12 . ./test-lib.sh 12 13 13 14 . "$TEST_DIRECTORY"/lib-rebase.sh
+1
t/t3424-rebase-empty.sh
··· 2 2 3 3 test_description='git rebase of commits that start or become empty' 4 4 5 + TEST_PASSES_SANITIZE_LEAK=true 5 6 . ./test-lib.sh 6 7 7 8 test_expect_success 'setup test repository' '
+1
t/t3505-cherry-pick-empty.sh
··· 5 5 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main 6 6 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME 7 7 8 + TEST_PASSES_SANITIZE_LEAK=true 8 9 . ./test-lib.sh 9 10 10 11 test_expect_success setup '
+1
t/t7505-prepare-commit-msg-hook.sh
··· 5 5 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main 6 6 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME 7 7 8 + TEST_PASSES_SANITIZE_LEAK=true 8 9 . ./test-lib.sh 9 10 10 11 test_expect_success 'set up commits for rebasing' '