{"thread":{"id":"61910","subject":"[PATCH 00/22] Memory leak fixes (pt.4)","startedAt":"2024-08-06T08:59:38Z","lastAt":"2024-08-14T15:28:33Z","messageCount":146,"participants":["Patrick Steinhardt","James Liu","Junio C Hamano","Phillip Wood","karthik nayak","Taylor Blau","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":22},"messages":[{"id":"500168","messageId":"cover.1722933642.git.ps@pks.im","threadId":"61910","inReplyTo":null,"subject":"[PATCH 00/22] Memory leak fixes (pt.4)","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-06T08:59:32Z","receivedAt":"2024-08-06T08:59:38Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthe third set of memory leak fixes was merged to `next`, so this is the\nnext part of more or less random memory leak fixes all over the place.\nWith this series, we're at ~155 leaking test suites. Naturally, I've\nalready got v5 in the pipeline, which brings us down to ~120.\n\nThe series is built on top of 406f326d27 (The second batch, 2024-08-01)\nwith ps/leakfixes-part-3 at f30bfafcd4 (commit-reach: fix trivial memory\nleak when computing reachability, 2024-08-01) merged into it.\n\nThanks!\n\nPatrick\n\nPatrick Steinhardt (22):\n  remote: plug memory leak when aliasing URLs\n  git: fix leaking system paths\n  object-file: fix memory leak when reading corrupted headers\n  object-name: fix leaking symlink paths in object context\n  bulk-checkin: fix leaking state TODO\n  read-cache: fix leaking hashfile when writing index fails\n  submodule-config: fix leaking name enrty when traversing submodules\n  config: fix leaking comment character config\n  builtin/rebase: fix leaking `commit.gpgsign` value\n  builtin/notes: fix leaking `struct notes_tree` when merging notes\n  builtin/fast-import: plug trivial memory leaks\n  builtin/fast-export: fix leaking diff options\n  builtin/fast-export: plug leaking tag names\n  merge-ort: unconditionally release attributes index\n  sequencer: release todo list on error paths\n  unpack-trees: clear index when not propagating it\n  diff: fix leak when parsing invalid ignore regex option\n  builtin/format-patch: fix various trivial memory leaks\n  userdiff: fix leaking memory for configured diff drivers\n  builtin/log: fix leak when showing converted blob contents\n  diff: free state populated via options\n  builtin/diff: free symmetric diff members\n\n builtin/diff.c                        | 10 ++-\n builtin/fast-export.c                 | 19 ++++--\n builtin/fast-import.c                 |  8 ++-\n builtin/log.c                         | 13 +++-\n builtin/notes.c                       |  9 ++-\n builtin/rebase.c                      |  8 +++\n bulk-checkin.c                        |  2 +\n config.c                              |  2 +\n csum-file.c                           |  2 +-\n csum-file.h                           | 10 +++\n diff.c                                | 16 ++++-\n environment.c                         |  3 +-\n environment.h                         |  1 +\n git.c                                 | 12 +++-\n merge-ort.c                           |  3 +-\n object-file.c                         |  1 +\n object-name.c                         |  1 +\n range-diff.c                          |  6 +-\n read-cache.c                          | 97 ++++++++++++++++-----------\n remote.c                              |  2 +\n sequencer.c                           | 65 +++++++++++++-----\n submodule-config.c                    | 18 +++--\n t/t0210-trace2-normal.sh              |  2 +-\n t/t1006-cat-file.sh                   |  1 +\n t/t1050-large.sh                      |  1 +\n t/t1450-fsck.sh                       |  1 +\n t/t1601-index-bogus.sh                |  2 +\n t/t2107-update-index-basic.sh         |  1 +\n t/t3310-notes-merge-manual-resolve.sh |  1 +\n t/t3311-notes-merge-fanout.sh         |  1 +\n t/t3404-rebase-interactive.sh         |  1 +\n t/t3435-rebase-gpg-sign.sh            |  1 +\n t/t3507-cherry-pick-conflict.sh       |  1 +\n t/t3510-cherry-pick-sequence.sh       |  1 +\n t/t3705-add-sparse-checkout.sh        |  1 +\n t/t4013-diff-various.sh               |  1 +\n t/t4014-format-patch.sh               |  1 +\n t/t4018-diff-funcname.sh              |  1 +\n t/t4030-diff-textconv.sh              |  2 +\n t/t4042-diff-textconv-caching.sh      |  2 +\n t/t4048-diff-combined-binary.sh       |  1 +\n t/t4064-diff-oidfind.sh               |  2 +\n t/t4065-diff-anchored.sh              |  1 +\n t/t4068-diff-symmetric-merge-base.sh  |  1 +\n t/t4069-remerge-diff.sh               |  1 +\n t/t4108-apply-threeway.sh             |  1 +\n t/t4209-log-pickaxe.sh                |  2 +\n t/t6421-merge-partial-clone.sh        |  1 +\n t/t6428-merge-conflicts-sparse.sh     |  1 +\n t/t7008-filter-branch-null-sha1.sh    |  1 +\n t/t7030-verify-tag.sh                 |  1 +\n t/t7817-grep-sparse-checkout.sh       |  1 +\n t/t9300-fast-import.sh                |  1 +\n t/t9304-fast-import-marks.sh          |  2 +\n t/t9351-fast-export-anonymize.sh      |  1 +\n unpack-trees.c                        |  2 +\n userdiff.c                            | 38 ++++++++---\n userdiff.h                            |  4 ++\n 58 files changed, 289 insertions(+), 103 deletions(-)\n\n-- \n2.46.0.dirty\n\n"},{"id":"500169","messageId":"6e2fcd85c7032a8f6ee0969ee230f3e319872e09.1722933642.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"[PATCH 01/22] remote: plug memory leak when aliasing URLs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-06T08:59:37Z","receivedAt":"2024-08-06T08:59:40Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When we have a `url.*.insteadOf` configuration, then we end up aliasing\nURLs when populating remotes. One place where this happens is in\n`alias_all_urls()`, where we loop through all remotes and then alias\neach of their URLs. The actual aliasing logic is then contained in\n`alias_url()`, which returns an allocated string that contains the new\nURL. This URL replaces the old URL that we have in the strvec that\ncontanis all remote URLs.\n\nWe replace the remote URLs via `strvec_replace()`, which does not hand\nover ownership of the new string to the vector. Still, we didn't free\nthe aliased URL and thus have a memory leak here. Fix it by freeing the\naliased string.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n remote.c                 | 2 ++\n t/t0210-trace2-normal.sh | 2 +-\n 2 files changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/remote.c b/remote.c\nindex f43cf5e7a4..3b898edd23 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -499,6 +499,7 @@ static void alias_all_urls(struct remote_state *remote_state)\n \t\t\tif (alias)\n \t\t\t\tstrvec_replace(&remote_state->remotes[i]->pushurl,\n \t\t\t\t\t       j, alias);\n+\t\t\tfree(alias);\n \t\t}\n \t\tadd_pushurl_aliases = remote_state->remotes[i]->pushurl.nr == 0;\n \t\tfor (j = 0; j < remote_state->remotes[i]->url.nr; j++) {\n@@ -512,6 +513,7 @@ static void alias_all_urls(struct remote_state *remote_state)\n \t\t\tif (alias)\n \t\t\t\tstrvec_replace(&remote_state->remotes[i]->url,\n \t\t\t\t\t       j, alias);\n+\t\t\tfree(alias);\n \t\t}\n \t}\n }\ndiff --git a/t/t0210-trace2-normal.sh b/t/t0210-trace2-normal.sh\nindex c312657a12..b9adc94aab 100755\n--- a/t/t0210-trace2-normal.sh\n+++ b/t/t0210-trace2-normal.sh\n@@ -2,7 +2,7 @@\n \n test_description='test trace2 facility (normal target)'\n \n-TEST_PASSES_SANITIZE_LEAK=false\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # Turn off any inherited trace2 settings for this test.\n-- \n2.46.0.dirty\n\n"},{"id":"500170","messageId":"9574995a246d96b90f03827bf0ba591593d6c4d9.1722933642.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"[PATCH 02/22] git: fix leaking system paths","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-06T08:59:42Z","receivedAt":"2024-08-06T08:59:46Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Git has some flags to make it output system paths as they have been\ncompiled into Git. This is done by calling `system_path()`, which\nreturns an allocated string. This string isn't ever free'd though,\ncreating a memory leak.\n\nPlug those leaks. While they are surfaced by t0211, there are more\nmemory leaks looming exposed by that test suite and it thus does not yet\npass with the memory leak checker enabled.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n git.c | 12 +++++++++---\n 1 file changed, 9 insertions(+), 3 deletions(-)\n\ndiff --git a/git.c b/git.c\nindex e35af9b0e5..5eab88b472 100644\n--- a/git.c\n+++ b/git.c\n@@ -173,15 +173,21 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\t\texit(0);\n \t\t\t}\n \t\t} else if (!strcmp(cmd, \"--html-path\")) {\n-\t\t\tputs(system_path(GIT_HTML_PATH));\n+\t\t\tchar *path = system_path(GIT_HTML_PATH);\n+\t\t\tputs(path);\n+\t\t\tfree(path);\n \t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"--man-path\")) {\n-\t\t\tputs(system_path(GIT_MAN_PATH));\n+\t\t\tchar *path = system_path(GIT_MAN_PATH);\n+\t\t\tputs(path);\n+\t\t\tfree(path);\n \t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"--info-path\")) {\n-\t\t\tputs(system_path(GIT_INFO_PATH));\n+\t\t\tchar *path = system_path(GIT_INFO_PATH);\n+\t\t\tputs(path);\n+\t\t\tfree(path);\n \t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"-p\") || !strcmp(cmd, \"--paginate\")) {\n-- \n2.46.0.dirty\n\n"},{"id":"500171","messageId":"f7e67d02d29b3b5a087e1d69aa0ab1d7dd490f0a.1722933642.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"[PATCH 03/22] object-file: fix memory leak when reading corrupted headers","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-06T08:59:47Z","receivedAt":"2024-08-06T08:59:51Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When reading corrupt object headers in `read_loose_object()`, then we\nbail out immediately. This causes a memory leak though because we would\nhave already initialized the zstream in `unpack_loose_header()`, and it\nis the callers responsibility to finish the zstream even on error. While\nthis feels weird, other callsites do it correctly already.\n\nFix this leak by ending the zstream even on errors. We may want to\nrevisit this interface in the future such that the callee handles this\nfor us already when there was an error.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c   | 1 +\n t/t1450-fsck.sh | 1 +\n 2 files changed, 2 insertions(+)\n\ndiff --git a/object-file.c b/object-file.c\nindex 065103be3e..7c65c435cd 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -2954,6 +2954,7 @@ int read_loose_object(const char *path,\n \tif (unpack_loose_header(&stream, map, mapsize, hdr, sizeof(hdr),\n \t\t\t\tNULL) != ULHR_OK) {\n \t\terror(_(\"unable to unpack header of %s\"), path);\n+\t\tgit_inflate_end(&stream);\n \t\tgoto out;\n \t}\n \ndiff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh\nindex 8a456b1142..280cbf3e03 100755\n--- a/t/t1450-fsck.sh\n+++ b/t/t1450-fsck.sh\n@@ -6,6 +6,7 @@ test_description='git fsck random collection of tests\n * (main) A\n '\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success setup '\n-- \n2.46.0.dirty\n\n"},{"id":"500172","messageId":"a9caaaed55045324bf0cde266c1db1581b73a0e4.1722933642.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"[PATCH 04/22] object-name: fix leaking symlink paths in object context","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-06T08:59:52Z","receivedAt":"2024-08-06T08:59:56Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The object context may be populated with symlink contents when reading a\nsymlink, but the associated strbuf doesn't ever get released when\nreleasing the object context, causing a memory leak. Plug it.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-name.c       | 1 +\n t/t1006-cat-file.sh | 1 +\n 2 files changed, 2 insertions(+)\n\ndiff --git a/object-name.c b/object-name.c\nindex 240a93e7ce..e39fa50e47 100644\n--- a/object-name.c\n+++ b/object-name.c\n@@ -1765,6 +1765,7 @@ int strbuf_check_branch_ref(struct strbuf *sb, const char *name)\n void object_context_release(struct object_context *ctx)\n {\n \tfree(ctx->path);\n+\tstrbuf_release(&ctx->symlink_path);\n }\n \n /*\ndiff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\nindex ff9bf213aa..d36cd7c086 100755\n--- a/t/t1006-cat-file.sh\n+++ b/t/t1006-cat-file.sh\n@@ -2,6 +2,7 @@\n \n test_description='git cat-file'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_cmdmode_usage () {\n-- \n2.46.0.dirty\n\n"},{"id":"500173","messageId":"794af6610395add19abb359be5245e95a57681d0.1722933642.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"[PATCH 05/22] bulk-checkin: fix leaking state TODO","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-06T08:59:57Z","receivedAt":"2024-08-06T09:00:01Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When flushing a bulk-checking to disk we also reset the `struct\nbulk_checkin_packfile` state. But while we free some of its members,\nothers aren't being free'd, leading to memory leaks:\n\n  - The temporary packfile name is not getting freed.\n\n  - The `struct hashfile` only gets freed in case we end up calling\n    `finalize_hashfile()`. There are code paths though where that is not\n    the case, namely when nothing has been written. For this, we need to\n    make `free_hashfile()` public.\n\nFix those leaks.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n bulk-checkin.c   |  2 ++\n csum-file.c      |  2 +-\n csum-file.h      | 10 ++++++++++\n t/t1050-large.sh |  1 +\n 4 files changed, 14 insertions(+), 1 deletion(-)\n\ndiff --git a/bulk-checkin.c b/bulk-checkin.c\nindex da8673199b..9089c214fa 100644\n--- a/bulk-checkin.c\n+++ b/bulk-checkin.c\n@@ -61,6 +61,7 @@ static void flush_bulk_checkin_packfile(struct bulk_checkin_packfile *state)\n \n \tif (state->nr_written == 0) {\n \t\tclose(state->f->fd);\n+\t\tfree_hashfile(state->f);\n \t\tunlink(state->pack_tmp_name);\n \t\tgoto clear_exit;\n \t} else if (state->nr_written == 1) {\n@@ -83,6 +84,7 @@ static void flush_bulk_checkin_packfile(struct bulk_checkin_packfile *state)\n \t\tfree(state->written[i]);\n \n clear_exit:\n+\tfree(state->pack_tmp_name);\n \tfree(state->written);\n \tmemset(state, 0, sizeof(*state));\n \ndiff --git a/csum-file.c b/csum-file.c\nindex 8abbf01325..7e0ece1305 100644\n--- a/csum-file.c\n+++ b/csum-file.c\n@@ -56,7 +56,7 @@ void hashflush(struct hashfile *f)\n \t}\n }\n \n-static void free_hashfile(struct hashfile *f)\n+void free_hashfile(struct hashfile *f)\n {\n \tfree(f->buffer);\n \tfree(f->check_buffer);\ndiff --git a/csum-file.h b/csum-file.h\nindex 566e05cbd2..ca553eba17 100644\n--- a/csum-file.h\n+++ b/csum-file.h\n@@ -46,6 +46,16 @@ int hashfile_truncate(struct hashfile *, struct hashfile_checkpoint *);\n struct hashfile *hashfd(int fd, const char *name);\n struct hashfile *hashfd_check(const char *name);\n struct hashfile *hashfd_throughput(int fd, const char *name, struct progress *tp);\n+\n+/*\n+ * Free the hashfile without flushing its contents to disk. This only\n+ * needs to be called when not calling `finalize_hashfile()`.\n+ */\n+void free_hashfile(struct hashfile *f);\n+\n+/*\n+ * Finalize the hashfile by flushing data to disk and free'ing it.\n+ */\n int finalize_hashfile(struct hashfile *, unsigned char *, enum fsync_component, unsigned int);\n void hashwrite(struct hashfile *, const void *, unsigned int);\n void hashflush(struct hashfile *f);\ndiff --git a/t/t1050-large.sh b/t/t1050-large.sh\nindex c71932b024..ed638f6644 100755\n--- a/t/t1050-large.sh\n+++ b/t/t1050-large.sh\n@@ -3,6 +3,7 @@\n \n test_description='adding and checking out large blobs'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'core.bigFileThreshold must be non-negative' '\n-- \n2.46.0.dirty\n\n"},{"id":"500174","messageId":"2810cada0af14c3331674a807b20d18e6c9af022.1722933642.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"[PATCH 06/22] read-cache: fix leaking hashfile when writing index fails","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-06T09:00:02Z","receivedAt":"2024-08-06T09:00:06Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `do_write_index()`, we use a `struct hashfile` to write the index\nwith a trailer hash. In case the write fails though, we never clean up\nthe allocated `hashfile` state and thus leak memory.\n\nRefactor the code to have a common exit path where we can free this and\nother allocated memory. While at it, refactor our use of `strbuf`s such\nthat we reuse the same buffer to avoid some unneeded allocations.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n read-cache.c                       | 97 ++++++++++++++++++------------\n t/t1601-index-bogus.sh             |  2 +\n t/t2107-update-index-basic.sh      |  1 +\n t/t7008-filter-branch-null-sha1.sh |  1 +\n 4 files changed, 62 insertions(+), 39 deletions(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex 48bf24f87c..36821fe5b5 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -2840,8 +2840,9 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \tint csum_fsync_flag;\n \tint ieot_entries = 1;\n \tstruct index_entry_offset_table *ieot = NULL;\n-\tint nr, nr_threads;\n \tstruct repository *r = istate->repo;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tint nr, nr_threads, ret;\n \n \tf = hashfd(tempfile->fd, tempfile->filename.buf);\n \n@@ -2962,8 +2963,8 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \tstrbuf_release(&previous_name_buf);\n \n \tif (err) {\n-\t\tfree(ieot);\n-\t\treturn err;\n+\t\tret = err;\n+\t\tgoto out;\n \t}\n \n \toffset = hashfile_total(f);\n@@ -2985,20 +2986,20 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \t * index.\n \t */\n \tif (ieot) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\twrite_ieot_extension(&sb, ieot);\n \t\terr = write_index_ext_header(f, eoie_c, CACHE_EXT_INDEXENTRYOFFSETTABLE, sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tfree(ieot);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \n \tif (write_extensions & WRITE_SPLIT_INDEX_EXTENSION &&\n \t    istate->split_index) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\tif (istate->sparse_index)\n \t\t\tdie(_(\"cannot write split index for a sparse index\"));\n@@ -3007,59 +3008,66 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \t\t\twrite_index_ext_header(f, eoie_c, CACHE_EXT_LINK,\n \t\t\t\t\t       sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \tif (write_extensions & WRITE_CACHE_TREE_EXTENSION &&\n \t    !drop_cache_tree && istate->cache_tree) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\tcache_tree_write(&sb, istate->cache_tree);\n \t\terr = write_index_ext_header(f, eoie_c, CACHE_EXT_TREE, sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \tif (write_extensions & WRITE_RESOLVE_UNDO_EXTENSION &&\n \t    istate->resolve_undo) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\tresolve_undo_write(&sb, istate->resolve_undo);\n \t\terr = write_index_ext_header(f, eoie_c, CACHE_EXT_RESOLVE_UNDO,\n \t\t\t\t\t     sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \tif (write_extensions & WRITE_UNTRACKED_CACHE_EXTENSION &&\n \t    istate->untracked) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\twrite_untracked_extension(&sb, istate->untracked);\n \t\terr = write_index_ext_header(f, eoie_c, CACHE_EXT_UNTRACKED,\n \t\t\t\t\t     sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \tif (write_extensions & WRITE_FSMONITOR_EXTENSION &&\n \t    istate->fsmonitor_last_update) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\twrite_fsmonitor_extension(&sb, istate);\n \t\terr = write_index_ext_header(f, eoie_c, CACHE_EXT_FSMONITOR, sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \tif (istate->sparse_index) {\n-\t\tif (write_index_ext_header(f, eoie_c, CACHE_EXT_SPARSE_DIRECTORIES, 0) < 0)\n-\t\t\treturn -1;\n+\t\tif (write_index_ext_header(f, eoie_c, CACHE_EXT_SPARSE_DIRECTORIES, 0) < 0) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \n \t/*\n@@ -3069,14 +3077,15 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \t * when loading the shared index.\n \t */\n \tif (eoie_c) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\twrite_eoie_extension(&sb, eoie_c, offset);\n \t\terr = write_index_ext_header(f, NULL, CACHE_EXT_ENDOFINDEXENTRIES, sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \n \tcsum_fsync_flag = 0;\n@@ -3085,13 +3094,16 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \n \tfinalize_hashfile(f, istate->oid.hash, FSYNC_COMPONENT_INDEX,\n \t\t\t  CSUM_HASH_IN_STREAM | csum_fsync_flag);\n+\tf = NULL;\n \n \tif (close_tempfile_gently(tempfile)) {\n-\t\terror(_(\"could not close '%s'\"), get_tempfile_path(tempfile));\n-\t\treturn -1;\n+\t\tret = error(_(\"could not close '%s'\"), get_tempfile_path(tempfile));\n+\t\tgoto out;\n+\t}\n+\tif (stat(get_tempfile_path(tempfile), &st)) {\n+\t\tret = -1;\n+\t\tgoto out;\n \t}\n-\tif (stat(get_tempfile_path(tempfile), &st))\n-\t\treturn -1;\n \tistate->timestamp.sec = (unsigned int)st.st_mtime;\n \tistate->timestamp.nsec = ST_MTIME_NSEC(st);\n \ttrace_performance_since(start, \"write index, changed mask = %x\", istate->cache_changed);\n@@ -3105,7 +3117,14 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \ttrace2_data_intmax(\"index\", the_repository, \"write/cache_nr\",\n \t\t\t   istate->cache_nr);\n \n-\treturn 0;\n+\tret = 0;\n+\n+out:\n+\tif (f)\n+\t\tfree_hashfile(f);\n+\tstrbuf_release(&sb);\n+\tfree(ieot);\n+\treturn ret;\n }\n \n void set_alternate_index_output(const char *name)\ndiff --git a/t/t1601-index-bogus.sh b/t/t1601-index-bogus.sh\nindex 4171f1e141..5dcc101882 100755\n--- a/t/t1601-index-bogus.sh\n+++ b/t/t1601-index-bogus.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='test handling of bogus index entries'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'create tree with null sha1' '\ndiff --git a/t/t2107-update-index-basic.sh b/t/t2107-update-index-basic.sh\nindex cc72ead79f..f0eab13f96 100755\n--- a/t/t2107-update-index-basic.sh\n+++ b/t/t2107-update-index-basic.sh\n@@ -5,6 +5,7 @@ test_description='basic update-index tests\n Tests for command-line parsing and basic operation.\n '\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'update-index --nonsense fails' '\ndiff --git a/t/t7008-filter-branch-null-sha1.sh b/t/t7008-filter-branch-null-sha1.sh\nindex 93fbc92b8d..0ce8fd2c89 100755\n--- a/t/t7008-filter-branch-null-sha1.sh\n+++ b/t/t7008-filter-branch-null-sha1.sh\n@@ -2,6 +2,7 @@\n \n test_description='filter-branch removal of trees with null sha1'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup: base commits' '\n-- \n2.46.0.dirty\n\n"},{"id":"500175","messageId":"03f699cf39ebbbb2bc3ba9acaa72415efda4bbe3.1722933642.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"[PATCH 07/22] submodule-config: fix leaking name enrty when traversing submodules","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-06T09:00:07Z","receivedAt":"2024-08-06T09:00:10Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We traverse through submodules in the tree via `tree_entry()`, passing\nto it a `struct name_entry` that it is supposed to populate with the\ntree entry's contents. We unnecessarily allocate this variable instead\nof passing a variable that is allocated on the stack, and the ultimately\ndon't even free that variable. This is unnecessary and leaks memory.\n\nConvert the variable to instead be allocated on the stack to plug the\nmemory leak.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n submodule-config.c | 18 ++++++++----------\n 1 file changed, 8 insertions(+), 10 deletions(-)\n\ndiff --git a/submodule-config.c b/submodule-config.c\nindex 9b0bb0b9f4..c8f2bb2bdd 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -899,27 +899,25 @@ static void traverse_tree_submodules(struct repository *r,\n {\n \tstruct tree_desc tree;\n \tstruct submodule_tree_entry *st_entry;\n-\tstruct name_entry *name_entry;\n+\tstruct name_entry name_entry;\n \tchar *tree_path = NULL;\n \n-\tname_entry = xmalloc(sizeof(*name_entry));\n-\n \tfill_tree_descriptor(r, &tree, treeish_name);\n-\twhile (tree_entry(&tree, name_entry)) {\n+\twhile (tree_entry(&tree, &name_entry)) {\n \t\tif (prefix)\n \t\t\ttree_path =\n-\t\t\t\tmkpathdup(\"%s/%s\", prefix, name_entry->path);\n+\t\t\t\tmkpathdup(\"%s/%s\", prefix, name_entry.path);\n \t\telse\n-\t\t\ttree_path = xstrdup(name_entry->path);\n+\t\t\ttree_path = xstrdup(name_entry.path);\n \n-\t\tif (S_ISGITLINK(name_entry->mode) &&\n+\t\tif (S_ISGITLINK(name_entry.mode) &&\n \t\t    is_tree_submodule_active(r, root_tree, tree_path)) {\n \t\t\tALLOC_GROW(out->entries, out->entry_nr + 1,\n \t\t\t\t   out->entry_alloc);\n \t\t\tst_entry = &out->entries[out->entry_nr++];\n \n \t\t\tst_entry->name_entry = xmalloc(sizeof(*st_entry->name_entry));\n-\t\t\t*st_entry->name_entry = *name_entry;\n+\t\t\t*st_entry->name_entry = name_entry;\n \t\t\tst_entry->submodule =\n \t\t\t\tsubmodule_from_path(r, root_tree, tree_path);\n \t\t\tst_entry->repo = xmalloc(sizeof(*st_entry->repo));\n@@ -927,9 +925,9 @@ static void traverse_tree_submodules(struct repository *r,\n \t\t\t\t\t\troot_tree))\n \t\t\t\tFREE_AND_NULL(st_entry->repo);\n \n-\t\t} else if (S_ISDIR(name_entry->mode))\n+\t\t} else if (S_ISDIR(name_entry.mode))\n \t\t\ttraverse_tree_submodules(r, root_tree, tree_path,\n-\t\t\t\t\t\t &name_entry->oid, out);\n+\t\t\t\t\t\t &name_entry.oid, out);\n \t\tfree(tree_path);\n \t}\n }\n-- \n2.46.0.dirty\n\n"},{"id":"500176","messageId":"a34c90a5527cb45ec89a0ad44dbca1d61705a0ea.1722933642.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"[PATCH 08/22] config: fix leaking comment character config","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-06T09:00:11Z","receivedAt":"2024-08-06T09:00:15Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When the comment line character has been specified multiple times in the\nconfiguration, then `git_default_core_config()` will cause a memory leak\nbecause it unconditionally copies the string into `comment_line_str`\nwithout free'ing the previous value. In fact, it can't easily free the\nvalue in the first place because it may contain a string constant.\n\nRefactor the code so that we initialize the value with another array.\nThis allows us to free the value in case the string is not pointing to\nthat constant array anymore.\n\nThis memory leak is being hit in t3404. As there are still other memory\nleaks in that file we cannot yet mark it as passing with leak checking\nenabled.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n config.c      | 2 ++\n environment.c | 3 ++-\n environment.h | 1 +\n 3 files changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/config.c b/config.c\nindex 6421894614..63e0211c7d 100644\n--- a/config.c\n+++ b/config.c\n@@ -1596,6 +1596,8 @@ static int git_default_core_config(const char *var, const char *value,\n \t\telse if (value[0]) {\n \t\t\tif (strchr(value, '\\n'))\n \t\t\t\treturn error(_(\"%s cannot contain newline\"), var);\n+\t\t\tif (comment_line_str != comment_line_str_default)\n+\t\t\t\tfree((char *) comment_line_str);\n \t\t\tcomment_line_str = xstrdup(value);\n \t\t\tauto_comment_line_char = 0;\n \t\t} else\ndiff --git a/environment.c b/environment.c\nindex 5cea2c9f54..8297c6e37b 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -113,7 +113,8 @@ int protect_ntfs = PROTECT_NTFS_DEFAULT;\n  * The character that begins a commented line in user-editable file\n  * that is subject to stripspace.\n  */\n-const char *comment_line_str = \"#\";\n+const char comment_line_str_default[] = \"#\";\n+const char *comment_line_str = comment_line_str_default;\n int auto_comment_line_char;\n \n /* Parallel index stat data preload? */\ndiff --git a/environment.h b/environment.h\nindex e9f01d4d11..5e5d9a8045 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -8,6 +8,7 @@ struct strvec;\n  * The character that begins a commented line in user-editable file\n  * that is subject to stripspace.\n  */\n+extern const char comment_line_str_default[];\n extern const char *comment_line_str;\n extern int auto_comment_line_char;\n \n-- \n2.46.0.dirty\n\n"},{"id":"500177","messageId":"05290fc1f14cae8229c42f2d0aafe6619c069e3a.1722933642.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"[PATCH 09/22] builtin/rebase: fix leaking `commit.gpgsign` value","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-06T09:00:16Z","receivedAt":"2024-08-06T09:00:19Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `get_replay_opts()`, we unconditionally override the `gpg_sign` field\nthat already got populated by `sequencer_init_config()` in case the user\nhas \"commit.gpgsign\" set in their config. It is kind of dubious whether\nthis is the correct thing to do or a bug. What is clear though is that\nthis creates a memory leak.\n\nLet's mark this assignment with a TODO comment to figure out whether\nthis needs to be fixed or not. Meanwhile though, let's plug the memory\nleak.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/rebase.c              | 8 ++++++++\n sequencer.c                   | 1 +\n t/t3404-rebase-interactive.sh | 1 +\n t/t3435-rebase-gpg-sign.sh    | 1 +\n t/t7030-verify-tag.sh         | 1 +\n 5 files changed, 12 insertions(+)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex e3a8e74cfc..f65316a023 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -186,7 +186,15 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n \treplay.committer_date_is_author_date =\n \t\t\t\t\topts->committer_date_is_author_date;\n \treplay.ignore_date = opts->ignore_date;\n+\n+\t/*\n+\t * TODO: Is it really intentional that we unconditionally override\n+\t * `replay.gpg_sign` even if it has already been initialized via the\n+\t * configuration?\n+\t */\n+\tfree(replay.gpg_sign);\n \treplay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);\n+\n \treplay.reflog_action = xstrdup(opts->reflog_action);\n \tif (opts->strategy)\n \t\treplay.strategy = xstrdup_or_null(opts->strategy);\ndiff --git a/sequencer.c b/sequencer.c\nindex 0291920f0b..cade9b0ca8 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -303,6 +303,7 @@ static int git_sequencer_config(const char *k, const char *v,\n \t}\n \n \tif (!strcmp(k, \"commit.gpgsign\")) {\n+\t\tfree(opts->gpg_sign);\n \t\topts->gpg_sign = git_config_bool(k, v) ? xstrdup(\"\") : NULL;\n \t\treturn 0;\n \t}\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex f92baad138..f171af3061 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -26,6 +26,7 @@ Initial setup:\n  touch file \"conflict\".\n '\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n . \"$TEST_DIRECTORY\"/lib-rebase.sh\ndiff --git a/t/t3435-rebase-gpg-sign.sh b/t/t3435-rebase-gpg-sign.sh\nindex 6aa2aeb628..6e329fea7c 100755\n--- a/t/t3435-rebase-gpg-sign.sh\n+++ b/t/t3435-rebase-gpg-sign.sh\n@@ -8,6 +8,7 @@ test_description='test rebase --[no-]gpg-sign'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY/lib-rebase.sh\"\n . \"$TEST_DIRECTORY/lib-gpg.sh\"\ndiff --git a/t/t7030-verify-tag.sh b/t/t7030-verify-tag.sh\nindex 6f526c37c2..effa826744 100755\n--- a/t/t7030-verify-tag.sh\n+++ b/t/t7030-verify-tag.sh\n@@ -4,6 +4,7 @@ test_description='signed tag tests'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY/lib-gpg.sh\"\n \n-- \n2.46.0.dirty\n\n"},{"id":"500178","messageId":"4f5d49007484e8c201f62f84ffec101da00548ae.1722933642.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"[PATCH 10/22] builtin/notes: fix leaking `struct notes_tree` when merging notes","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-06T09:00:21Z","receivedAt":"2024-08-06T09:00:24Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We allocate a `struct notes_tree` in `merge_commit()` which we then\ninitialize via `init_notes()`. It's not really necessary to allocate the\nstructure though given that we never pass ownership to the caller.\nFurthermore, the allocation leads to a memory leak because despite its\nname, `free_notes()` doesn't free the `notes_tree` but only clears it.\n\nFix this issue by converting the code to use an on-stack variable.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/notes.c                       | 9 ++++-----\n t/t3310-notes-merge-manual-resolve.sh | 1 +\n t/t3311-notes-merge-fanout.sh         | 1 +\n 3 files changed, 6 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex d9c356e354..81cbaeec6b 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -807,7 +807,7 @@ static int merge_commit(struct notes_merge_options *o)\n {\n \tstruct strbuf msg = STRBUF_INIT;\n \tstruct object_id oid, parent_oid;\n-\tstruct notes_tree *t;\n+\tstruct notes_tree t = {0};\n \tstruct commit *partial;\n \tstruct pretty_print_context pretty_ctx;\n \tvoid *local_ref_to_free;\n@@ -830,8 +830,7 @@ static int merge_commit(struct notes_merge_options *o)\n \telse\n \t\toidclr(&parent_oid, the_repository->hash_algo);\n \n-\tCALLOC_ARRAY(t, 1);\n-\tinit_notes(t, \"NOTES_MERGE_PARTIAL\", combine_notes_overwrite, 0);\n+\tinit_notes(&t, \"NOTES_MERGE_PARTIAL\", combine_notes_overwrite, 0);\n \n \to->local_ref = local_ref_to_free =\n \t\trefs_resolve_refdup(get_main_ref_store(the_repository),\n@@ -839,7 +838,7 @@ static int merge_commit(struct notes_merge_options *o)\n \tif (!o->local_ref)\n \t\tdie(_(\"failed to resolve NOTES_MERGE_REF\"));\n \n-\tif (notes_merge_commit(o, t, partial, &oid))\n+\tif (notes_merge_commit(o, &t, partial, &oid))\n \t\tdie(_(\"failed to finalize notes merge\"));\n \n \t/* Reuse existing commit message in reflog message */\n@@ -853,7 +852,7 @@ static int merge_commit(struct notes_merge_options *o)\n \t\t\tis_null_oid(&parent_oid) ? NULL : &parent_oid,\n \t\t\t0, UPDATE_REFS_DIE_ON_ERR);\n \n-\tfree_notes(t);\n+\tfree_notes(&t);\n \tstrbuf_release(&msg);\n \tret = merge_abort(o);\n \tfree(local_ref_to_free);\ndiff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh\nindex 597df5ebc0..04866b89be 100755\n--- a/t/t3310-notes-merge-manual-resolve.sh\n+++ b/t/t3310-notes-merge-manual-resolve.sh\n@@ -5,6 +5,7 @@\n \n test_description='Test notes merging with manual conflict resolution'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # Set up a notes merge scenario with different kinds of conflicts\ndiff --git a/t/t3311-notes-merge-fanout.sh b/t/t3311-notes-merge-fanout.sh\nindex 5b675417e9..ce4144db0f 100755\n--- a/t/t3311-notes-merge-fanout.sh\n+++ b/t/t3311-notes-merge-fanout.sh\n@@ -5,6 +5,7 @@\n \n test_description='Test notes merging at various fanout levels'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n verify_notes () {\n-- \n2.46.0.dirty\n\n"},{"id":"500179","messageId":"798b911f775a8d565f5c05605e257071d9f8a00a.1722933642.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"[PATCH 11/22] builtin/fast-import: plug trivial memory leaks","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-06T09:00:25Z","receivedAt":"2024-08-06T09:00:29Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Plug some trivial memory leaks in git-fast-import(1).\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fast-import.c        | 8 ++++++--\n t/t9300-fast-import.sh       | 1 +\n t/t9304-fast-import-marks.sh | 2 ++\n 3 files changed, 9 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex d21c4053a7..6dfeb01665 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -206,8 +206,8 @@ static unsigned int object_entry_alloc = 5000;\n static struct object_entry_pool *blocks;\n static struct hashmap object_table;\n static struct mark_set *marks;\n-static const char *export_marks_file;\n-static const char *import_marks_file;\n+static char *export_marks_file;\n+static char *import_marks_file;\n static int import_marks_file_from_stream;\n static int import_marks_file_ignore_missing;\n static int import_marks_file_done;\n@@ -3274,6 +3274,7 @@ static void option_import_marks(const char *marks,\n \t\t\tread_marks();\n \t}\n \n+\tfree(import_marks_file);\n \timport_marks_file = make_fast_import_path(marks);\n \timport_marks_file_from_stream = from_stream;\n \timport_marks_file_ignore_missing = ignore_missing;\n@@ -3316,6 +3317,7 @@ static void option_active_branches(const char *branches)\n \n static void option_export_marks(const char *marks)\n {\n+\tfree(export_marks_file);\n \texport_marks_file = make_fast_import_path(marks);\n }\n \n@@ -3357,6 +3359,8 @@ static void option_rewrite_submodules(const char *arg, struct string_list *list)\n \tfree(f);\n \n \tstring_list_insert(list, s)->util = ms;\n+\n+\tfree(s);\n }\n \n static int parse_one_option(const char *option)\ndiff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh\nindex 1e68426852..3b3c371740 100755\n--- a/t/t9300-fast-import.sh\n+++ b/t/t9300-fast-import.sh\n@@ -7,6 +7,7 @@ test_description='test git fast-import utility'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-diff.sh ;# test-lib chdir's into trash\n \ndiff --git a/t/t9304-fast-import-marks.sh b/t/t9304-fast-import-marks.sh\nindex 410a871c52..1f776a80f3 100755\n--- a/t/t9304-fast-import-marks.sh\n+++ b/t/t9304-fast-import-marks.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='test exotic situations with marks'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup dump of basic history' '\n-- \n2.46.0.dirty\n\n"},{"id":"500180","messageId":"660732d29df8be6253f205fbbad2cdcafb0a0bbd.1722933642.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"[PATCH 12/22] builtin/fast-export: fix leaking diff options","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-06T09:00:30Z","receivedAt":"2024-08-06T09:00:34Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Before caling `handle_commit()` in a loop, we set `diffopt.no_free` such\nthat its contents aren't getting freed inside of `handle_commit()`. We\nnever unset that flag though, which means that it'll ultimately leak\nwhen calling `release_revisions()`.\n\nFix this by unsetting the flag after the loop.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fast-export.c | 2 ++\n 1 file changed, 2 insertions(+)\n\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex 4b6e8c6832..fe92d2436c 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -1278,9 +1278,11 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)\n \trevs.diffopt.format_callback = show_filemodify;\n \trevs.diffopt.format_callback_data = &paths_of_changed_objects;\n \trevs.diffopt.flags.recursive = 1;\n+\n \trevs.diffopt.no_free = 1;\n \twhile ((commit = get_revision(&revs)))\n \t\thandle_commit(commit, &revs, &paths_of_changed_objects);\n+\trevs.diffopt.no_free = 0;\n \n \thandle_tags_and_duplicates(&extra_refs);\n \thandle_tags_and_duplicates(&tag_refs);\n-- \n2.46.0.dirty\n\n"},{"id":"500181","messageId":"64366155dee25209ab9c434016c5918d47d7e1d5.1722933642.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"[PATCH 13/22] builtin/fast-export: plug leaking tag names","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-06T09:00:35Z","receivedAt":"2024-08-06T09:00:38Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When resolving revisions in `get_tags_and_duplicates()`, we only\npartially manage the lifetime of `full_name`. In fact, managing its\nlifetime properly is almost impossible because we put direct pointers to\nthat variable into multiple lists without duplicating the string. The\nconsequence is that these strings will ultimately leak.\n\nRefactor the code to make the lists we put those names into duplicate\nthe memory. This allows us to properly free the string as required and\nthus plugs the memory leak.\n\nWhile this requires us to allocate more data overall, it shouldn't be\nall that bad given that the number of allocations corresponds with the\nnumber of command line parameters, which typically aren't all that many.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fast-export.c            | 17 ++++++++++++-----\n t/t9351-fast-export-anonymize.sh |  1 +\n 2 files changed, 13 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex fe92d2436c..f253b79322 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -42,8 +42,8 @@ static int full_tree;\n static int reference_excluded_commits;\n static int show_original_ids;\n static int mark_tags;\n-static struct string_list extra_refs = STRING_LIST_INIT_NODUP;\n-static struct string_list tag_refs = STRING_LIST_INIT_NODUP;\n+static struct string_list extra_refs = STRING_LIST_INIT_DUP;\n+static struct string_list tag_refs = STRING_LIST_INIT_DUP;\n static struct refspec refspecs = REFSPEC_INIT_FETCH;\n static int anonymize;\n static struct hashmap anonymized_seeds;\n@@ -901,7 +901,7 @@ static void handle_tag(const char *name, struct tag *tag)\n \tfree(buf);\n }\n \n-static struct commit *get_commit(struct rev_cmdline_entry *e, char *full_name)\n+static struct commit *get_commit(struct rev_cmdline_entry *e, const char *full_name)\n {\n \tswitch (e->item->type) {\n \tcase OBJ_COMMIT:\n@@ -932,14 +932,16 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)\n \t\tstruct rev_cmdline_entry *e = info->rev + i;\n \t\tstruct object_id oid;\n \t\tstruct commit *commit;\n-\t\tchar *full_name;\n+\t\tchar *full_name = NULL;\n \n \t\tif (e->flags & UNINTERESTING)\n \t\t\tcontinue;\n \n \t\tif (repo_dwim_ref(the_repository, e->name, strlen(e->name),\n-\t\t\t\t  &oid, &full_name, 0) != 1)\n+\t\t\t\t  &oid, &full_name, 0) != 1) {\n+\t\t\tfree(full_name);\n \t\t\tcontinue;\n+\t\t}\n \n \t\tif (refspecs.nr) {\n \t\t\tchar *private;\n@@ -955,6 +957,7 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)\n \t\t\twarning(\"%s: Unexpected object of type %s, skipping.\",\n \t\t\t\te->name,\n \t\t\t\ttype_name(e->item->type));\n+\t\t\tfree(full_name);\n \t\t\tcontinue;\n \t\t}\n \n@@ -963,10 +966,12 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)\n \t\t\tbreak;\n \t\tcase OBJ_BLOB:\n \t\t\texport_blob(&commit->object.oid);\n+\t\t\tfree(full_name);\n \t\t\tcontinue;\n \t\tdefault: /* OBJ_TAG (nested tags) is already handled */\n \t\t\twarning(\"Tag points to object of unexpected type %s, skipping.\",\n \t\t\t\ttype_name(commit->object.type));\n+\t\t\tfree(full_name);\n \t\t\tcontinue;\n \t\t}\n \n@@ -979,6 +984,8 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)\n \n \t\tif (!*revision_sources_at(&revision_sources, commit))\n \t\t\t*revision_sources_at(&revision_sources, commit) = full_name;\n+\t\telse\n+\t\t\tfree(full_name);\n \t}\n \n \tstring_list_sort(&extra_refs);\ndiff --git a/t/t9351-fast-export-anonymize.sh b/t/t9351-fast-export-anonymize.sh\nindex 156a647484..c0d9d7be75 100755\n--- a/t/t9351-fast-export-anonymize.sh\n+++ b/t/t9351-fast-export-anonymize.sh\n@@ -4,6 +4,7 @@ test_description='basic tests for fast-export --anonymize'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup simple repo' '\n-- \n2.46.0.dirty\n\n"},{"id":"500182","messageId":"b12015b3c309b177b6a14bc519561daabb321a72.1722933642.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"[PATCH 14/22] merge-ort: unconditionally release attributes index","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-06T09:00:40Z","receivedAt":"2024-08-06T09:00:43Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We conditionally release the index used for reading gitattributes in\nmerge-ort based on whether or the index has been populated. This check\nuses `cache_nr` as a condition. This isn't sufficient though, as the\nvariable may be zero even when some other parts of the index have been\npopulated. This leads to memory leaks when sparse checkouts are in use,\nas we may not end up releasing the sparse checkout patterns.\n\nFix this issue by unconditionally releasing the index.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n merge-ort.c                       | 3 +--\n t/t3507-cherry-pick-conflict.sh   | 1 +\n t/t6421-merge-partial-clone.sh    | 1 +\n t/t6428-merge-conflicts-sparse.sh | 1 +\n t/t7817-grep-sparse-checkout.sh   | 1 +\n 5 files changed, 5 insertions(+), 2 deletions(-)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex e9d01ac7f7..3752c7e595 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -689,8 +689,7 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,\n \t */\n \tstrmap_clear_func(&opti->conflicted, 0);\n \n-\tif (opti->attr_index.cache_nr) /* true iff opt->renormalize */\n-\t\tdiscard_index(&opti->attr_index);\n+\tdiscard_index(&opti->attr_index);\n \n \t/* Free memory used by various renames maps */\n \tfor (i = MERGE_SIDE1; i <= MERGE_SIDE2; ++i) {\ndiff --git a/t/t3507-cherry-pick-conflict.sh b/t/t3507-cherry-pick-conflict.sh\nindex f3947b400a..10e9c91dbb 100755\n--- a/t/t3507-cherry-pick-conflict.sh\n+++ b/t/t3507-cherry-pick-conflict.sh\n@@ -13,6 +13,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n TEST_CREATE_REPO_NO_TEMPLATE=1\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n pristine_detach () {\ndiff --git a/t/t6421-merge-partial-clone.sh b/t/t6421-merge-partial-clone.sh\nindex 711b709e75..020375c805 100755\n--- a/t/t6421-merge-partial-clone.sh\n+++ b/t/t6421-merge-partial-clone.sh\n@@ -26,6 +26,7 @@ test_description=\"limiting blob downloads when merging with partial clones\"\n #                     underscore notation is to differentiate different\n #                     files that might be renamed into each other's paths.)\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-merge.sh\n \ndiff --git a/t/t6428-merge-conflicts-sparse.sh b/t/t6428-merge-conflicts-sparse.sh\nindex 9919c3fa7c..8a79bc2e92 100755\n--- a/t/t6428-merge-conflicts-sparse.sh\n+++ b/t/t6428-merge-conflicts-sparse.sh\n@@ -22,6 +22,7 @@ test_description=\"merge cases\"\n #                     underscore notation is to differentiate different\n #                     files that might be renamed into each other's paths.)\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-merge.sh\n \ndiff --git a/t/t7817-grep-sparse-checkout.sh b/t/t7817-grep-sparse-checkout.sh\nindex eb59564565..0ba7817fb7 100755\n--- a/t/t7817-grep-sparse-checkout.sh\n+++ b/t/t7817-grep-sparse-checkout.sh\n@@ -33,6 +33,7 @@ should leave the following structure in the working tree:\n But note that sub2 should have the SKIP_WORKTREE bit set.\n '\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup' '\n-- \n2.46.0.dirty\n\n"},{"id":"500183","messageId":"df4c21b49f86d6e1e9d2b28375ab6465ffa4339a.1722933642.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"[PATCH 15/22] sequencer: release todo list on error paths","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-06T09:00:45Z","receivedAt":"2024-08-06T09:00:48Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We're not releasing the `todo_list` in `sequencer_pick_revisions()` when\nhitting an error path. Restructure the function to have a common exit\npath such that we can easily clean up the list and thus plug this memory\nleak.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n sequencer.c                     | 64 +++++++++++++++++++++++----------\n t/t3510-cherry-pick-sequence.sh |  1 +\n 2 files changed, 47 insertions(+), 18 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex cade9b0ca8..fec3c5e846 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5490,8 +5490,10 @@ int sequencer_pick_revisions(struct repository *r,\n \tint i, res;\n \n \tassert(opts->revs);\n-\tif (read_and_refresh_cache(r, opts))\n-\t\treturn -1;\n+\tif (read_and_refresh_cache(r, opts)) {\n+\t\tres = -1;\n+\t\tgoto out;\n+\t}\n \n \tfor (i = 0; i < opts->revs->pending.nr; i++) {\n \t\tstruct object_id oid;\n@@ -5506,11 +5508,14 @@ int sequencer_pick_revisions(struct repository *r,\n \t\t\t\tenum object_type type = oid_object_info(r,\n \t\t\t\t\t\t\t\t\t&oid,\n \t\t\t\t\t\t\t\t\tNULL);\n-\t\t\t\treturn error(_(\"%s: can't cherry-pick a %s\"),\n+\t\t\t\tres = error(_(\"%s: can't cherry-pick a %s\"),\n \t\t\t\t\tname, type_name(type));\n+\t\t\t\tgoto out;\n \t\t\t}\n-\t\t} else\n-\t\t\treturn error(_(\"%s: bad revision\"), name);\n+\t\t} else {\n+\t\t\tres = error(_(\"%s: bad revision\"), name);\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \n \t/*\n@@ -5525,14 +5530,23 @@ int sequencer_pick_revisions(struct repository *r,\n \t    opts->revs->no_walk &&\n \t    !opts->revs->cmdline.rev->flags) {\n \t\tstruct commit *cmit;\n-\t\tif (prepare_revision_walk(opts->revs))\n-\t\t\treturn error(_(\"revision walk setup failed\"));\n+\n+\t\tif (prepare_revision_walk(opts->revs)) {\n+\t\t\tres = error(_(\"revision walk setup failed\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n \t\tcmit = get_revision(opts->revs);\n-\t\tif (!cmit)\n-\t\t\treturn error(_(\"empty commit set passed\"));\n+\t\tif (!cmit) {\n+\t\t\tres = error(_(\"empty commit set passed\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n \t\tif (get_revision(opts->revs))\n \t\t\tBUG(\"unexpected extra commit from walk\");\n-\t\treturn single_pick(r, cmit, opts);\n+\n+\t\tres = single_pick(r, cmit, opts);\n+\t\tgoto out;\n \t}\n \n \t/*\n@@ -5542,16 +5556,30 @@ int sequencer_pick_revisions(struct repository *r,\n \t */\n \n \tif (walk_revs_populate_todo(&todo_list, opts) ||\n-\t\t\tcreate_seq_dir(r) < 0)\n-\t\treturn -1;\n-\tif (repo_get_oid(r, \"HEAD\", &oid) && (opts->action == REPLAY_REVERT))\n-\t\treturn error(_(\"can't revert as initial commit\"));\n-\tif (save_head(oid_to_hex(&oid)))\n-\t\treturn -1;\n-\tif (save_opts(opts))\n-\t\treturn -1;\n+\t\t\tcreate_seq_dir(r) < 0) {\n+\t\tres = -1;\n+\t\tgoto out;\n+\t}\n+\n+\tif (repo_get_oid(r, \"HEAD\", &oid) && (opts->action == REPLAY_REVERT)) {\n+\t\tres = error(_(\"can't revert as initial commit\"));\n+\t\tgoto out;\n+\t}\n+\n+\tif (save_head(oid_to_hex(&oid))) {\n+\t\tres = -1;\n+\t\tgoto out;\n+\t}\n+\n+\tif (save_opts(opts)) {\n+\t\tres = -1;\n+\t\tgoto out;\n+\t}\n+\n \tupdate_abort_safety_file();\n \tres = pick_commits(r, &todo_list, opts);\n+\n+out:\n \ttodo_list_release(&todo_list);\n \treturn res;\n }\ndiff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh\nindex 7eb52b12ed..93c725bac3 100755\n--- a/t/t3510-cherry-pick-sequence.sh\n+++ b/t/t3510-cherry-pick-sequence.sh\n@@ -12,6 +12,7 @@ test_description='Test cherry-pick continuation features\n \n '\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # Repeat first match 10 times\n-- \n2.46.0.dirty\n\n"},{"id":"500184","messageId":"1f8553fd4343c10a9d2ed2f9bb54937c1cc407c0.1722933642.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"[PATCH 16/22] unpack-trees: clear index when not propagating it","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-06T09:00:49Z","receivedAt":"2024-08-06T09:00:53Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When provided a pointer to a destination index, then `unpack_trees()`\nwill end up copying its `o->internal.result` index into the provided\npointer. In those cases it is thus not necessary to free the index, as\nwe have transferred ownership of it.\n\nThere are cases though where we do not end up transferring ownership of\nthe memory, but `clear_unpack_trees_porcelain()` will never discard the\nindex in that case and thus cause a memory leak. And right now it cannot\ndo so in the first place because we have no indicator of whether we did\nor didn't transfer ownership of the index.\n\nAdapt the code to zero out the index in case we transfer its ownership.\nLike this, we can now unconditionally discard the index when being asked\nto clear the `unpack_trees_options`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n t/t3705-add-sparse-checkout.sh | 1 +\n unpack-trees.c                 | 2 ++\n 2 files changed, 3 insertions(+)\n\ndiff --git a/t/t3705-add-sparse-checkout.sh b/t/t3705-add-sparse-checkout.sh\nindex 2bade9e804..6ae45a788d 100755\n--- a/t/t3705-add-sparse-checkout.sh\n+++ b/t/t3705-add-sparse-checkout.sh\n@@ -2,6 +2,7 @@\n \n test_description='git add in sparse checked out working trees'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n SPARSE_ENTRY_BLOB=\"\"\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 7dc884fafd..9a55cb6204 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -210,6 +210,7 @@ void clear_unpack_trees_porcelain(struct unpack_trees_options *opts)\n {\n \tstrvec_clear(&opts->internal.msgs_to_free);\n \tmemset(opts->internal.msgs, 0, sizeof(opts->internal.msgs));\n+\tdiscard_index(&opts->internal.result);\n }\n \n static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce,\n@@ -2082,6 +2083,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options\n \t\to->internal.result.updated_workdir = 1;\n \t\tdiscard_index(o->dst_index);\n \t\t*o->dst_index = o->internal.result;\n+\t\tmemset(&o->internal.result, 0, sizeof(o->internal.result));\n \t} else {\n \t\tdiscard_index(&o->internal.result);\n \t}\n-- \n2.46.0.dirty\n\n"},{"id":"500185","messageId":"c6db8df324b4a33a73e97071c99fc9df62471cd4.1722933642.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"[PATCH 17/22] diff: fix leak when parsing invalid ignore regex option","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-06T09:00:54Z","receivedAt":"2024-08-06T09:00:58Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When parsing invalid ignore regexes passed via the `-I` option we don't\nfree already-allocated memory, leading to a memory leak. Fix this.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n diff.c                  | 6 +++++-\n t/t4013-diff-various.sh | 1 +\n 2 files changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/diff.c b/diff.c\nindex ebb7538e04..9251c47b72 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -5464,9 +5464,13 @@ static int diff_opt_ignore_regex(const struct option *opt,\n \tregex_t *regex;\n \n \tBUG_ON_OPT_NEG(unset);\n+\n \tregex = xmalloc(sizeof(*regex));\n-\tif (regcomp(regex, arg, REG_EXTENDED | REG_NEWLINE))\n+\tif (regcomp(regex, arg, REG_EXTENDED | REG_NEWLINE)) {\n+\t\tfree(regex);\n \t\treturn error(_(\"invalid regex given to -I: '%s'\"), arg);\n+\t}\n+\n \tALLOC_GROW(options->ignore_regex, options->ignore_regex_nr + 1,\n \t\t   options->ignore_regex_alloc);\n \toptions->ignore_regex[options->ignore_regex_nr++] = regex;\ndiff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh\nindex 3855d68dbc..87d248d034 100755\n--- a/t/t4013-diff-various.sh\n+++ b/t/t4013-diff-various.sh\n@@ -8,6 +8,7 @@ test_description='Various diff formatting options'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-diff.sh\n \n-- \n2.46.0.dirty\n\n"},{"id":"500186","messageId":"bf818a8a79385af739345e22c74550acd5f31a0b.1722933643.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"[PATCH 18/22] builtin/format-patch: fix various trivial memory leaks","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-06T09:00:59Z","receivedAt":"2024-08-06T09:01:03Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"There are various memory leaks hit by git-format-patch(1). Basically all\nof them are trivial, except that un-setting `diffopt.no_free` requires\nus to unset the `diffopt.file` because we manually close it already.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/log.c           | 12 +++++++++---\n t/t4014-format-patch.sh |  1 +\n 2 files changed, 10 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex a73a767606..ff997a0d0e 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1833,6 +1833,7 @@ static struct commit *get_base_commit(const struct format_config *cfg,\n \t\t\t}\n \n \t\t\trev[i] = merge_base->item;\n+\t\t\tfree_commit_list(merge_base);\n \t\t}\n \n \t\tif (rev_nr % 2)\n@@ -2023,6 +2024,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \tconst char *rfc = NULL;\n \tint creation_factor = -1;\n \tconst char *signature = git_version_string;\n+\tchar *signature_to_free = NULL;\n \tchar *signature_file_arg = NULL;\n \tstruct keep_callback_data keep_callback_data = {\n \t\t.cfg = &cfg,\n@@ -2443,7 +2445,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \n \t\tif (strbuf_read_file(&buf, signature_file, 128) < 0)\n \t\t\tdie_errno(_(\"unable to read signature file '%s'\"), signature_file);\n-\t\tsignature = strbuf_detach(&buf, NULL);\n+\t\tsignature = signature_to_free = strbuf_detach(&buf, NULL);\n \t} else if (cfg.signature) {\n \t\tsignature = cfg.signature;\n \t}\n@@ -2548,12 +2550,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \t\t\telse\n \t\t\t\tprint_signature(signature, rev.diffopt.file);\n \t\t}\n-\t\tif (output_directory)\n+\t\tif (output_directory) {\n \t\t\tfclose(rev.diffopt.file);\n+\t\t\trev.diffopt.file = NULL;\n+\t\t}\n \t}\n \tstop_progress(&progress);\n \tfree(list);\n-\tfree(branch_name);\n \tif (ignore_if_in_upstream)\n \t\tfree_patch_ids(&ids);\n \n@@ -2565,11 +2568,14 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \tstrbuf_release(&rdiff_title);\n \tfree(description_file);\n \tfree(signature_file_arg);\n+\tfree(signature_to_free);\n+\tfree(branch_name);\n \tfree(to_free);\n \tfree(rev.message_id);\n \tif (rev.ref_message_ids)\n \t\tstring_list_clear(rev.ref_message_ids, 0);\n \tfree(rev.ref_message_ids);\n+\trev.diffopt.no_free = 0;\n \trelease_revisions(&rev);\n \tformat_config_release(&cfg);\n \treturn 0;\ndiff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh\nindex 884f83fb8a..1c46e963e4 100755\n--- a/t/t4014-format-patch.sh\n+++ b/t/t4014-format-patch.sh\n@@ -8,6 +8,7 @@ test_description='various format-patch tests'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-terminal.sh\n \n-- \n2.46.0.dirty\n\n"},{"id":"500187","messageId":"ef780aa36039560fd069ec97ce87665eb0775200.1722933643.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"[PATCH 19/22] userdiff: fix leaking memory for configured diff drivers","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-06T09:01:03Z","receivedAt":"2024-08-06T09:01:07Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The userdiff structures may be initialized either statically on the\nstack or dynamically via configuration keys. In the latter case we end\nup leaking memory because we didn't have any infrastructure to discern\nthose strings which have been allocated statically and those which have\nbeen allocated dynamically.\n\nRefactor the code such that we have two pointers for each of these\nstrings: one that holds the value as accessed by other subsystems, and\none that points to the same string in case it has been allocated. Like\nthis, we can safely free the second pointer and thus plug those memory\nleaks.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n range-diff.c                     |  6 +++--\n t/t4018-diff-funcname.sh         |  1 +\n t/t4042-diff-textconv-caching.sh |  2 ++\n t/t4048-diff-combined-binary.sh  |  1 +\n t/t4209-log-pickaxe.sh           |  2 ++\n userdiff.c                       | 38 ++++++++++++++++++++++++--------\n userdiff.h                       |  4 ++++\n 7 files changed, 43 insertions(+), 11 deletions(-)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex 5f01605550..bbb0952264 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -450,8 +450,10 @@ static void output_pair_header(struct diff_options *diffopt,\n }\n \n static struct userdiff_driver section_headers = {\n-\t.funcname = { \"^ ## (.*) ##$\\n\"\n-\t\t      \"^.?@@ (.*)$\", REG_EXTENDED }\n+\t.funcname = {\n+\t\t.pattern = \"^ ## (.*) ##$\\n^.?@@ (.*)$\",\n+\t\t.cflags = REG_EXTENDED,\n+\t},\n };\n \n static struct diff_filespec *get_filespec(const char *name, const char *p)\ndiff --git a/t/t4018-diff-funcname.sh b/t/t4018-diff-funcname.sh\nindex e026fac1f4..8128c30e7f 100755\n--- a/t/t4018-diff-funcname.sh\n+++ b/t/t4018-diff-funcname.sh\n@@ -5,6 +5,7 @@\n \n test_description='Test custom diff function name patterns'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup' '\ndiff --git a/t/t4042-diff-textconv-caching.sh b/t/t4042-diff-textconv-caching.sh\nindex 8ebfa3c1be..a179205394 100755\n--- a/t/t4042-diff-textconv-caching.sh\n+++ b/t/t4042-diff-textconv-caching.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='test textconv caching'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n cat >helper <<'EOF'\ndiff --git a/t/t4048-diff-combined-binary.sh b/t/t4048-diff-combined-binary.sh\nindex 0260cf64f5..f399484bce 100755\n--- a/t/t4048-diff-combined-binary.sh\n+++ b/t/t4048-diff-combined-binary.sh\n@@ -4,6 +4,7 @@ test_description='combined and merge diff handle binary files and textconv'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup binary merge conflict' '\ndiff --git a/t/t4209-log-pickaxe.sh b/t/t4209-log-pickaxe.sh\nindex 64e1623733..b42fdc54fc 100755\n--- a/t/t4209-log-pickaxe.sh\n+++ b/t/t4209-log-pickaxe.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='log --grep/--author/--regexp-ignore-case/-S/-G'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_log () {\ndiff --git a/userdiff.c b/userdiff.c\nindex c4ebb9ff73..989629149f 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -399,8 +399,11 @@ static struct userdiff_driver *userdiff_find_by_namelen(const char *name, size_t\n static int parse_funcname(struct userdiff_funcname *f, const char *k,\n \t\tconst char *v, int cflags)\n {\n-\tif (git_config_string((char **) &f->pattern, k, v) < 0)\n+\tf->pattern = NULL;\n+\tFREE_AND_NULL(f->pattern_owned);\n+\tif (git_config_string(&f->pattern_owned, k, v) < 0)\n \t\treturn -1;\n+\tf->pattern = f->pattern_owned;\n \tf->cflags = cflags;\n \treturn 0;\n }\n@@ -444,20 +447,37 @@ int userdiff_config(const char *k, const char *v)\n \t\treturn parse_funcname(&drv->funcname, k, v, REG_EXTENDED);\n \tif (!strcmp(type, \"binary\"))\n \t\treturn parse_tristate(&drv->binary, k, v);\n-\tif (!strcmp(type, \"command\"))\n-\t\treturn git_config_string((char **) &drv->external.cmd, k, v);\n+\tif (!strcmp(type, \"command\")) {\n+\t\tFREE_AND_NULL(drv->external.cmd);\n+\t\treturn git_config_string(&drv->external.cmd, k, v);\n+\t}\n \tif (!strcmp(type, \"trustexitcode\")) {\n \t\tdrv->external.trust_exit_code = git_config_bool(k, v);\n \t\treturn 0;\n \t}\n-\tif (!strcmp(type, \"textconv\"))\n-\t\treturn git_config_string((char **) &drv->textconv, k, v);\n+\tif (!strcmp(type, \"textconv\")) {\n+\t\tint ret;\n+\t\tFREE_AND_NULL(drv->textconv_owned);\n+\t\tret = git_config_string(&drv->textconv_owned, k, v);\n+\t\tdrv->textconv = drv->textconv_owned;\n+\t\treturn ret;\n+\t}\n \tif (!strcmp(type, \"cachetextconv\"))\n \t\treturn parse_bool(&drv->textconv_want_cache, k, v);\n-\tif (!strcmp(type, \"wordregex\"))\n-\t\treturn git_config_string((char **) &drv->word_regex, k, v);\n-\tif (!strcmp(type, \"algorithm\"))\n-\t\treturn git_config_string((char **) &drv->algorithm, k, v);\n+\tif (!strcmp(type, \"wordregex\")) {\n+\t\tint ret;\n+\t\tFREE_AND_NULL(drv->word_regex_owned);\n+\t\tret = git_config_string(&drv->word_regex_owned, k, v);\n+\t\tdrv->word_regex = drv->word_regex_owned;\n+\t\treturn ret;\n+\t}\n+\tif (!strcmp(type, \"algorithm\")) {\n+\t\tint ret;\n+\t\tFREE_AND_NULL(drv->algorithm_owned);\n+\t\tret = git_config_string(&drv->algorithm_owned, k, v);\n+\t\tdrv->algorithm = drv->algorithm_owned;\n+\t\treturn ret;\n+\t}\n \n \treturn 0;\n }\ndiff --git a/userdiff.h b/userdiff.h\nindex 7565930337..827361b0bc 100644\n--- a/userdiff.h\n+++ b/userdiff.h\n@@ -8,6 +8,7 @@ struct repository;\n \n struct userdiff_funcname {\n \tconst char *pattern;\n+\tchar *pattern_owned;\n \tint cflags;\n };\n \n@@ -20,11 +21,14 @@ struct userdiff_driver {\n \tconst char *name;\n \tstruct external_diff external;\n \tconst char *algorithm;\n+\tchar *algorithm_owned;\n \tint binary;\n \tstruct userdiff_funcname funcname;\n \tconst char *word_regex;\n+\tchar *word_regex_owned;\n \tconst char *word_regex_multi_byte;\n \tconst char *textconv;\n+\tchar *textconv_owned;\n \tstruct notes_cache *textconv_cache;\n \tint textconv_want_cache;\n };\n-- \n2.46.0.dirty\n\n"},{"id":"500188","messageId":"f3882986a37c8f6b2f532c955bd5609842414e60.1722933643.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"[PATCH 20/22] builtin/log: fix leak when showing converted blob contents","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-06T09:01:08Z","receivedAt":"2024-08-06T09:01:13Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `show_blob_object()`, we proactively call `textconv_object()`. In\ncase we have a textconv driver for this blob we will end up showing the\nconverted contents, otherwise we'll show the un-converted contents of it\ninstead.\n\nWhen the object has been converted we never free the buffer containing\nthe converted contents. Fix this to plug this memory leak.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/log.c            | 1 +\n t/t4030-diff-textconv.sh | 2 ++\n 2 files changed, 3 insertions(+)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex ff997a0d0e..1a684b68f2 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -707,6 +707,7 @@ static int show_blob_object(const struct object_id *oid, struct rev_info *rev, c\n \n \twrite_or_die(1, buf, size);\n \tobject_context_release(&obj_context);\n+\tfree(buf);\n \treturn 0;\n }\n \ndiff --git a/t/t4030-diff-textconv.sh b/t/t4030-diff-textconv.sh\nindex a39a626664..29f6d610c2 100755\n--- a/t/t4030-diff-textconv.sh\n+++ b/t/t4030-diff-textconv.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='diff.*.textconv tests'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n find_diff() {\n-- \n2.46.0.dirty\n\n"},{"id":"500189","messageId":"a49bb2e0cc0e89c8dbd49e56169b5ed17f8faa54.1722933643.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"[PATCH 21/22] diff: free state populated via options","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-06T09:01:13Z","receivedAt":"2024-08-06T09:01:16Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `objfind` and `anchors` members of `struct diff_options` are\npopulated via option parsing, but are never freed in `diff_free()`. Fix\nthis to plug those memory leaks.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n diff.c                   | 10 ++++++++++\n t/t4064-diff-oidfind.sh  |  2 ++\n t/t4065-diff-anchored.sh |  1 +\n t/t4069-remerge-diff.sh  |  1 +\n 4 files changed, 14 insertions(+)\n\ndiff --git a/diff.c b/diff.c\nindex 9251c47b72..4035a9374d 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -6717,6 +6717,16 @@ void diff_free(struct diff_options *options)\n \tif (options->no_free)\n \t\treturn;\n \n+\tif (options->objfind) {\n+\t\toidset_clear(options->objfind);\n+\t\tFREE_AND_NULL(options->objfind);\n+\t}\n+\n+\tfor (size_t i = 0; i < options->anchors_nr; i++)\n+\t\tfree(options->anchors[i]);\n+\tFREE_AND_NULL(options->anchors);\n+\toptions->anchors_nr = options->anchors_alloc = 0;\n+\n \tdiff_free_file(options);\n \tdiff_free_ignore_regex(options);\n \tclear_pathspec(&options->pathspec);\ndiff --git a/t/t4064-diff-oidfind.sh b/t/t4064-diff-oidfind.sh\nindex 6d8c8986fc..846f285f77 100755\n--- a/t/t4064-diff-oidfind.sh\n+++ b/t/t4064-diff-oidfind.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='test finding specific blobs in the revision walking'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup ' '\ndiff --git a/t/t4065-diff-anchored.sh b/t/t4065-diff-anchored.sh\nindex b3f510f040..647537c12e 100755\n--- a/t/t4065-diff-anchored.sh\n+++ b/t/t4065-diff-anchored.sh\n@@ -2,6 +2,7 @@\n \n test_description='anchored diff algorithm'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success '--anchored' '\ndiff --git a/t/t4069-remerge-diff.sh b/t/t4069-remerge-diff.sh\nindex 07323ebafe..888714bbd3 100755\n--- a/t/t4069-remerge-diff.sh\n+++ b/t/t4069-remerge-diff.sh\n@@ -2,6 +2,7 @@\n \n test_description='remerge-diff handling'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # This test is ort-specific\n-- \n2.46.0.dirty\n\n"},{"id":"500190","messageId":"fb525994043434be21c31bf0226056302f482f54.1722933643.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"[PATCH 22/22] builtin/diff: free symmetric diff members","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-06T09:01:18Z","receivedAt":"2024-08-06T09:01:21Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We populate a `struct symdiff` in case the user has requested a\nsymmetric diff. Part of this is to populate a `skip` bitmap that\nindicates whihc commits shall be ignored in the diff. But while this\nbitmap is dynamically allocated, we never free it.\n\nFix this by introducing and calling a new `symdiff_release()` function\nthat does this for us.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/diff.c                       | 10 +++++++++-\n t/t4068-diff-symmetric-merge-base.sh |  1 +\n t/t4108-apply-threeway.sh            |  1 +\n 3 files changed, 11 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/diff.c b/builtin/diff.c\nindex 9b6cdabe15..f87f68a5bc 100644\n--- a/builtin/diff.c\n+++ b/builtin/diff.c\n@@ -388,6 +388,13 @@ static void symdiff_prepare(struct rev_info *rev, struct symdiff *sym)\n \tsym->skip = map;\n }\n \n+static void symdiff_release(struct symdiff *sdiff)\n+{\n+\tif (!sdiff)\n+\t\treturn;\n+\tbitmap_free(sdiff->skip);\n+}\n+\n int cmd_diff(int argc, const char **argv, const char *prefix)\n {\n \tint i;\n@@ -398,7 +405,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)\n \tstruct object_array_entry *blob[2];\n \tint nongit = 0, no_index = 0;\n \tint result;\n-\tstruct symdiff sdiff;\n+\tstruct symdiff sdiff = {0};\n \n \t/*\n \t * We could get N tree-ish in the rev.pending_objects list.\n@@ -619,6 +626,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)\n \t\trefresh_index_quietly();\n \trelease_revisions(&rev);\n \tobject_array_clear(&ent);\n+\tsymdiff_release(&sdiff);\n \tUNLEAK(blob);\n \treturn result;\n }\ndiff --git a/t/t4068-diff-symmetric-merge-base.sh b/t/t4068-diff-symmetric-merge-base.sh\nindex eff63c16b0..4d6565e728 100755\n--- a/t/t4068-diff-symmetric-merge-base.sh\n+++ b/t/t4068-diff-symmetric-merge-base.sh\n@@ -5,6 +5,7 @@ test_description='behavior of diff with symmetric-diff setups and --merge-base'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # build these situations:\ndiff --git a/t/t4108-apply-threeway.sh b/t/t4108-apply-threeway.sh\nindex c558282bc0..3211e1e65f 100755\n--- a/t/t4108-apply-threeway.sh\n+++ b/t/t4108-apply-threeway.sh\n@@ -5,6 +5,7 @@ test_description='git apply --3way'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n print_sanitized_conflicted_diff () {\n-- \n2.46.0.dirty\n\n"},{"id":"500258","messageId":"D39DPCW06C28.2EYBPWPBSQ5G6@jamesliu.io","threadId":"61910","inReplyTo":"9574995a246d96b90f03827bf0ba591593d6c4d9.1722933642.git.ps@pks.im","subject":"Re: [PATCH 02/22] git: fix leaking system paths","fromName":"James Liu","fromEmail":"james@jamesliu.io","sentAt":"2024-08-07T04:02:06Z","receivedAt":"2024-08-07T04:02:10Z","isPatch":true,"sender":{"key":"james@jamesliu.io","avatar":"https://avatars.githubusercontent.com/u/4476568?v=4"},"body":"On Tue Aug 6, 2024 at 6:59 PM AEST, Patrick Steinhardt wrote:\n> Git has some flags to make it output system paths as they have been\n> compiled into Git. This is done by calling `system_path()`, which\n> returns an allocated string. This string isn't ever free'd though,\n> creating a memory leak.\n>\n> Plug those leaks. While they are surfaced by t0211, there are more\n> memory leaks looming exposed by that test suite and it thus does not yet\n> pass with the memory leak checker enabled.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  git.c | 12 +++++++++---\n>  1 file changed, 9 insertions(+), 3 deletions(-)\n>\n> diff --git a/git.c b/git.c\n> index e35af9b0e5..5eab88b472 100644\n> --- a/git.c\n> +++ b/git.c\n> @@ -173,15 +173,21 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n>  \t\t\t\texit(0);\n>  \t\t\t}\n>  \t\t} else if (!strcmp(cmd, \"--html-path\")) {\n> -\t\t\tputs(system_path(GIT_HTML_PATH));\n> +\t\t\tchar *path = system_path(GIT_HTML_PATH);\n> +\t\t\tputs(path);\n> +\t\t\tfree(path);\n>  \t\t\ttrace2_cmd_name(\"_query_\");\n>  \t\t\texit(0);\n>  \t\t} else if (!strcmp(cmd, \"--man-path\")) {\n> -\t\t\tputs(system_path(GIT_MAN_PATH));\n> +\t\t\tchar *path = system_path(GIT_MAN_PATH);\n> +\t\t\tputs(path);\n> +\t\t\tfree(path);\n>  \t\t\ttrace2_cmd_name(\"_query_\");\n>  \t\t\texit(0);\n>  \t\t} else if (!strcmp(cmd, \"--info-path\")) {\n> -\t\t\tputs(system_path(GIT_INFO_PATH));\n> +\t\t\tchar *path = system_path(GIT_INFO_PATH);\n> +\t\t\tputs(path);\n> +\t\t\tfree(path);\n>  \t\t\ttrace2_cmd_name(\"_query_\");\n>  \t\t\texit(0);\n>  \t\t} else if (!strcmp(cmd, \"-p\") || !strcmp(cmd, \"--paginate\")) {\n\nOh interesting. These don't immediately stand out as leaky due to the\nabsence of intermediate variables, but nevertheless an allocation took\nplace that we need to free.\n\n"},{"id":"500291","messageId":"D39HIJOL7UTK.29BEN5ZXSH4KF@jamesliu.io","threadId":"61910","inReplyTo":"2810cada0af14c3331674a807b20d18e6c9af022.1722933642.git.ps@pks.im","subject":"Re: [PATCH 06/22] read-cache: fix leaking hashfile when writing index fails","fromName":"James Liu","fromEmail":"james@jamesliu.io","sentAt":"2024-08-07T07:01:17Z","receivedAt":"2024-08-07T07:01:20Z","isPatch":true,"sender":{"key":"james@jamesliu.io","avatar":"https://avatars.githubusercontent.com/u/4476568?v=4"},"body":"On Tue Aug 6, 2024 at 7:00 PM AEST, Patrick Steinhardt wrote:\n> Refactor the code to have a common exit path where we can free this and\n> other allocated memory. While at it, refactor our use of `strbuf`s such\n> that we reuse the same buffer to avoid some unneeded allocations.\n>\n> @@ -3105,7 +3117,14 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n>  \ttrace2_data_intmax(\"index\", the_repository, \"write/cache_nr\",\n>  \t\t\t   istate->cache_nr);\n>  \n> -\treturn 0;\n> +\tret = 0;\n> +\n> +out:\n> +\tif (f)\n> +\t\tfree_hashfile(f);\n> +\tstrbuf_release(&sb);\n> +\tfree(ieot);\n> +\treturn ret;\n>  }\n\nIs it generally a pattern in Git to use `goto <label>` instead of\nreturns when there are multiple return points in a function? We're also\nperforming cleanup duties here and in most of those scenarios but there\nare some cases like `reftable_be_pack_refs()` where the goto simply\ncollapses multiple return points into a single path.\n\n"},{"id":"500292","messageId":"D39HQCFQ2DCW.1KFGPLVU1EVQD@jamesliu.io","threadId":"61910","inReplyTo":"a34c90a5527cb45ec89a0ad44dbca1d61705a0ea.1722933642.git.ps@pks.im","subject":"Re: [PATCH 08/22] config: fix leaking comment character config","fromName":"James Liu","fromEmail":"james@jamesliu.io","sentAt":"2024-08-07T07:11:28Z","receivedAt":"2024-08-07T07:11:32Z","isPatch":true,"sender":{"key":"james@jamesliu.io","avatar":"https://avatars.githubusercontent.com/u/4476568?v=4"},"body":"On Tue Aug 6, 2024 at 7:00 PM AEST, Patrick Steinhardt wrote:\n> Refactor the code so that we initialize the value with another array.\n> This allows us to free the value in case the string is not pointing to\n> that constant array anymore.\n>\n> diff --git a/environment.c b/environment.c\n> index 5cea2c9f54..8297c6e37b 100644\n> --- a/environment.c\n> +++ b/environment.c\n> @@ -113,7 +113,8 @@ int protect_ntfs = PROTECT_NTFS_DEFAULT;\n>   * The character that begins a commented line in user-editable file\n>   * that is subject to stripspace.\n>   */\n> -const char *comment_line_str = \"#\";\n> +const char comment_line_str_default[] = \"#\";\n> +const char *comment_line_str = comment_line_str_default;\n>  int auto_comment_line_char;\n>  \n>  /* Parallel index stat data preload? */\n\nIs my understanding correct that `comment_line_str` is now just a\npointer to the `comment_line_str_default` array, and thus can be freed\nonce we're done with it?\n\n"},{"id":"500293","messageId":"D39I6DZWV40G.T5O4SNGH9OIX@jamesliu.io","threadId":"61910","inReplyTo":"05290fc1f14cae8229c42f2d0aafe6619c069e3a.1722933642.git.ps@pks.im","subject":"Re: [PATCH 09/22] builtin/rebase: fix leaking `commit.gpgsign` value","fromName":"James Liu","fromEmail":"james@jamesliu.io","sentAt":"2024-08-07T07:32:25Z","receivedAt":"2024-08-07T07:32:29Z","isPatch":true,"sender":{"key":"james@jamesliu.io","avatar":"https://avatars.githubusercontent.com/u/4476568?v=4"},"body":"On Tue Aug 6, 2024 at 7:00 PM AEST, Patrick Steinhardt wrote:\n> In `get_replay_opts()`, we unconditionally override the `gpg_sign` field\n> that already got populated by `sequencer_init_config()` in case the user\n> has \"commit.gpgsign\" set in their config. It is kind of dubious whether\n> this is the correct thing to do or a bug. What is clear though is that\n> this creates a memory leak.\n>\n> Let's mark this assignment with a TODO comment to figure out whether\n> this needs to be fixed or not. Meanwhile though, let's plug the memory\n> leak.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/rebase.c              | 8 ++++++++\n>  sequencer.c                   | 1 +\n>  t/t3404-rebase-interactive.sh | 1 +\n>  t/t3435-rebase-gpg-sign.sh    | 1 +\n>  t/t7030-verify-tag.sh         | 1 +\n>  5 files changed, 12 insertions(+)\n>\n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index e3a8e74cfc..f65316a023 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -186,7 +186,15 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n>  \treplay.committer_date_is_author_date =\n>  \t\t\t\t\topts->committer_date_is_author_date;\n>  \treplay.ignore_date = opts->ignore_date;\n> +\n> +\t/*\n> +\t * TODO: Is it really intentional that we unconditionally override\n> +\t * `replay.gpg_sign` even if it has already been initialized via the\n> +\t * configuration?\n> +\t */\n> +\tfree(replay.gpg_sign);\n>  \treplay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);\n> +\n>  \treplay.reflog_action = xstrdup(opts->reflog_action);\n>  \tif (opts->strategy)\n>  \t\treplay.strategy = xstrdup_or_null(opts->strategy);\n> diff --git a/sequencer.c b/sequencer.c\n> index 0291920f0b..cade9b0ca8 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -303,6 +303,7 @@ static int git_sequencer_config(const char *k, const char *v,\n>  \t}\n>  \n>  \tif (!strcmp(k, \"commit.gpgsign\")) {\n> +\t\tfree(opts->gpg_sign);\n>  \t\topts->gpg_sign = git_config_bool(k, v) ? xstrdup(\"\") : NULL;\n>  \t\treturn 0;\n>  \t}\n\nIt looks like this free'ing would be managed by the caller by invoking\n`replay_opts_release()`, but it's not being done consistently.\n\nFor example, `do_interactive_rebase()` invokes `replay_opts_release()`,\nbut `run_sequencer_rebase()` does not. Would it be better to address the\nleak here?\n\n"},{"id":"500297","messageId":"D39JFO40F1FC.2Q22EY440N67N@jamesliu.io","threadId":"61910","inReplyTo":"64366155dee25209ab9c434016c5918d47d7e1d5.1722933642.git.ps@pks.im","subject":"Re: [PATCH 13/22] builtin/fast-export: plug leaking tag names","fromName":"James Liu","fromEmail":"james@jamesliu.io","sentAt":"2024-08-07T08:31:33Z","receivedAt":"2024-08-07T08:31:37Z","isPatch":true,"sender":{"key":"james@jamesliu.io","avatar":"https://avatars.githubusercontent.com/u/4476568?v=4"},"body":"On Tue Aug 6, 2024 at 7:00 PM AEST, Patrick Steinhardt wrote:\n> Refactor the code to make the lists we put those names into duplicate\n> the memory. This allows us to properly free the string as required and\n> thus plugs the memory leak.\n>\n> While this requires us to allocate more data overall, it shouldn't be\n> all that bad given that the number of allocations corresponds with the\n> number of command line parameters, which typically aren't all that many.\n\nAhh so using the `STRING_LIST_INIT_DUP` initialiser means that every\ntime we call `string_list_append()` on the list, we retain ownership of\nthe string and the list gets its own copy.\n\nThat means we're able to free our own copy later on.\n\n"},{"id":"500298","messageId":"D39JV83QXD5C.3U74JE9YD5SGO@jamesliu.io","threadId":"61910","inReplyTo":"bf818a8a79385af739345e22c74550acd5f31a0b.1722933643.git.ps@pks.im","subject":"Re: [PATCH 18/22] builtin/format-patch: fix various trivial memory leaks","fromName":"James Liu","fromEmail":"james@jamesliu.io","sentAt":"2024-08-07T08:51:52Z","receivedAt":"2024-08-07T08:51:56Z","isPatch":true,"sender":{"key":"james@jamesliu.io","avatar":"https://avatars.githubusercontent.com/u/4476568?v=4"},"body":"> diff --git a/builtin/log.c b/builtin/log.c\n> index a73a767606..ff997a0d0e 100644\n> --- a/builtin/log.c\n> +++ b/builtin/log.c\n> @@ -2023,6 +2024,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n>  \tconst char *rfc = NULL;\n>  \tint creation_factor = -1;\n>  \tconst char *signature = git_version_string;\n> +\tchar *signature_to_free = NULL;\n>  \tchar *signature_file_arg = NULL;\n>  \tstruct keep_callback_data keep_callback_data = {\n>  \t\t.cfg = &cfg,\n> @@ -2443,7 +2445,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n>  \n>  \t\tif (strbuf_read_file(&buf, signature_file, 128) < 0)\n>  \t\t\tdie_errno(_(\"unable to read signature file '%s'\"), signature_file);\n> -\t\tsignature = strbuf_detach(&buf, NULL);\n> +\t\tsignature = signature_to_free = strbuf_detach(&buf, NULL);\n\nDo I understand this correctly, that the multiple assignment here allows\nus to maintain a reference to the pointer returned by `strbuf_detach()`\nin `signature_to_free`, and we do this because `signature` can take on a\ndifferent value below?\n\n>  \t} else if (cfg.signature) {\n>  \t\tsignature = cfg.signature;\n>  \t}\n\n\n"},{"id":"500299","messageId":"D39KL8N6S0HC.HFB9VRAVWYOX@jamesliu.io","threadId":"61910","inReplyTo":"ef780aa36039560fd069ec97ce87665eb0775200.1722933643.git.ps@pks.im","subject":"Re: [PATCH 19/22] userdiff: fix leaking memory for configured diff drivers","fromName":"James Liu","fromEmail":"james@jamesliu.io","sentAt":"2024-08-07T09:25:51Z","receivedAt":"2024-08-07T09:25:55Z","isPatch":true,"sender":{"key":"james@jamesliu.io","avatar":"https://avatars.githubusercontent.com/u/4476568?v=4"},"body":"> Refactor the code such that we have two pointers for each of these\n> strings: one that holds the value as accessed by other subsystems, and\n> one that points to the same string in case it has been allocated. Like\n> this, we can safely free the second pointer and thus plug those memory\n> leaks.\n>\n> diff --git a/userdiff.c b/userdiff.c\n> index c4ebb9ff73..989629149f 100644\n> --- a/userdiff.c\n> +++ b/userdiff.c\n> @@ -399,8 +399,11 @@ static struct userdiff_driver *userdiff_find_by_namelen(const char *name, size_t\n>  static int parse_funcname(struct userdiff_funcname *f, const char *k,\n>  \t\tconst char *v, int cflags)\n>  {\n> -\tif (git_config_string((char **) &f->pattern, k, v) < 0)\n> +\tf->pattern = NULL;\n> +\tFREE_AND_NULL(f->pattern_owned);\n> +\tif (git_config_string(&f->pattern_owned, k, v) < 0)\n>  \t\treturn -1;\n> +\tf->pattern = f->pattern_owned;\n>  \tf->cflags = cflags;\n>  \treturn 0;\n>  }\n\nI'm not sure if I understand this change completely. We don't seem to be\nusing `pattern_owned` (and the other *_owned) fields differently from\ntheir regular counterparts.\n\nIs it because we can't do the following?\n\n        FREE_AND_NULL((char **)f->pattern);\n\n"},{"id":"500300","messageId":"D39KMJ3FXBZC.7S74VUTPEQHY@jamesliu.io","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"Re: [PATCH 00/22] Memory leak fixes (pt.4)","fromName":"James Liu","fromEmail":"james@jamesliu.io","sentAt":"2024-08-07T09:27:32Z","receivedAt":"2024-08-07T09:27:36Z","isPatch":true,"sender":{"key":"james@jamesliu.io","avatar":"https://avatars.githubusercontent.com/u/4476568?v=4"},"body":"On Tue Aug 6, 2024 at 6:59 PM AEST, Patrick Steinhardt wrote:\n> Hi,\n>\n> the third set of memory leak fixes was merged to `next`, so this is the\n> next part of more or less random memory leak fixes all over the place.\n> With this series, we're at ~155 leaking test suites. Naturally, I've\n> already got v5 in the pipeline, which brings us down to ~120.\n>\n> The series is built on top of 406f326d27 (The second batch, 2024-08-01)\n> with ps/leakfixes-part-3 at f30bfafcd4 (commit-reach: fix trivial memory\n> leak when computing reachability, 2024-08-01) merged into it.\n>\n> Thanks!\n>\n> Patrick\n\nThanks Patrick, most of these fixes make sense to me! I appreciate that\neven the minor changes are accompanied by context.\n\nCheers,\nJames\n"},{"id":"500336","messageId":"xmqqy158nu9w.fsf@gitster.g","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"Re: [PATCH 00/22] Memory leak fixes (pt.4)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-08-07T16:59:39Z","receivedAt":"2024-08-07T16:59:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The series is built on top of 406f326d27 (The second batch, 2024-08-01)\n> with ps/leakfixes-part-3 at f30bfafcd4 (commit-reach: fix trivial memory\n> leak when computing reachability, 2024-08-01) merged into it.\n\nA quick question.  Is it on your radar that transport_get() leaks\nthe helper name when \"foo::bar\" is given as a remote?\n\n  https://github.com/git/git/actions/runs/10274435719/job/28431161208#step:5:893\n\nIf not, I'll handle it separately, whose fix should look something\nlike the attached.\n\nThanks.\n\n transport.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git c/transport.c w/transport.c\nindex 12cc5b4d96..13bf8183b7 100644\n--- c/transport.c\n+++ w/transport.c\n@@ -1115,6 +1115,7 @@ static struct transport_vtable builtin_smart_vtable = {\n struct transport *transport_get(struct remote *remote, const char *url)\n {\n \tconst char *helper;\n+\tchar *helper_to_free = NULL;\n \tconst char *p;\n \tstruct transport *ret = xcalloc(1, sizeof(*ret));\n \n@@ -1139,10 +1140,11 @@ struct transport *transport_get(struct remote *remote, const char *url)\n \twhile (is_urlschemechar(p == url, *p))\n \t\tp++;\n \tif (starts_with(p, \"::\"))\n-\t\thelper = xstrndup(url, p - url);\n+\t\thelper_to_free = helper = xstrndup(url, p - url);\n \n \tif (helper) {\n \t\ttransport_helper_init(ret, helper);\n+\t\tfree(helper_to_free);\n \t} else if (starts_with(url, \"rsync:\")) {\n \t\tdie(_(\"git-over-rsync is no longer supported\"));\n \t} else if (url_is_local_not_ssh(url) && is_file(url) && is_bundle(url, 1)) {\n\n"},{"id":"500337","messageId":"ZrOo0DuiPeSp9E0b@ncase","threadId":"61910","inReplyTo":"xmqqy158nu9w.fsf@gitster.g","subject":"Re: [PATCH 00/22] Memory leak fixes (pt.4)","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-07T17:03:12Z","receivedAt":"2024-08-07T17:03:18Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Aug 07, 2024 at 09:59:39AM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > The series is built on top of 406f326d27 (The second batch, 2024-08-01)\n> > with ps/leakfixes-part-3 at f30bfafcd4 (commit-reach: fix trivial memory\n> > leak when computing reachability, 2024-08-01) merged into it.\n> \n> A quick question.  Is it on your radar that transport_get() leaks\n> the helper name when \"foo::bar\" is given as a remote?\n> \n>   https://github.com/git/git/actions/runs/10274435719/job/28431161208#step:5:893\n> \n> If not, I'll handle it separately, whose fix should look something\n> like the attached.\n> \n> Thanks.\n\nYeah, it's in part 5 [1], 97613b9cb9 (transport-helper: fix leaking\nhelper name, 2024-05-27). Feel free to handle it separately though, I'll\nwait for part 4 to land first anyway, which likely takes a couple of\ndays.\n\nPatrick\n\n[1]: https://gitlab.com/gitlab-org/git/-/tree/pks-leak-fixes-pt5\n"},{"id":"500376","messageId":"xmqqo763n9b2.fsf@gitster.g","threadId":"61910","inReplyTo":"ZrOo0DuiPeSp9E0b@ncase","subject":"Re: [PATCH 00/22] Memory leak fixes (pt.4)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-08-08T00:32:33Z","receivedAt":"2024-08-08T00:32:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Wed, Aug 07, 2024 at 09:59:39AM -0700, Junio C Hamano wrote:\n>> Patrick Steinhardt <ps@pks.im> writes:\n>> \n>> > The series is built on top of 406f326d27 (The second batch, 2024-08-01)\n>> > with ps/leakfixes-part-3 at f30bfafcd4 (commit-reach: fix trivial memory\n>> > leak when computing reachability, 2024-08-01) merged into it.\n>> \n>> A quick question.  Is it on your radar that transport_get() leaks\n>> the helper name when \"foo::bar\" is given as a remote?\n>> \n>>   https://github.com/git/git/actions/runs/10274435719/job/28431161208#step:5:893\n>> \n>> If not, I'll handle it separately, whose fix should look something\n>> like the attached.\n>> \n>> Thanks.\n>\n> Yeah, it's in part 5 [1], 97613b9cb9 (transport-helper: fix leaking\n> helper name, 2024-05-27). Feel free to handle it separately though, I'll\n> wait for part 4 to land first anyway, which likely takes a couple of\n> days.\n\nOK, will do, as this seems to break CI.\n"},{"id":"500385","messageId":"ZrRR8crXHBGvMdjM@tanuki","threadId":"61910","inReplyTo":"D39HIJOL7UTK.29BEN5ZXSH4KF@jamesliu.io","subject":"Re: [PATCH 06/22] read-cache: fix leaking hashfile when writing index fails","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T05:04:49Z","receivedAt":"2024-08-08T05:04:54Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Aug 07, 2024 at 05:01:17PM +1000, James Liu wrote:\n> On Tue Aug 6, 2024 at 7:00 PM AEST, Patrick Steinhardt wrote:\n> > Refactor the code to have a common exit path where we can free this and\n> > other allocated memory. While at it, refactor our use of `strbuf`s such\n> > that we reuse the same buffer to avoid some unneeded allocations.\n> >\n> > @@ -3105,7 +3117,14 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n> >  \ttrace2_data_intmax(\"index\", the_repository, \"write/cache_nr\",\n> >  \t\t\t   istate->cache_nr);\n> >  \n> > -\treturn 0;\n> > +\tret = 0;\n> > +\n> > +out:\n> > +\tif (f)\n> > +\t\tfree_hashfile(f);\n> > +\tstrbuf_release(&sb);\n> > +\tfree(ieot);\n> > +\treturn ret;\n> >  }\n> \n> Is it generally a pattern in Git to use `goto <label>` instead of\n> returns when there are multiple return points in a function? We're also\n> performing cleanup duties here and in most of those scenarios but there\n> are some cases like `reftable_be_pack_refs()` where the goto simply\n> collapses multiple return points into a single path.\n\nYes, that's usually how we avoid repetetive cleanup code for each of the\nreturn paths. `reftable_be_pack_refs()` is a bit more on the curious\nside as the common exit path doesn't really do helpful. That one could\nhave just as well used plain returns.\n\nPatrick\n"},{"id":"500386","messageId":"ZrRR9mwyQQrw4Dg5@tanuki","threadId":"61910","inReplyTo":"D39HQCFQ2DCW.1KFGPLVU1EVQD@jamesliu.io","subject":"Re: [PATCH 08/22] config: fix leaking comment character config","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T05:04:54Z","receivedAt":"2024-08-08T05:04:58Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Aug 07, 2024 at 05:11:28PM +1000, James Liu wrote:\n> On Tue Aug 6, 2024 at 7:00 PM AEST, Patrick Steinhardt wrote:\n> > Refactor the code so that we initialize the value with another array.\n> > This allows us to free the value in case the string is not pointing to\n> > that constant array anymore.\n> >\n> > diff --git a/environment.c b/environment.c\n> > index 5cea2c9f54..8297c6e37b 100644\n> > --- a/environment.c\n> > +++ b/environment.c\n> > @@ -113,7 +113,8 @@ int protect_ntfs = PROTECT_NTFS_DEFAULT;\n> >   * The character that begins a commented line in user-editable file\n> >   * that is subject to stripspace.\n> >   */\n> > -const char *comment_line_str = \"#\";\n> > +const char comment_line_str_default[] = \"#\";\n> > +const char *comment_line_str = comment_line_str_default;\n> >  int auto_comment_line_char;\n> >  \n> >  /* Parallel index stat data preload? */\n> \n> Is my understanding correct that `comment_line_str` is now just a\n> pointer to the `comment_line_str_default` array, and thus can be freed\n> once we're done with it?\n\nNot quite. By default, `comment_line_str` also points to\ncomment_line_str_default`, which is a string constant and thus neither\nof these variables can be free'd. But what this split allows us to do is\nto check whether `comment_line_str` has changed from the default, and\nthus we can conditionall free it when it does not point to the default\nvalue anymore.\n\nNow that I revisit this commit I'm not quite happy with it anymore. We\nstill need to have the cast, which is somewhat awkward. I think the\nbetter solution is to instead have a `comment_line_str_allocated`\nvariable that is non-constant. I'll adapt the code accordingly.\n\nAn even better solution would be to have `struct strbuf` provide an\ninitializer that populates it with a string constant. But that feels\nlike a larger undertaking, so I'll leave that for the future.\n\nPatrick\n"},{"id":"500387","messageId":"ZrRR_EU_Ptz0hrDi@tanuki","threadId":"61910","inReplyTo":"D39I6DZWV40G.T5O4SNGH9OIX@jamesliu.io","subject":"Re: [PATCH 09/22] builtin/rebase: fix leaking `commit.gpgsign` value","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T05:05:00Z","receivedAt":"2024-08-08T05:05:05Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Aug 07, 2024 at 05:32:25PM +1000, James Liu wrote:\n> On Tue Aug 6, 2024 at 7:00 PM AEST, Patrick Steinhardt wrote:\n> > In `get_replay_opts()`, we unconditionally override the `gpg_sign` field\n> > that already got populated by `sequencer_init_config()` in case the user\n> > has \"commit.gpgsign\" set in their config. It is kind of dubious whether\n> > this is the correct thing to do or a bug. What is clear though is that\n> > this creates a memory leak.\n> >\n> > Let's mark this assignment with a TODO comment to figure out whether\n> > this needs to be fixed or not. Meanwhile though, let's plug the memory\n> > leak.\n> >\n> > Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> > ---\n> >  builtin/rebase.c              | 8 ++++++++\n> >  sequencer.c                   | 1 +\n> >  t/t3404-rebase-interactive.sh | 1 +\n> >  t/t3435-rebase-gpg-sign.sh    | 1 +\n> >  t/t7030-verify-tag.sh         | 1 +\n> >  5 files changed, 12 insertions(+)\n> >\n> > diff --git a/builtin/rebase.c b/builtin/rebase.c\n> > index e3a8e74cfc..f65316a023 100644\n> > --- a/builtin/rebase.c\n> > +++ b/builtin/rebase.c\n> > @@ -186,7 +186,15 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n> >  \treplay.committer_date_is_author_date =\n> >  \t\t\t\t\topts->committer_date_is_author_date;\n> >  \treplay.ignore_date = opts->ignore_date;\n> > +\n> > +\t/*\n> > +\t * TODO: Is it really intentional that we unconditionally override\n> > +\t * `replay.gpg_sign` even if it has already been initialized via the\n> > +\t * configuration?\n> > +\t */\n> > +\tfree(replay.gpg_sign);\n> >  \treplay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);\n> > +\n> >  \treplay.reflog_action = xstrdup(opts->reflog_action);\n> >  \tif (opts->strategy)\n> >  \t\treplay.strategy = xstrdup_or_null(opts->strategy);\n> > diff --git a/sequencer.c b/sequencer.c\n> > index 0291920f0b..cade9b0ca8 100644\n> > --- a/sequencer.c\n> > +++ b/sequencer.c\n> > @@ -303,6 +303,7 @@ static int git_sequencer_config(const char *k, const char *v,\n> >  \t}\n> >  \n> >  \tif (!strcmp(k, \"commit.gpgsign\")) {\n> > +\t\tfree(opts->gpg_sign);\n> >  \t\topts->gpg_sign = git_config_bool(k, v) ? xstrdup(\"\") : NULL;\n> >  \t\treturn 0;\n> >  \t}\n> \n> It looks like this free'ing would be managed by the caller by invoking\n> `replay_opts_release()`, but it's not being done consistently.\n> \n> For example, `do_interactive_rebase()` invokes `replay_opts_release()`,\n> but `run_sequencer_rebase()` does not. Would it be better to address the\n> leak here?\n\nThe problem here isn't that `replay_opts_release()` doesn't free the\nvalues, or that the function isn't called consistently. The problem\nrather is that we assign `opts->gpg_sign` even though it may already be\nassigned an allocated string. Consequently, `replay_opts_release()`\ndoesn't even have a chance to free the old value because it cannot see\nit anymore.\n\nI'll massage the commit message a bit to clarify this.\n\nPatrick\n"},{"id":"500388","messageId":"ZrRSAgWe-GWe_uaO@tanuki","threadId":"61910","inReplyTo":"D39JFO40F1FC.2Q22EY440N67N@jamesliu.io","subject":"Re: [PATCH 13/22] builtin/fast-export: plug leaking tag names","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T05:05:06Z","receivedAt":"2024-08-08T05:05:10Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Aug 07, 2024 at 06:31:33PM +1000, James Liu wrote:\n> On Tue Aug 6, 2024 at 7:00 PM AEST, Patrick Steinhardt wrote:\n> > Refactor the code to make the lists we put those names into duplicate\n> > the memory. This allows us to properly free the string as required and\n> > thus plugs the memory leak.\n> >\n> > While this requires us to allocate more data overall, it shouldn't be\n> > all that bad given that the number of allocations corresponds with the\n> > number of command line parameters, which typically aren't all that many.\n> \n> Ahh so using the `STRING_LIST_INIT_DUP` initialiser means that every\n> time we call `string_list_append()` on the list, we retain ownership of\n> the string and the list gets its own copy.\n> \n> That means we're able to free our own copy later on.\n\nYes, exactly. I think that we really should change the naming though.\nI've repeatedly seen the pattern that people think initializing the list\nwtih `_NODUP` would transfer ownership of inserted strings. It does not\nthough, it simply assumes that the strings will be kept alive by the\ncaller.\n\nThis is made worse by the fact that we have `strvec_insert_nodup()`,\nwhich _does_ transfer ownership. So we're using two different meanings\nfor \"nodup\", so I totally get why people are confused by this interface.\n\nI'll leave that for a separate series though.\n\nPatrick\n"},{"id":"500389","messageId":"ZrRSB46ZxHFU1X-5@tanuki","threadId":"61910","inReplyTo":"D39JV83QXD5C.3U74JE9YD5SGO@jamesliu.io","subject":"Re: [PATCH 18/22] builtin/format-patch: fix various trivial memory leaks","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T05:05:11Z","receivedAt":"2024-08-08T05:05:16Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Aug 07, 2024 at 06:51:52PM +1000, James Liu wrote:\n> > diff --git a/builtin/log.c b/builtin/log.c\n> > index a73a767606..ff997a0d0e 100644\n> > --- a/builtin/log.c\n> > +++ b/builtin/log.c\n> > @@ -2023,6 +2024,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n> >  \tconst char *rfc = NULL;\n> >  \tint creation_factor = -1;\n> >  \tconst char *signature = git_version_string;\n> > +\tchar *signature_to_free = NULL;\n> >  \tchar *signature_file_arg = NULL;\n> >  \tstruct keep_callback_data keep_callback_data = {\n> >  \t\t.cfg = &cfg,\n> > @@ -2443,7 +2445,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n> >  \n> >  \t\tif (strbuf_read_file(&buf, signature_file, 128) < 0)\n> >  \t\t\tdie_errno(_(\"unable to read signature file '%s'\"), signature_file);\n> > -\t\tsignature = strbuf_detach(&buf, NULL);\n> > +\t\tsignature = signature_to_free = strbuf_detach(&buf, NULL);\n> \n> Do I understand this correctly, that the multiple assignment here allows\n> us to maintain a reference to the pointer returned by `strbuf_detach()`\n> in `signature_to_free`, and we do this because `signature` can take on a\n> different value below?\n\nNot only below, but also its default value is `git_version_string`,\nwhich is a string constant. So we use the multiple assignment such that\nwe can avoid freeing `signature`, which may contain string constants,\nand unconditionally free `signature_to_free` because that variable\nalways holds an allocated string or a `NULL` pointer.\n\nPatrick\n\n> >  \t} else if (cfg.signature) {\n> >  \t\tsignature = cfg.signature;\n> >  \t}\n> \n> \n"},{"id":"500390","messageId":"ZrRSDaCClfh9dMZK@tanuki","threadId":"61910","inReplyTo":"D39KL8N6S0HC.HFB9VRAVWYOX@jamesliu.io","subject":"Re: [PATCH 19/22] userdiff: fix leaking memory for configured diff drivers","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T05:05:17Z","receivedAt":"2024-08-08T05:05:21Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Aug 07, 2024 at 07:25:51PM +1000, James Liu wrote:\n> > Refactor the code such that we have two pointers for each of these\n> > strings: one that holds the value as accessed by other subsystems, and\n> > one that points to the same string in case it has been allocated. Like\n> > this, we can safely free the second pointer and thus plug those memory\n> > leaks.\n> >\n> > diff --git a/userdiff.c b/userdiff.c\n> > index c4ebb9ff73..989629149f 100644\n> > --- a/userdiff.c\n> > +++ b/userdiff.c\n> > @@ -399,8 +399,11 @@ static struct userdiff_driver *userdiff_find_by_namelen(const char *name, size_t\n> >  static int parse_funcname(struct userdiff_funcname *f, const char *k,\n> >  \t\tconst char *v, int cflags)\n> >  {\n> > -\tif (git_config_string((char **) &f->pattern, k, v) < 0)\n> > +\tf->pattern = NULL;\n> > +\tFREE_AND_NULL(f->pattern_owned);\n> > +\tif (git_config_string(&f->pattern_owned, k, v) < 0)\n> >  \t\treturn -1;\n> > +\tf->pattern = f->pattern_owned;\n> >  \tf->cflags = cflags;\n> >  \treturn 0;\n> >  }\n> \n> I'm not sure if I understand this change completely. We don't seem to be\n> using `pattern_owned` (and the other *_owned) fields differently from\n> their regular counterparts.\n> \n> Is it because we can't do the following?\n> \n>         FREE_AND_NULL((char **)f->pattern);\n\nYup. We have a bunch of statically defined userdiff drivers, all of\nwhich use string constants as patterns. We thus cannot reliably free\nthose and instead have to track the allocated strings in a separate\nvariable.\n\nPatrick\n"},{"id":"500391","messageId":"ZrRSE0etqno-yFiE@tanuki","threadId":"61910","inReplyTo":"D39KMJ3FXBZC.7S74VUTPEQHY@jamesliu.io","subject":"Re: [PATCH 00/22] Memory leak fixes (pt.4)","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T05:05:23Z","receivedAt":"2024-08-08T05:05:27Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Aug 07, 2024 at 07:27:32PM +1000, James Liu wrote:\n> On Tue Aug 6, 2024 at 6:59 PM AEST, Patrick Steinhardt wrote:\n> > Hi,\n> >\n> > the third set of memory leak fixes was merged to `next`, so this is the\n> > next part of more or less random memory leak fixes all over the place.\n> > With this series, we're at ~155 leaking test suites. Naturally, I've\n> > already got v5 in the pipeline, which brings us down to ~120.\n> >\n> > The series is built on top of 406f326d27 (The second batch, 2024-08-01)\n> > with ps/leakfixes-part-3 at f30bfafcd4 (commit-reach: fix trivial memory\n> > leak when computing reachability, 2024-08-01) merged into it.\n> >\n> > Thanks!\n> >\n> > Patrick\n> \n> Thanks Patrick, most of these fixes make sense to me! I appreciate that\n> even the minor changes are accompanied by context.\n\nThanks for your review!\n\nPatrick\n"},{"id":"500401","messageId":"D3AAUM8AFHO8.1BF5590ZO4RIN@jamesliu.io","threadId":"61910","inReplyTo":"ZrRSE0etqno-yFiE@tanuki","subject":"Re: [PATCH 00/22] Memory leak fixes (pt.4)","fromName":"James Liu","fromEmail":"james@jamesliu.io","sentAt":"2024-08-08T06:00:35Z","receivedAt":"2024-08-08T06:00:38Z","isPatch":true,"sender":{"key":"james@jamesliu.io","avatar":"https://avatars.githubusercontent.com/u/4476568?v=4"},"body":"On Thu Aug 8, 2024 at 3:05 PM AEST, Patrick Steinhardt wrote:\n> On Wed, Aug 07, 2024 at 07:27:32PM +1000, James Liu wrote:\n> > On Tue Aug 6, 2024 at 6:59 PM AEST, Patrick Steinhardt wrote:\n> > > Hi,\n> > >\n> > > the third set of memory leak fixes was merged to `next`, so this is the\n> > > next part of more or less random memory leak fixes all over the place.\n> > > With this series, we're at ~155 leaking test suites. Naturally, I've\n> > > already got v5 in the pipeline, which brings us down to ~120.\n> > >\n> > > The series is built on top of 406f326d27 (The second batch, 2024-08-01)\n> > > with ps/leakfixes-part-3 at f30bfafcd4 (commit-reach: fix trivial memory\n> > > leak when computing reachability, 2024-08-01) merged into it.\n> > >\n> > > Thanks!\n> > >\n> > > Patrick\n> > \n> > Thanks Patrick, most of these fixes make sense to me! I appreciate that\n> > even the minor changes are accompanied by context.\n>\n> Thanks for your review!\n>\n> Patrick\n\nThanks for responding to my questions! I don't have anything further to\nadd.\n\nCheers,\nJames\n"},{"id":"500418","messageId":"b5abb3f5-c74c-444d-8006-22bdde2e9cd8@gmail.com","threadId":"61910","inReplyTo":"05290fc1f14cae8229c42f2d0aafe6619c069e3a.1722933642.git.ps@pks.im","subject":"Re: [PATCH 09/22] builtin/rebase: fix leaking `commit.gpgsign` value","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-08-08T10:07:53Z","receivedAt":"2024-08-08T10:07:56Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Patrick\n\nOn 06/08/2024 10:00, Patrick Steinhardt wrote:\n> @@ -186,7 +186,15 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n>   \treplay.committer_date_is_author_date =\n>   \t\t\t\t\topts->committer_date_is_author_date;\n>   \treplay.ignore_date = opts->ignore_date;\n> +\n> +\t/*\n> +\t * TODO: Is it really intentional that we unconditionally override\n> +\t * `replay.gpg_sign` even if it has already been initialized via the\n> +\t * configuration?\n> +\t */\n> +\tfree(replay.gpg_sign);\n>   \treplay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);\n> +\n\nThe code that handles \"-S\" could certainly be clearer. The value \nreturned from the config is either \"\" or NULL, not a key name. In \ncmd_main() options.gpg_sign_opt is initialized by rebase_config(), we \nset gpg_sign to \"\" if options.gpg_sign_opt is non-NULL, free \noptions.gpg_sign_opt and then copy gpg_sign back into \noptions.gpg_sign_opt after parsing the command line so we're not losing \nanything by unconditionally copying it here. The code changes look good, \nthough I'm not sure we need to add the blank lines. It's always nice to \nsee more tests marked as leak-free especially a big file like t3404.\n\nBest Wishes\n\nPhillip\n"},{"id":"500419","messageId":"e0685657-369f-4211-8518-cc41aa28dcb9@gmail.com","threadId":"61910","inReplyTo":"df4c21b49f86d6e1e9d2b28375ab6465ffa4339a.1722933642.git.ps@pks.im","subject":"Re: [PATCH 15/22] sequencer: release todo list on error paths","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-08-08T10:08:23Z","receivedAt":"2024-08-08T10:08:25Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Patrick\n\nOn 06/08/2024 10:00, Patrick Steinhardt wrote:\n> We're not releasing the `todo_list` in `sequencer_pick_revisions()` when\n> hitting an error path. Restructure the function to have a common exit\n> path such that we can easily clean up the list and thus plug this memory\n> leak.\n\nThis looks good, I've left a couple of small formatting comments below \nif you do end up re-rolling.\n\n> @@ -5506,11 +5508,14 @@ int sequencer_pick_revisions(struct repository *r,\n>   \t\t\t\tenum object_type type = oid_object_info(r,\n>   \t\t\t\t\t\t\t\t\t&oid,\n>   \t\t\t\t\t\t\t\t\tNULL);\n> -\t\t\t\treturn error(_(\"%s: can't cherry-pick a %s\"),\n> +\t\t\t\tres = error(_(\"%s: can't cherry-pick a %s\"),\n>   \t\t\t\t\tname, type_name(type));\n\nThis line needs re-indenting to match the changes above.\n\n> +\t\t\t\tgoto out;\n>   \t\t\t}\n> -\t\t} else\n> -\t\t\treturn error(_(\"%s: bad revision\"), name);\n> +\t\t} else {\n> +\t\t\tres = error(_(\"%s: bad revision\"), name);\n> +\t\t\tgoto out;\n> +\t\t}\n>   \t}\n>   \n>   \t/*\n> @@ -5525,14 +5530,23 @@ int sequencer_pick_revisions(struct repository *r,\n>   \t    opts->revs->no_walk &&\n>   \t    !opts->revs->cmdline.rev->flags) {\n>   \t\tstruct commit *cmit;\n> -\t\tif (prepare_revision_walk(opts->revs))\n> -\t\t\treturn error(_(\"revision walk setup failed\"));\n> +\n\nThis whitespace change is good as it means we now have an empty line \nbetween the variable declarations and the code, the others I'm not \nfussed about either way.\n\nBest Wishes\n\nPhillip\n\n> +\t\tif (prepare_revision_walk(opts->revs)) {\n> +\t\t\tres = error(_(\"revision walk setup failed\"));\n> +\t\t\tgoto out;\n> +\t\t}\n> +\n>   \t\tcmit = get_revision(opts->revs);\n> -\t\tif (!cmit)\n> -\t\t\treturn error(_(\"empty commit set passed\"));\n> +\t\tif (!cmit) {\n> +\t\t\tres = error(_(\"empty commit set passed\"));\n> +\t\t\tgoto out;\n> +\t\t}\n> +\n>   \t\tif (get_revision(opts->revs))\n>   \t\t\tBUG(\"unexpected extra commit from walk\");\n> -\t\treturn single_pick(r, cmit, opts);\n> +\n> +\t\tres = single_pick(r, cmit, opts);\n> +\t\tgoto out;\n>   \t}\n>   \n>   \t/*\n> @@ -5542,16 +5556,30 @@ int sequencer_pick_revisions(struct repository *r,\n>   \t */\n>   \n>   \tif (walk_revs_populate_todo(&todo_list, opts) ||\n> -\t\t\tcreate_seq_dir(r) < 0)\n> -\t\treturn -1;\n> -\tif (repo_get_oid(r, \"HEAD\", &oid) && (opts->action == REPLAY_REVERT))\n> -\t\treturn error(_(\"can't revert as initial commit\"));\n> -\tif (save_head(oid_to_hex(&oid)))\n> -\t\treturn -1;\n> -\tif (save_opts(opts))\n> -\t\treturn -1;\n> +\t\t\tcreate_seq_dir(r) < 0) {\n> +\t\tres = -1;\n> +\t\tgoto out;\n> +\t}\n> +\n> +\tif (repo_get_oid(r, \"HEAD\", &oid) && (opts->action == REPLAY_REVERT)) {\n> +\t\tres = error(_(\"can't revert as initial commit\"));\n> +\t\tgoto out;\n> +\t}\n> +\n> +\tif (save_head(oid_to_hex(&oid))) {\n> +\t\tres = -1;\n> +\t\tgoto out;\n> +\t}\n> +\n> +\tif (save_opts(opts)) {\n> +\t\tres = -1;\n> +\t\tgoto out;\n> +\t}\n> +\n>   \tupdate_abort_safety_file();\n>   \tres = pick_commits(r, &todo_list, opts);\n> +\n> +out:\n>   \ttodo_list_release(&todo_list);\n>   \treturn res;\n>   }\n> diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh\n> index 7eb52b12ed..93c725bac3 100755\n> --- a/t/t3510-cherry-pick-sequence.sh\n> +++ b/t/t3510-cherry-pick-sequence.sh\n> @@ -12,6 +12,7 @@ test_description='Test cherry-pick continuation features\n>   \n>   '\n>   \n> +TEST_PASSES_SANITIZE_LEAK=true\n>   . ./test-lib.sh\n>   \n>   # Repeat first match 10 times\n"},{"id":"500439","messageId":"ZrTBBJAr4XoIJENz@tanuki","threadId":"61910","inReplyTo":"b5abb3f5-c74c-444d-8006-22bdde2e9cd8@gmail.com","subject":"Re: [PATCH 09/22] builtin/rebase: fix leaking `commit.gpgsign` value","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T12:58:44Z","receivedAt":"2024-08-08T12:58:50Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Aug 08, 2024 at 11:07:53AM +0100, Phillip Wood wrote:\n> Hi Patrick\n> \n> On 06/08/2024 10:00, Patrick Steinhardt wrote:\n> > @@ -186,7 +186,15 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n> >   \treplay.committer_date_is_author_date =\n> >   \t\t\t\t\topts->committer_date_is_author_date;\n> >   \treplay.ignore_date = opts->ignore_date;\n> > +\n> > +\t/*\n> > +\t * TODO: Is it really intentional that we unconditionally override\n> > +\t * `replay.gpg_sign` even if it has already been initialized via the\n> > +\t * configuration?\n> > +\t */\n> > +\tfree(replay.gpg_sign);\n> >   \treplay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);\n> > +\n> \n> The code that handles \"-S\" could certainly be clearer. The value returned\n> from the config is either \"\" or NULL, not a key name. In cmd_main()\n> options.gpg_sign_opt is initialized by rebase_config(), we set gpg_sign to\n> \"\" if options.gpg_sign_opt is non-NULL, free options.gpg_sign_opt and then\n> copy gpg_sign back into options.gpg_sign_opt after parsing the command line\n> so we're not losing anything by unconditionally copying it here. The code\n> changes look good, though I'm not sure we need to add the blank lines. It's\n> always nice to see more tests marked as leak-free especially a big file like\n> t3404.\n\nOkay. In that case I'll just drop the comment. Thanks!\n\nPatrick\n"},{"id":"500440","messageId":"cover.1723121979.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"[PATCH v2 00/22] Memory leak fixes (pt.4)","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T13:04:28Z","receivedAt":"2024-08-08T13:04:33Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis is the second version of my fourth batch of patches that fix\nvarious memory leaks.\n\nChanges compared to v1:\n\n  - Adapt the memory leak fix for command characters to instead use a\n    `comment_line_str_allocated` variable.\n\n  - Clarify some commit messages.\n\n  - Drop the TODO comment about `rebase.gpgsign`. Turns out that this is\n    working as intended, as explained by Phillip.\n\nThanks!\n\nPatrick\n\nPatrick Steinhardt (22):\n  remote: plug memory leak when aliasing URLs\n  git: fix leaking system paths\n  object-file: fix memory leak when reading corrupted headers\n  object-name: fix leaking symlink paths in object context\n  bulk-checkin: fix leaking state TODO\n  read-cache: fix leaking hashfile when writing index fails\n  submodule-config: fix leaking name enrty when traversing submodules\n  config: fix leaking comment character config\n  builtin/rebase: fix leaking `commit.gpgsign` value\n  builtin/notes: fix leaking `struct notes_tree` when merging notes\n  builtin/fast-import: plug trivial memory leaks\n  builtin/fast-export: fix leaking diff options\n  builtin/fast-export: plug leaking tag names\n  merge-ort: unconditionally release attributes index\n  sequencer: release todo list on error paths\n  unpack-trees: clear index when not propagating it\n  diff: fix leak when parsing invalid ignore regex option\n  builtin/format-patch: fix various trivial memory leaks\n  userdiff: fix leaking memory for configured diff drivers\n  builtin/log: fix leak when showing converted blob contents\n  diff: free state populated via options\n  builtin/diff: free symmetric diff members\n\n builtin/commit.c                      |  7 +-\n builtin/diff.c                        | 10 ++-\n builtin/fast-export.c                 | 19 ++++--\n builtin/fast-import.c                 |  8 ++-\n builtin/log.c                         | 13 +++-\n builtin/notes.c                       |  9 ++-\n builtin/rebase.c                      |  1 +\n bulk-checkin.c                        |  2 +\n config.c                              |  4 +-\n csum-file.c                           |  2 +-\n csum-file.h                           | 10 +++\n diff.c                                | 16 ++++-\n environment.c                         |  1 +\n environment.h                         |  1 +\n git.c                                 | 12 +++-\n merge-ort.c                           |  3 +-\n object-file.c                         |  1 +\n object-name.c                         |  1 +\n range-diff.c                          |  6 +-\n read-cache.c                          | 97 ++++++++++++++++-----------\n remote.c                              |  2 +\n sequencer.c                           | 67 ++++++++++++------\n submodule-config.c                    | 18 +++--\n t/t0210-trace2-normal.sh              |  2 +-\n t/t1006-cat-file.sh                   |  1 +\n t/t1050-large.sh                      |  1 +\n t/t1450-fsck.sh                       |  1 +\n t/t1601-index-bogus.sh                |  2 +\n t/t2107-update-index-basic.sh         |  1 +\n t/t3310-notes-merge-manual-resolve.sh |  1 +\n t/t3311-notes-merge-fanout.sh         |  1 +\n t/t3404-rebase-interactive.sh         |  1 +\n t/t3435-rebase-gpg-sign.sh            |  1 +\n t/t3507-cherry-pick-conflict.sh       |  1 +\n t/t3510-cherry-pick-sequence.sh       |  1 +\n t/t3705-add-sparse-checkout.sh        |  1 +\n t/t4013-diff-various.sh               |  1 +\n t/t4014-format-patch.sh               |  1 +\n t/t4018-diff-funcname.sh              |  1 +\n t/t4030-diff-textconv.sh              |  2 +\n t/t4042-diff-textconv-caching.sh      |  2 +\n t/t4048-diff-combined-binary.sh       |  1 +\n t/t4064-diff-oidfind.sh               |  2 +\n t/t4065-diff-anchored.sh              |  1 +\n t/t4068-diff-symmetric-merge-base.sh  |  1 +\n t/t4069-remerge-diff.sh               |  1 +\n t/t4108-apply-threeway.sh             |  1 +\n t/t4209-log-pickaxe.sh                |  2 +\n t/t6421-merge-partial-clone.sh        |  1 +\n t/t6428-merge-conflicts-sparse.sh     |  1 +\n t/t7008-filter-branch-null-sha1.sh    |  1 +\n t/t7030-verify-tag.sh                 |  1 +\n t/t7817-grep-sparse-checkout.sh       |  1 +\n t/t9300-fast-import.sh                |  1 +\n t/t9304-fast-import-marks.sh          |  2 +\n t/t9351-fast-export-anonymize.sh      |  1 +\n unpack-trees.c                        |  2 +\n userdiff.c                            | 38 ++++++++---\n userdiff.h                            |  4 ++\n 59 files changed, 288 insertions(+), 106 deletions(-)\n\nRange-diff against v1:\n 1:  6e2fcd85c7 =  1:  2afa51f9ff remote: plug memory leak when aliasing URLs\n 2:  9574995a24 =  2:  324140e4fd git: fix leaking system paths\n 3:  f7e67d02d2 =  3:  43a38a2281 object-file: fix memory leak when reading corrupted headers\n 4:  a9caaaed55 =  4:  9d3dc145e8 object-name: fix leaking symlink paths in object context\n 5:  794af66103 =  5:  454139e7a4 bulk-checkin: fix leaking state TODO\n 6:  2810cada0a =  6:  f8b7195796 read-cache: fix leaking hashfile when writing index fails\n 7:  03f699cf39 =  7:  762fb5aa73 submodule-config: fix leaking name enrty when traversing submodules\n 8:  a34c90a552 !  8:  8fbd72a100 config: fix leaking comment character config\n    @@ Commit message\n         without free'ing the previous value. In fact, it can't easily free the\n         value in the first place because it may contain a string constant.\n     \n    -    Refactor the code so that we initialize the value with another array.\n    -    This allows us to free the value in case the string is not pointing to\n    -    that constant array anymore.\n    +    Refactor the code such that we track allocated comment character strings\n    +    via a separate non-constant variable `comment_line_str_allocated`. Adapt\n    +    sites that set `comment_line_str` to set both and free the old value\n    +    that was stored in `comment_line_str_allocated`.\n     \n         This memory leak is being hit in t3404. As there are still other memory\n         leaks in that file we cannot yet mark it as passing with leak checking\n    @@ Commit message\n     \n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n    + ## builtin/commit.c ##\n    +@@ builtin/commit.c: static void adjust_comment_line_char(const struct strbuf *sb)\n    + \tconst char *p;\n    + \n    + \tif (!memchr(sb->buf, candidates[0], sb->len)) {\n    +-\t\tcomment_line_str = xstrfmt(\"%c\", candidates[0]);\n    ++\t\tfree(comment_line_str_allocated);\n    ++\t\tcomment_line_str = comment_line_str_allocated =\n    ++\t\t\txstrfmt(\"%c\", candidates[0]);\n    + \t\treturn;\n    + \t}\n    + \n    +@@ builtin/commit.c: static void adjust_comment_line_char(const struct strbuf *sb)\n    + \tif (!*p)\n    + \t\tdie(_(\"unable to select a comment character that is not used\\n\"\n    + \t\t      \"in the current commit message\"));\n    +-\tcomment_line_str = xstrfmt(\"%c\", *p);\n    ++\tfree(comment_line_str_allocated);\n    ++\tcomment_line_str = comment_line_str_allocated = xstrfmt(\"%c\", *p);\n    + }\n    + \n    + static void prepare_amend_commit(struct commit *commit, struct strbuf *sb,\n    +\n      ## config.c ##\n     @@ config.c: static int git_default_core_config(const char *var, const char *value,\n      \t\telse if (value[0]) {\n      \t\t\tif (strchr(value, '\\n'))\n      \t\t\t\treturn error(_(\"%s cannot contain newline\"), var);\n    -+\t\t\tif (comment_line_str != comment_line_str_default)\n    -+\t\t\t\tfree((char *) comment_line_str);\n    - \t\t\tcomment_line_str = xstrdup(value);\n    +-\t\t\tcomment_line_str = xstrdup(value);\n    ++\t\t\tfree(comment_line_str_allocated);\n    ++\t\t\tcomment_line_str = comment_line_str_allocated =\n    ++\t\t\t\txstrdup(value);\n      \t\t\tauto_comment_line_char = 0;\n      \t\t} else\n    + \t\t\treturn error(_(\"%s must have at least one character\"), var);\n     \n      ## environment.c ##\n     @@ environment.c: int protect_ntfs = PROTECT_NTFS_DEFAULT;\n    -  * The character that begins a commented line in user-editable file\n       * that is subject to stripspace.\n       */\n    --const char *comment_line_str = \"#\";\n    -+const char comment_line_str_default[] = \"#\";\n    -+const char *comment_line_str = comment_line_str_default;\n    + const char *comment_line_str = \"#\";\n    ++char *comment_line_str_allocated;\n      int auto_comment_line_char;\n      \n      /* Parallel index stat data preload? */\n     \n      ## environment.h ##\n     @@ environment.h: struct strvec;\n    -  * The character that begins a commented line in user-editable file\n       * that is subject to stripspace.\n       */\n    -+extern const char comment_line_str_default[];\n      extern const char *comment_line_str;\n    ++extern char *comment_line_str_allocated;\n      extern int auto_comment_line_char;\n      \n    + /*\n 9:  05290fc1f1 !  9:  e497b76e9c builtin/rebase: fix leaking `commit.gpgsign` value\n    @@ Metadata\n      ## Commit message ##\n         builtin/rebase: fix leaking `commit.gpgsign` value\n     \n    -    In `get_replay_opts()`, we unconditionally override the `gpg_sign` field\n    -    that already got populated by `sequencer_init_config()` in case the user\n    -    has \"commit.gpgsign\" set in their config. It is kind of dubious whether\n    -    this is the correct thing to do or a bug. What is clear though is that\n    -    this creates a memory leak.\n    +    In `get_replay_opts()`, we override the `gpg_sign` field that already\n    +    got populated by `sequencer_init_config()` in case the user has\n    +    \"commit.gpgsign\" set in their config. This creates a memory leak because\n    +    we overwrite the previously assigned value, which may have already\n    +    pointed to an allocated string.\n     \n    -    Let's mark this assignment with a TODO comment to figure out whether\n    -    this needs to be fixed or not. Meanwhile though, let's plug the memory\n    -    leak.\n    +    Let's plug the memory leak by freeing the value before we overwrite it.\n     \n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n    @@ builtin/rebase.c: static struct replay_opts get_replay_opts(const struct rebase_\n      \treplay.committer_date_is_author_date =\n      \t\t\t\t\topts->committer_date_is_author_date;\n      \treplay.ignore_date = opts->ignore_date;\n    -+\n    -+\t/*\n    -+\t * TODO: Is it really intentional that we unconditionally override\n    -+\t * `replay.gpg_sign` even if it has already been initialized via the\n    -+\t * configuration?\n    -+\t */\n     +\tfree(replay.gpg_sign);\n      \treplay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);\n    -+\n      \treplay.reflog_action = xstrdup(opts->reflog_action);\n      \tif (opts->strategy)\n    - \t\treplay.strategy = xstrdup_or_null(opts->strategy);\n     \n      ## sequencer.c ##\n     @@ sequencer.c: static int git_sequencer_config(const char *k, const char *v,\n10:  4f5d490074 = 10:  c886b666f7 builtin/notes: fix leaking `struct notes_tree` when merging notes\n11:  798b911f77 = 11:  d1c757157b builtin/fast-import: plug trivial memory leaks\n12:  660732d29d = 12:  fa2d5c5d6b builtin/fast-export: fix leaking diff options\n13:  64366155de = 13:  d9dd860d2a builtin/fast-export: plug leaking tag names\n14:  b12015b3c3 = 14:  8f6860485e merge-ort: unconditionally release attributes index\n15:  df4c21b49f ! 15:  ea6a350f31 sequencer: release todo list on error paths\n    @@ sequencer.c: int sequencer_pick_revisions(struct repository *r,\n      \t\t\t\t\t\t\t\t\t&oid,\n      \t\t\t\t\t\t\t\t\tNULL);\n     -\t\t\t\treturn error(_(\"%s: can't cherry-pick a %s\"),\n    +-\t\t\t\t\tname, type_name(type));\n     +\t\t\t\tres = error(_(\"%s: can't cherry-pick a %s\"),\n    - \t\t\t\t\tname, type_name(type));\n    ++\t\t\t\t\t    name, type_name(type));\n     +\t\t\t\tgoto out;\n      \t\t\t}\n     -\t\t} else\n16:  1f8553fd43 = 16:  2755023742 unpack-trees: clear index when not propagating it\n17:  c6db8df324 = 17:  edf6f148cd diff: fix leak when parsing invalid ignore regex option\n18:  bf818a8a79 = 18:  343e3bd4df builtin/format-patch: fix various trivial memory leaks\n19:  ef780aa360 = 19:  be2c5b0bca userdiff: fix leaking memory for configured diff drivers\n20:  f3882986a3 = 20:  7888203833 builtin/log: fix leak when showing converted blob contents\n21:  a49bb2e0cc = 21:  245fc30afb diff: free state populated via options\n22:  fb52599404 = 22:  343ddcd17b builtin/diff: free symmetric diff members\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500441","messageId":"2afa51f9ffedfa6dab51c9515f695ddfe0a9a4f9.1723121979.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723121979.git.ps@pks.im","subject":"[PATCH v2 01/22] remote: plug memory leak when aliasing URLs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T13:04:33Z","receivedAt":"2024-08-08T13:04:37Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When we have a `url.*.insteadOf` configuration, then we end up aliasing\nURLs when populating remotes. One place where this happens is in\n`alias_all_urls()`, where we loop through all remotes and then alias\neach of their URLs. The actual aliasing logic is then contained in\n`alias_url()`, which returns an allocated string that contains the new\nURL. This URL replaces the old URL that we have in the strvec that\ncontanis all remote URLs.\n\nWe replace the remote URLs via `strvec_replace()`, which does not hand\nover ownership of the new string to the vector. Still, we didn't free\nthe aliased URL and thus have a memory leak here. Fix it by freeing the\naliased string.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n remote.c                 | 2 ++\n t/t0210-trace2-normal.sh | 2 +-\n 2 files changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/remote.c b/remote.c\nindex f43cf5e7a4..3b898edd23 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -499,6 +499,7 @@ static void alias_all_urls(struct remote_state *remote_state)\n \t\t\tif (alias)\n \t\t\t\tstrvec_replace(&remote_state->remotes[i]->pushurl,\n \t\t\t\t\t       j, alias);\n+\t\t\tfree(alias);\n \t\t}\n \t\tadd_pushurl_aliases = remote_state->remotes[i]->pushurl.nr == 0;\n \t\tfor (j = 0; j < remote_state->remotes[i]->url.nr; j++) {\n@@ -512,6 +513,7 @@ static void alias_all_urls(struct remote_state *remote_state)\n \t\t\tif (alias)\n \t\t\t\tstrvec_replace(&remote_state->remotes[i]->url,\n \t\t\t\t\t       j, alias);\n+\t\t\tfree(alias);\n \t\t}\n \t}\n }\ndiff --git a/t/t0210-trace2-normal.sh b/t/t0210-trace2-normal.sh\nindex c312657a12..b9adc94aab 100755\n--- a/t/t0210-trace2-normal.sh\n+++ b/t/t0210-trace2-normal.sh\n@@ -2,7 +2,7 @@\n \n test_description='test trace2 facility (normal target)'\n \n-TEST_PASSES_SANITIZE_LEAK=false\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # Turn off any inherited trace2 settings for this test.\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500442","messageId":"324140e4fdee54fbf0054e76abb287b7212fd21e.1723121979.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723121979.git.ps@pks.im","subject":"[PATCH v2 02/22] git: fix leaking system paths","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T13:04:39Z","receivedAt":"2024-08-08T13:04:43Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Git has some flags to make it output system paths as they have been\ncompiled into Git. This is done by calling `system_path()`, which\nreturns an allocated string. This string isn't ever free'd though,\ncreating a memory leak.\n\nPlug those leaks. While they are surfaced by t0211, there are more\nmemory leaks looming exposed by that test suite and it thus does not yet\npass with the memory leak checker enabled.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n git.c | 12 +++++++++---\n 1 file changed, 9 insertions(+), 3 deletions(-)\n\ndiff --git a/git.c b/git.c\nindex e35af9b0e5..5eab88b472 100644\n--- a/git.c\n+++ b/git.c\n@@ -173,15 +173,21 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\t\texit(0);\n \t\t\t}\n \t\t} else if (!strcmp(cmd, \"--html-path\")) {\n-\t\t\tputs(system_path(GIT_HTML_PATH));\n+\t\t\tchar *path = system_path(GIT_HTML_PATH);\n+\t\t\tputs(path);\n+\t\t\tfree(path);\n \t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"--man-path\")) {\n-\t\t\tputs(system_path(GIT_MAN_PATH));\n+\t\t\tchar *path = system_path(GIT_MAN_PATH);\n+\t\t\tputs(path);\n+\t\t\tfree(path);\n \t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"--info-path\")) {\n-\t\t\tputs(system_path(GIT_INFO_PATH));\n+\t\t\tchar *path = system_path(GIT_INFO_PATH);\n+\t\t\tputs(path);\n+\t\t\tfree(path);\n \t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"-p\") || !strcmp(cmd, \"--paginate\")) {\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500443","messageId":"43a38a2281f25db837dc9b5a59ecbe12214ac7bf.1723121979.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723121979.git.ps@pks.im","subject":"[PATCH v2 03/22] object-file: fix memory leak when reading corrupted headers","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T13:04:44Z","receivedAt":"2024-08-08T13:04:47Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When reading corrupt object headers in `read_loose_object()`, then we\nbail out immediately. This causes a memory leak though because we would\nhave already initialized the zstream in `unpack_loose_header()`, and it\nis the callers responsibility to finish the zstream even on error. While\nthis feels weird, other callsites do it correctly already.\n\nFix this leak by ending the zstream even on errors. We may want to\nrevisit this interface in the future such that the callee handles this\nfor us already when there was an error.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c   | 1 +\n t/t1450-fsck.sh | 1 +\n 2 files changed, 2 insertions(+)\n\ndiff --git a/object-file.c b/object-file.c\nindex 065103be3e..7c65c435cd 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -2954,6 +2954,7 @@ int read_loose_object(const char *path,\n \tif (unpack_loose_header(&stream, map, mapsize, hdr, sizeof(hdr),\n \t\t\t\tNULL) != ULHR_OK) {\n \t\terror(_(\"unable to unpack header of %s\"), path);\n+\t\tgit_inflate_end(&stream);\n \t\tgoto out;\n \t}\n \ndiff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh\nindex 8a456b1142..280cbf3e03 100755\n--- a/t/t1450-fsck.sh\n+++ b/t/t1450-fsck.sh\n@@ -6,6 +6,7 @@ test_description='git fsck random collection of tests\n * (main) A\n '\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success setup '\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500444","messageId":"9d3dc145e8a60ae446b890ae5b97f81bfd762f79.1723121979.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723121979.git.ps@pks.im","subject":"[PATCH v2 04/22] object-name: fix leaking symlink paths in object context","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T13:04:48Z","receivedAt":"2024-08-08T13:04:52Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The object context may be populated with symlink contents when reading a\nsymlink, but the associated strbuf doesn't ever get released when\nreleasing the object context, causing a memory leak. Plug it.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-name.c       | 1 +\n t/t1006-cat-file.sh | 1 +\n 2 files changed, 2 insertions(+)\n\ndiff --git a/object-name.c b/object-name.c\nindex 240a93e7ce..e39fa50e47 100644\n--- a/object-name.c\n+++ b/object-name.c\n@@ -1765,6 +1765,7 @@ int strbuf_check_branch_ref(struct strbuf *sb, const char *name)\n void object_context_release(struct object_context *ctx)\n {\n \tfree(ctx->path);\n+\tstrbuf_release(&ctx->symlink_path);\n }\n \n /*\ndiff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\nindex ff9bf213aa..d36cd7c086 100755\n--- a/t/t1006-cat-file.sh\n+++ b/t/t1006-cat-file.sh\n@@ -2,6 +2,7 @@\n \n test_description='git cat-file'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_cmdmode_usage () {\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500445","messageId":"454139e7a4b0272ec8c304bc2a84dad35187de78.1723121979.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723121979.git.ps@pks.im","subject":"[PATCH v2 05/22] bulk-checkin: fix leaking state TODO","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T13:04:53Z","receivedAt":"2024-08-08T13:04:57Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When flushing a bulk-checking to disk we also reset the `struct\nbulk_checkin_packfile` state. But while we free some of its members,\nothers aren't being free'd, leading to memory leaks:\n\n  - The temporary packfile name is not getting freed.\n\n  - The `struct hashfile` only gets freed in case we end up calling\n    `finalize_hashfile()`. There are code paths though where that is not\n    the case, namely when nothing has been written. For this, we need to\n    make `free_hashfile()` public.\n\nFix those leaks.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n bulk-checkin.c   |  2 ++\n csum-file.c      |  2 +-\n csum-file.h      | 10 ++++++++++\n t/t1050-large.sh |  1 +\n 4 files changed, 14 insertions(+), 1 deletion(-)\n\ndiff --git a/bulk-checkin.c b/bulk-checkin.c\nindex da8673199b..9089c214fa 100644\n--- a/bulk-checkin.c\n+++ b/bulk-checkin.c\n@@ -61,6 +61,7 @@ static void flush_bulk_checkin_packfile(struct bulk_checkin_packfile *state)\n \n \tif (state->nr_written == 0) {\n \t\tclose(state->f->fd);\n+\t\tfree_hashfile(state->f);\n \t\tunlink(state->pack_tmp_name);\n \t\tgoto clear_exit;\n \t} else if (state->nr_written == 1) {\n@@ -83,6 +84,7 @@ static void flush_bulk_checkin_packfile(struct bulk_checkin_packfile *state)\n \t\tfree(state->written[i]);\n \n clear_exit:\n+\tfree(state->pack_tmp_name);\n \tfree(state->written);\n \tmemset(state, 0, sizeof(*state));\n \ndiff --git a/csum-file.c b/csum-file.c\nindex 8abbf01325..7e0ece1305 100644\n--- a/csum-file.c\n+++ b/csum-file.c\n@@ -56,7 +56,7 @@ void hashflush(struct hashfile *f)\n \t}\n }\n \n-static void free_hashfile(struct hashfile *f)\n+void free_hashfile(struct hashfile *f)\n {\n \tfree(f->buffer);\n \tfree(f->check_buffer);\ndiff --git a/csum-file.h b/csum-file.h\nindex 566e05cbd2..ca553eba17 100644\n--- a/csum-file.h\n+++ b/csum-file.h\n@@ -46,6 +46,16 @@ int hashfile_truncate(struct hashfile *, struct hashfile_checkpoint *);\n struct hashfile *hashfd(int fd, const char *name);\n struct hashfile *hashfd_check(const char *name);\n struct hashfile *hashfd_throughput(int fd, const char *name, struct progress *tp);\n+\n+/*\n+ * Free the hashfile without flushing its contents to disk. This only\n+ * needs to be called when not calling `finalize_hashfile()`.\n+ */\n+void free_hashfile(struct hashfile *f);\n+\n+/*\n+ * Finalize the hashfile by flushing data to disk and free'ing it.\n+ */\n int finalize_hashfile(struct hashfile *, unsigned char *, enum fsync_component, unsigned int);\n void hashwrite(struct hashfile *, const void *, unsigned int);\n void hashflush(struct hashfile *f);\ndiff --git a/t/t1050-large.sh b/t/t1050-large.sh\nindex c71932b024..ed638f6644 100755\n--- a/t/t1050-large.sh\n+++ b/t/t1050-large.sh\n@@ -3,6 +3,7 @@\n \n test_description='adding and checking out large blobs'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'core.bigFileThreshold must be non-negative' '\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500446","messageId":"f8b7195796610d81fe03b52eb990ef8301d5679b.1723121979.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723121979.git.ps@pks.im","subject":"[PATCH v2 06/22] read-cache: fix leaking hashfile when writing index fails","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T13:04:58Z","receivedAt":"2024-08-08T13:05:02Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `do_write_index()`, we use a `struct hashfile` to write the index\nwith a trailer hash. In case the write fails though, we never clean up\nthe allocated `hashfile` state and thus leak memory.\n\nRefactor the code to have a common exit path where we can free this and\nother allocated memory. While at it, refactor our use of `strbuf`s such\nthat we reuse the same buffer to avoid some unneeded allocations.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n read-cache.c                       | 97 ++++++++++++++++++------------\n t/t1601-index-bogus.sh             |  2 +\n t/t2107-update-index-basic.sh      |  1 +\n t/t7008-filter-branch-null-sha1.sh |  1 +\n 4 files changed, 62 insertions(+), 39 deletions(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex 48bf24f87c..36821fe5b5 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -2840,8 +2840,9 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \tint csum_fsync_flag;\n \tint ieot_entries = 1;\n \tstruct index_entry_offset_table *ieot = NULL;\n-\tint nr, nr_threads;\n \tstruct repository *r = istate->repo;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tint nr, nr_threads, ret;\n \n \tf = hashfd(tempfile->fd, tempfile->filename.buf);\n \n@@ -2962,8 +2963,8 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \tstrbuf_release(&previous_name_buf);\n \n \tif (err) {\n-\t\tfree(ieot);\n-\t\treturn err;\n+\t\tret = err;\n+\t\tgoto out;\n \t}\n \n \toffset = hashfile_total(f);\n@@ -2985,20 +2986,20 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \t * index.\n \t */\n \tif (ieot) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\twrite_ieot_extension(&sb, ieot);\n \t\terr = write_index_ext_header(f, eoie_c, CACHE_EXT_INDEXENTRYOFFSETTABLE, sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tfree(ieot);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \n \tif (write_extensions & WRITE_SPLIT_INDEX_EXTENSION &&\n \t    istate->split_index) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\tif (istate->sparse_index)\n \t\t\tdie(_(\"cannot write split index for a sparse index\"));\n@@ -3007,59 +3008,66 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \t\t\twrite_index_ext_header(f, eoie_c, CACHE_EXT_LINK,\n \t\t\t\t\t       sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \tif (write_extensions & WRITE_CACHE_TREE_EXTENSION &&\n \t    !drop_cache_tree && istate->cache_tree) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\tcache_tree_write(&sb, istate->cache_tree);\n \t\terr = write_index_ext_header(f, eoie_c, CACHE_EXT_TREE, sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \tif (write_extensions & WRITE_RESOLVE_UNDO_EXTENSION &&\n \t    istate->resolve_undo) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\tresolve_undo_write(&sb, istate->resolve_undo);\n \t\terr = write_index_ext_header(f, eoie_c, CACHE_EXT_RESOLVE_UNDO,\n \t\t\t\t\t     sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \tif (write_extensions & WRITE_UNTRACKED_CACHE_EXTENSION &&\n \t    istate->untracked) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\twrite_untracked_extension(&sb, istate->untracked);\n \t\terr = write_index_ext_header(f, eoie_c, CACHE_EXT_UNTRACKED,\n \t\t\t\t\t     sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \tif (write_extensions & WRITE_FSMONITOR_EXTENSION &&\n \t    istate->fsmonitor_last_update) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\twrite_fsmonitor_extension(&sb, istate);\n \t\terr = write_index_ext_header(f, eoie_c, CACHE_EXT_FSMONITOR, sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \tif (istate->sparse_index) {\n-\t\tif (write_index_ext_header(f, eoie_c, CACHE_EXT_SPARSE_DIRECTORIES, 0) < 0)\n-\t\t\treturn -1;\n+\t\tif (write_index_ext_header(f, eoie_c, CACHE_EXT_SPARSE_DIRECTORIES, 0) < 0) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \n \t/*\n@@ -3069,14 +3077,15 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \t * when loading the shared index.\n \t */\n \tif (eoie_c) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\twrite_eoie_extension(&sb, eoie_c, offset);\n \t\terr = write_index_ext_header(f, NULL, CACHE_EXT_ENDOFINDEXENTRIES, sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \n \tcsum_fsync_flag = 0;\n@@ -3085,13 +3094,16 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \n \tfinalize_hashfile(f, istate->oid.hash, FSYNC_COMPONENT_INDEX,\n \t\t\t  CSUM_HASH_IN_STREAM | csum_fsync_flag);\n+\tf = NULL;\n \n \tif (close_tempfile_gently(tempfile)) {\n-\t\terror(_(\"could not close '%s'\"), get_tempfile_path(tempfile));\n-\t\treturn -1;\n+\t\tret = error(_(\"could not close '%s'\"), get_tempfile_path(tempfile));\n+\t\tgoto out;\n+\t}\n+\tif (stat(get_tempfile_path(tempfile), &st)) {\n+\t\tret = -1;\n+\t\tgoto out;\n \t}\n-\tif (stat(get_tempfile_path(tempfile), &st))\n-\t\treturn -1;\n \tistate->timestamp.sec = (unsigned int)st.st_mtime;\n \tistate->timestamp.nsec = ST_MTIME_NSEC(st);\n \ttrace_performance_since(start, \"write index, changed mask = %x\", istate->cache_changed);\n@@ -3105,7 +3117,14 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \ttrace2_data_intmax(\"index\", the_repository, \"write/cache_nr\",\n \t\t\t   istate->cache_nr);\n \n-\treturn 0;\n+\tret = 0;\n+\n+out:\n+\tif (f)\n+\t\tfree_hashfile(f);\n+\tstrbuf_release(&sb);\n+\tfree(ieot);\n+\treturn ret;\n }\n \n void set_alternate_index_output(const char *name)\ndiff --git a/t/t1601-index-bogus.sh b/t/t1601-index-bogus.sh\nindex 4171f1e141..5dcc101882 100755\n--- a/t/t1601-index-bogus.sh\n+++ b/t/t1601-index-bogus.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='test handling of bogus index entries'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'create tree with null sha1' '\ndiff --git a/t/t2107-update-index-basic.sh b/t/t2107-update-index-basic.sh\nindex cc72ead79f..f0eab13f96 100755\n--- a/t/t2107-update-index-basic.sh\n+++ b/t/t2107-update-index-basic.sh\n@@ -5,6 +5,7 @@ test_description='basic update-index tests\n Tests for command-line parsing and basic operation.\n '\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'update-index --nonsense fails' '\ndiff --git a/t/t7008-filter-branch-null-sha1.sh b/t/t7008-filter-branch-null-sha1.sh\nindex 93fbc92b8d..0ce8fd2c89 100755\n--- a/t/t7008-filter-branch-null-sha1.sh\n+++ b/t/t7008-filter-branch-null-sha1.sh\n@@ -2,6 +2,7 @@\n \n test_description='filter-branch removal of trees with null sha1'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup: base commits' '\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500447","messageId":"762fb5aa730fd2c0af21f8f4a4632cd9ca6ae35b.1723121979.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723121979.git.ps@pks.im","subject":"[PATCH v2 07/22] submodule-config: fix leaking name enrty when traversing submodules","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T13:05:02Z","receivedAt":"2024-08-08T13:05:06Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We traverse through submodules in the tree via `tree_entry()`, passing\nto it a `struct name_entry` that it is supposed to populate with the\ntree entry's contents. We unnecessarily allocate this variable instead\nof passing a variable that is allocated on the stack, and the ultimately\ndon't even free that variable. This is unnecessary and leaks memory.\n\nConvert the variable to instead be allocated on the stack to plug the\nmemory leak.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n submodule-config.c | 18 ++++++++----------\n 1 file changed, 8 insertions(+), 10 deletions(-)\n\ndiff --git a/submodule-config.c b/submodule-config.c\nindex 9b0bb0b9f4..c8f2bb2bdd 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -899,27 +899,25 @@ static void traverse_tree_submodules(struct repository *r,\n {\n \tstruct tree_desc tree;\n \tstruct submodule_tree_entry *st_entry;\n-\tstruct name_entry *name_entry;\n+\tstruct name_entry name_entry;\n \tchar *tree_path = NULL;\n \n-\tname_entry = xmalloc(sizeof(*name_entry));\n-\n \tfill_tree_descriptor(r, &tree, treeish_name);\n-\twhile (tree_entry(&tree, name_entry)) {\n+\twhile (tree_entry(&tree, &name_entry)) {\n \t\tif (prefix)\n \t\t\ttree_path =\n-\t\t\t\tmkpathdup(\"%s/%s\", prefix, name_entry->path);\n+\t\t\t\tmkpathdup(\"%s/%s\", prefix, name_entry.path);\n \t\telse\n-\t\t\ttree_path = xstrdup(name_entry->path);\n+\t\t\ttree_path = xstrdup(name_entry.path);\n \n-\t\tif (S_ISGITLINK(name_entry->mode) &&\n+\t\tif (S_ISGITLINK(name_entry.mode) &&\n \t\t    is_tree_submodule_active(r, root_tree, tree_path)) {\n \t\t\tALLOC_GROW(out->entries, out->entry_nr + 1,\n \t\t\t\t   out->entry_alloc);\n \t\t\tst_entry = &out->entries[out->entry_nr++];\n \n \t\t\tst_entry->name_entry = xmalloc(sizeof(*st_entry->name_entry));\n-\t\t\t*st_entry->name_entry = *name_entry;\n+\t\t\t*st_entry->name_entry = name_entry;\n \t\t\tst_entry->submodule =\n \t\t\t\tsubmodule_from_path(r, root_tree, tree_path);\n \t\t\tst_entry->repo = xmalloc(sizeof(*st_entry->repo));\n@@ -927,9 +925,9 @@ static void traverse_tree_submodules(struct repository *r,\n \t\t\t\t\t\troot_tree))\n \t\t\t\tFREE_AND_NULL(st_entry->repo);\n \n-\t\t} else if (S_ISDIR(name_entry->mode))\n+\t\t} else if (S_ISDIR(name_entry.mode))\n \t\t\ttraverse_tree_submodules(r, root_tree, tree_path,\n-\t\t\t\t\t\t &name_entry->oid, out);\n+\t\t\t\t\t\t &name_entry.oid, out);\n \t\tfree(tree_path);\n \t}\n }\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500448","messageId":"8fbd72a1002d1a285847c62b5524041927a7b4d4.1723121979.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723121979.git.ps@pks.im","subject":"[PATCH v2 08/22] config: fix leaking comment character config","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T13:05:07Z","receivedAt":"2024-08-08T13:05:11Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When the comment line character has been specified multiple times in the\nconfiguration, then `git_default_core_config()` will cause a memory leak\nbecause it unconditionally copies the string into `comment_line_str`\nwithout free'ing the previous value. In fact, it can't easily free the\nvalue in the first place because it may contain a string constant.\n\nRefactor the code such that we track allocated comment character strings\nvia a separate non-constant variable `comment_line_str_allocated`. Adapt\nsites that set `comment_line_str` to set both and free the old value\nthat was stored in `comment_line_str_allocated`.\n\nThis memory leak is being hit in t3404. As there are still other memory\nleaks in that file we cannot yet mark it as passing with leak checking\nenabled.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/commit.c | 7 +++++--\n config.c         | 4 +++-\n environment.c    | 1 +\n environment.h    | 1 +\n 4 files changed, 10 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 66427ba82d..025b1c4686 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -684,7 +684,9 @@ static void adjust_comment_line_char(const struct strbuf *sb)\n \tconst char *p;\n \n \tif (!memchr(sb->buf, candidates[0], sb->len)) {\n-\t\tcomment_line_str = xstrfmt(\"%c\", candidates[0]);\n+\t\tfree(comment_line_str_allocated);\n+\t\tcomment_line_str = comment_line_str_allocated =\n+\t\t\txstrfmt(\"%c\", candidates[0]);\n \t\treturn;\n \t}\n \n@@ -705,7 +707,8 @@ static void adjust_comment_line_char(const struct strbuf *sb)\n \tif (!*p)\n \t\tdie(_(\"unable to select a comment character that is not used\\n\"\n \t\t      \"in the current commit message\"));\n-\tcomment_line_str = xstrfmt(\"%c\", *p);\n+\tfree(comment_line_str_allocated);\n+\tcomment_line_str = comment_line_str_allocated = xstrfmt(\"%c\", *p);\n }\n \n static void prepare_amend_commit(struct commit *commit, struct strbuf *sb,\ndiff --git a/config.c b/config.c\nindex 6421894614..cb78b652ee 100644\n--- a/config.c\n+++ b/config.c\n@@ -1596,7 +1596,9 @@ static int git_default_core_config(const char *var, const char *value,\n \t\telse if (value[0]) {\n \t\t\tif (strchr(value, '\\n'))\n \t\t\t\treturn error(_(\"%s cannot contain newline\"), var);\n-\t\t\tcomment_line_str = xstrdup(value);\n+\t\t\tfree(comment_line_str_allocated);\n+\t\t\tcomment_line_str = comment_line_str_allocated =\n+\t\t\t\txstrdup(value);\n \t\t\tauto_comment_line_char = 0;\n \t\t} else\n \t\t\treturn error(_(\"%s must have at least one character\"), var);\ndiff --git a/environment.c b/environment.c\nindex 5cea2c9f54..1a95798d5f 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -114,6 +114,7 @@ int protect_ntfs = PROTECT_NTFS_DEFAULT;\n  * that is subject to stripspace.\n  */\n const char *comment_line_str = \"#\";\n+char *comment_line_str_allocated;\n int auto_comment_line_char;\n \n /* Parallel index stat data preload? */\ndiff --git a/environment.h b/environment.h\nindex e9f01d4d11..0e0906f125 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -9,6 +9,7 @@ struct strvec;\n  * that is subject to stripspace.\n  */\n extern const char *comment_line_str;\n+extern char *comment_line_str_allocated;\n extern int auto_comment_line_char;\n \n /*\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500449","messageId":"e497b76e9cd0fc2e664e8031b68dd04f6b236c74.1723121979.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723121979.git.ps@pks.im","subject":"[PATCH v2 09/22] builtin/rebase: fix leaking `commit.gpgsign` value","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T13:05:11Z","receivedAt":"2024-08-08T13:05:15Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `get_replay_opts()`, we override the `gpg_sign` field that already\ngot populated by `sequencer_init_config()` in case the user has\n\"commit.gpgsign\" set in their config. This creates a memory leak because\nwe overwrite the previously assigned value, which may have already\npointed to an allocated string.\n\nLet's plug the memory leak by freeing the value before we overwrite it.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/rebase.c              | 1 +\n sequencer.c                   | 1 +\n t/t3404-rebase-interactive.sh | 1 +\n t/t3435-rebase-gpg-sign.sh    | 1 +\n t/t7030-verify-tag.sh         | 1 +\n 5 files changed, 5 insertions(+)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex e3a8e74cfc..2f01d5d3a6 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -186,6 +186,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n \treplay.committer_date_is_author_date =\n \t\t\t\t\topts->committer_date_is_author_date;\n \treplay.ignore_date = opts->ignore_date;\n+\tfree(replay.gpg_sign);\n \treplay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);\n \treplay.reflog_action = xstrdup(opts->reflog_action);\n \tif (opts->strategy)\ndiff --git a/sequencer.c b/sequencer.c\nindex 0291920f0b..cade9b0ca8 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -303,6 +303,7 @@ static int git_sequencer_config(const char *k, const char *v,\n \t}\n \n \tif (!strcmp(k, \"commit.gpgsign\")) {\n+\t\tfree(opts->gpg_sign);\n \t\topts->gpg_sign = git_config_bool(k, v) ? xstrdup(\"\") : NULL;\n \t\treturn 0;\n \t}\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex f92baad138..f171af3061 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -26,6 +26,7 @@ Initial setup:\n  touch file \"conflict\".\n '\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n . \"$TEST_DIRECTORY\"/lib-rebase.sh\ndiff --git a/t/t3435-rebase-gpg-sign.sh b/t/t3435-rebase-gpg-sign.sh\nindex 6aa2aeb628..6e329fea7c 100755\n--- a/t/t3435-rebase-gpg-sign.sh\n+++ b/t/t3435-rebase-gpg-sign.sh\n@@ -8,6 +8,7 @@ test_description='test rebase --[no-]gpg-sign'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY/lib-rebase.sh\"\n . \"$TEST_DIRECTORY/lib-gpg.sh\"\ndiff --git a/t/t7030-verify-tag.sh b/t/t7030-verify-tag.sh\nindex 6f526c37c2..effa826744 100755\n--- a/t/t7030-verify-tag.sh\n+++ b/t/t7030-verify-tag.sh\n@@ -4,6 +4,7 @@ test_description='signed tag tests'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY/lib-gpg.sh\"\n \n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500450","messageId":"c886b666f72637fbc16e5c5ec0b2a05718328866.1723121979.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723121979.git.ps@pks.im","subject":"[PATCH v2 10/22] builtin/notes: fix leaking `struct notes_tree` when merging notes","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T13:05:16Z","receivedAt":"2024-08-08T13:05:20Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We allocate a `struct notes_tree` in `merge_commit()` which we then\ninitialize via `init_notes()`. It's not really necessary to allocate the\nstructure though given that we never pass ownership to the caller.\nFurthermore, the allocation leads to a memory leak because despite its\nname, `free_notes()` doesn't free the `notes_tree` but only clears it.\n\nFix this issue by converting the code to use an on-stack variable.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/notes.c                       | 9 ++++-----\n t/t3310-notes-merge-manual-resolve.sh | 1 +\n t/t3311-notes-merge-fanout.sh         | 1 +\n 3 files changed, 6 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex d9c356e354..81cbaeec6b 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -807,7 +807,7 @@ static int merge_commit(struct notes_merge_options *o)\n {\n \tstruct strbuf msg = STRBUF_INIT;\n \tstruct object_id oid, parent_oid;\n-\tstruct notes_tree *t;\n+\tstruct notes_tree t = {0};\n \tstruct commit *partial;\n \tstruct pretty_print_context pretty_ctx;\n \tvoid *local_ref_to_free;\n@@ -830,8 +830,7 @@ static int merge_commit(struct notes_merge_options *o)\n \telse\n \t\toidclr(&parent_oid, the_repository->hash_algo);\n \n-\tCALLOC_ARRAY(t, 1);\n-\tinit_notes(t, \"NOTES_MERGE_PARTIAL\", combine_notes_overwrite, 0);\n+\tinit_notes(&t, \"NOTES_MERGE_PARTIAL\", combine_notes_overwrite, 0);\n \n \to->local_ref = local_ref_to_free =\n \t\trefs_resolve_refdup(get_main_ref_store(the_repository),\n@@ -839,7 +838,7 @@ static int merge_commit(struct notes_merge_options *o)\n \tif (!o->local_ref)\n \t\tdie(_(\"failed to resolve NOTES_MERGE_REF\"));\n \n-\tif (notes_merge_commit(o, t, partial, &oid))\n+\tif (notes_merge_commit(o, &t, partial, &oid))\n \t\tdie(_(\"failed to finalize notes merge\"));\n \n \t/* Reuse existing commit message in reflog message */\n@@ -853,7 +852,7 @@ static int merge_commit(struct notes_merge_options *o)\n \t\t\tis_null_oid(&parent_oid) ? NULL : &parent_oid,\n \t\t\t0, UPDATE_REFS_DIE_ON_ERR);\n \n-\tfree_notes(t);\n+\tfree_notes(&t);\n \tstrbuf_release(&msg);\n \tret = merge_abort(o);\n \tfree(local_ref_to_free);\ndiff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh\nindex 597df5ebc0..04866b89be 100755\n--- a/t/t3310-notes-merge-manual-resolve.sh\n+++ b/t/t3310-notes-merge-manual-resolve.sh\n@@ -5,6 +5,7 @@\n \n test_description='Test notes merging with manual conflict resolution'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # Set up a notes merge scenario with different kinds of conflicts\ndiff --git a/t/t3311-notes-merge-fanout.sh b/t/t3311-notes-merge-fanout.sh\nindex 5b675417e9..ce4144db0f 100755\n--- a/t/t3311-notes-merge-fanout.sh\n+++ b/t/t3311-notes-merge-fanout.sh\n@@ -5,6 +5,7 @@\n \n test_description='Test notes merging at various fanout levels'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n verify_notes () {\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500451","messageId":"d1c757157bae695ccbab5d41523c341d0aa8b9e5.1723121979.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723121979.git.ps@pks.im","subject":"[PATCH v2 11/22] builtin/fast-import: plug trivial memory leaks","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T13:05:21Z","receivedAt":"2024-08-08T13:05:25Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Plug some trivial memory leaks in git-fast-import(1).\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fast-import.c        | 8 ++++++--\n t/t9300-fast-import.sh       | 1 +\n t/t9304-fast-import-marks.sh | 2 ++\n 3 files changed, 9 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex d21c4053a7..6dfeb01665 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -206,8 +206,8 @@ static unsigned int object_entry_alloc = 5000;\n static struct object_entry_pool *blocks;\n static struct hashmap object_table;\n static struct mark_set *marks;\n-static const char *export_marks_file;\n-static const char *import_marks_file;\n+static char *export_marks_file;\n+static char *import_marks_file;\n static int import_marks_file_from_stream;\n static int import_marks_file_ignore_missing;\n static int import_marks_file_done;\n@@ -3274,6 +3274,7 @@ static void option_import_marks(const char *marks,\n \t\t\tread_marks();\n \t}\n \n+\tfree(import_marks_file);\n \timport_marks_file = make_fast_import_path(marks);\n \timport_marks_file_from_stream = from_stream;\n \timport_marks_file_ignore_missing = ignore_missing;\n@@ -3316,6 +3317,7 @@ static void option_active_branches(const char *branches)\n \n static void option_export_marks(const char *marks)\n {\n+\tfree(export_marks_file);\n \texport_marks_file = make_fast_import_path(marks);\n }\n \n@@ -3357,6 +3359,8 @@ static void option_rewrite_submodules(const char *arg, struct string_list *list)\n \tfree(f);\n \n \tstring_list_insert(list, s)->util = ms;\n+\n+\tfree(s);\n }\n \n static int parse_one_option(const char *option)\ndiff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh\nindex 1e68426852..3b3c371740 100755\n--- a/t/t9300-fast-import.sh\n+++ b/t/t9300-fast-import.sh\n@@ -7,6 +7,7 @@ test_description='test git fast-import utility'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-diff.sh ;# test-lib chdir's into trash\n \ndiff --git a/t/t9304-fast-import-marks.sh b/t/t9304-fast-import-marks.sh\nindex 410a871c52..1f776a80f3 100755\n--- a/t/t9304-fast-import-marks.sh\n+++ b/t/t9304-fast-import-marks.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='test exotic situations with marks'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup dump of basic history' '\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500452","messageId":"fa2d5c5d6b7b9018c3271a3c955852d1aeaf44d9.1723121979.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723121979.git.ps@pks.im","subject":"[PATCH v2 12/22] builtin/fast-export: fix leaking diff options","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T13:05:26Z","receivedAt":"2024-08-08T13:05:30Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Before caling `handle_commit()` in a loop, we set `diffopt.no_free` such\nthat its contents aren't getting freed inside of `handle_commit()`. We\nnever unset that flag though, which means that it'll ultimately leak\nwhen calling `release_revisions()`.\n\nFix this by unsetting the flag after the loop.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fast-export.c | 2 ++\n 1 file changed, 2 insertions(+)\n\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex 4b6e8c6832..fe92d2436c 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -1278,9 +1278,11 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)\n \trevs.diffopt.format_callback = show_filemodify;\n \trevs.diffopt.format_callback_data = &paths_of_changed_objects;\n \trevs.diffopt.flags.recursive = 1;\n+\n \trevs.diffopt.no_free = 1;\n \twhile ((commit = get_revision(&revs)))\n \t\thandle_commit(commit, &revs, &paths_of_changed_objects);\n+\trevs.diffopt.no_free = 0;\n \n \thandle_tags_and_duplicates(&extra_refs);\n \thandle_tags_and_duplicates(&tag_refs);\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500453","messageId":"d9dd860d2aab6346fedc8816891e916e4b948270.1723121979.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723121979.git.ps@pks.im","subject":"[PATCH v2 13/22] builtin/fast-export: plug leaking tag names","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T13:05:30Z","receivedAt":"2024-08-08T13:05:34Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When resolving revisions in `get_tags_and_duplicates()`, we only\npartially manage the lifetime of `full_name`. In fact, managing its\nlifetime properly is almost impossible because we put direct pointers to\nthat variable into multiple lists without duplicating the string. The\nconsequence is that these strings will ultimately leak.\n\nRefactor the code to make the lists we put those names into duplicate\nthe memory. This allows us to properly free the string as required and\nthus plugs the memory leak.\n\nWhile this requires us to allocate more data overall, it shouldn't be\nall that bad given that the number of allocations corresponds with the\nnumber of command line parameters, which typically aren't all that many.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fast-export.c            | 17 ++++++++++++-----\n t/t9351-fast-export-anonymize.sh |  1 +\n 2 files changed, 13 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex fe92d2436c..f253b79322 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -42,8 +42,8 @@ static int full_tree;\n static int reference_excluded_commits;\n static int show_original_ids;\n static int mark_tags;\n-static struct string_list extra_refs = STRING_LIST_INIT_NODUP;\n-static struct string_list tag_refs = STRING_LIST_INIT_NODUP;\n+static struct string_list extra_refs = STRING_LIST_INIT_DUP;\n+static struct string_list tag_refs = STRING_LIST_INIT_DUP;\n static struct refspec refspecs = REFSPEC_INIT_FETCH;\n static int anonymize;\n static struct hashmap anonymized_seeds;\n@@ -901,7 +901,7 @@ static void handle_tag(const char *name, struct tag *tag)\n \tfree(buf);\n }\n \n-static struct commit *get_commit(struct rev_cmdline_entry *e, char *full_name)\n+static struct commit *get_commit(struct rev_cmdline_entry *e, const char *full_name)\n {\n \tswitch (e->item->type) {\n \tcase OBJ_COMMIT:\n@@ -932,14 +932,16 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)\n \t\tstruct rev_cmdline_entry *e = info->rev + i;\n \t\tstruct object_id oid;\n \t\tstruct commit *commit;\n-\t\tchar *full_name;\n+\t\tchar *full_name = NULL;\n \n \t\tif (e->flags & UNINTERESTING)\n \t\t\tcontinue;\n \n \t\tif (repo_dwim_ref(the_repository, e->name, strlen(e->name),\n-\t\t\t\t  &oid, &full_name, 0) != 1)\n+\t\t\t\t  &oid, &full_name, 0) != 1) {\n+\t\t\tfree(full_name);\n \t\t\tcontinue;\n+\t\t}\n \n \t\tif (refspecs.nr) {\n \t\t\tchar *private;\n@@ -955,6 +957,7 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)\n \t\t\twarning(\"%s: Unexpected object of type %s, skipping.\",\n \t\t\t\te->name,\n \t\t\t\ttype_name(e->item->type));\n+\t\t\tfree(full_name);\n \t\t\tcontinue;\n \t\t}\n \n@@ -963,10 +966,12 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)\n \t\t\tbreak;\n \t\tcase OBJ_BLOB:\n \t\t\texport_blob(&commit->object.oid);\n+\t\t\tfree(full_name);\n \t\t\tcontinue;\n \t\tdefault: /* OBJ_TAG (nested tags) is already handled */\n \t\t\twarning(\"Tag points to object of unexpected type %s, skipping.\",\n \t\t\t\ttype_name(commit->object.type));\n+\t\t\tfree(full_name);\n \t\t\tcontinue;\n \t\t}\n \n@@ -979,6 +984,8 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)\n \n \t\tif (!*revision_sources_at(&revision_sources, commit))\n \t\t\t*revision_sources_at(&revision_sources, commit) = full_name;\n+\t\telse\n+\t\t\tfree(full_name);\n \t}\n \n \tstring_list_sort(&extra_refs);\ndiff --git a/t/t9351-fast-export-anonymize.sh b/t/t9351-fast-export-anonymize.sh\nindex 156a647484..c0d9d7be75 100755\n--- a/t/t9351-fast-export-anonymize.sh\n+++ b/t/t9351-fast-export-anonymize.sh\n@@ -4,6 +4,7 @@ test_description='basic tests for fast-export --anonymize'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup simple repo' '\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500454","messageId":"8f6860485e8f806477964575928d31dba8373f66.1723121979.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723121979.git.ps@pks.im","subject":"[PATCH v2 14/22] merge-ort: unconditionally release attributes index","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T13:05:35Z","receivedAt":"2024-08-08T13:05:39Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We conditionally release the index used for reading gitattributes in\nmerge-ort based on whether or the index has been populated. This check\nuses `cache_nr` as a condition. This isn't sufficient though, as the\nvariable may be zero even when some other parts of the index have been\npopulated. This leads to memory leaks when sparse checkouts are in use,\nas we may not end up releasing the sparse checkout patterns.\n\nFix this issue by unconditionally releasing the index.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n merge-ort.c                       | 3 +--\n t/t3507-cherry-pick-conflict.sh   | 1 +\n t/t6421-merge-partial-clone.sh    | 1 +\n t/t6428-merge-conflicts-sparse.sh | 1 +\n t/t7817-grep-sparse-checkout.sh   | 1 +\n 5 files changed, 5 insertions(+), 2 deletions(-)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex e9d01ac7f7..3752c7e595 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -689,8 +689,7 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,\n \t */\n \tstrmap_clear_func(&opti->conflicted, 0);\n \n-\tif (opti->attr_index.cache_nr) /* true iff opt->renormalize */\n-\t\tdiscard_index(&opti->attr_index);\n+\tdiscard_index(&opti->attr_index);\n \n \t/* Free memory used by various renames maps */\n \tfor (i = MERGE_SIDE1; i <= MERGE_SIDE2; ++i) {\ndiff --git a/t/t3507-cherry-pick-conflict.sh b/t/t3507-cherry-pick-conflict.sh\nindex f3947b400a..10e9c91dbb 100755\n--- a/t/t3507-cherry-pick-conflict.sh\n+++ b/t/t3507-cherry-pick-conflict.sh\n@@ -13,6 +13,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n TEST_CREATE_REPO_NO_TEMPLATE=1\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n pristine_detach () {\ndiff --git a/t/t6421-merge-partial-clone.sh b/t/t6421-merge-partial-clone.sh\nindex 711b709e75..020375c805 100755\n--- a/t/t6421-merge-partial-clone.sh\n+++ b/t/t6421-merge-partial-clone.sh\n@@ -26,6 +26,7 @@ test_description=\"limiting blob downloads when merging with partial clones\"\n #                     underscore notation is to differentiate different\n #                     files that might be renamed into each other's paths.)\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-merge.sh\n \ndiff --git a/t/t6428-merge-conflicts-sparse.sh b/t/t6428-merge-conflicts-sparse.sh\nindex 9919c3fa7c..8a79bc2e92 100755\n--- a/t/t6428-merge-conflicts-sparse.sh\n+++ b/t/t6428-merge-conflicts-sparse.sh\n@@ -22,6 +22,7 @@ test_description=\"merge cases\"\n #                     underscore notation is to differentiate different\n #                     files that might be renamed into each other's paths.)\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-merge.sh\n \ndiff --git a/t/t7817-grep-sparse-checkout.sh b/t/t7817-grep-sparse-checkout.sh\nindex eb59564565..0ba7817fb7 100755\n--- a/t/t7817-grep-sparse-checkout.sh\n+++ b/t/t7817-grep-sparse-checkout.sh\n@@ -33,6 +33,7 @@ should leave the following structure in the working tree:\n But note that sub2 should have the SKIP_WORKTREE bit set.\n '\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup' '\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500455","messageId":"ea6a350f31b7c6cf9ada714759afdddf3c672692.1723121979.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723121979.git.ps@pks.im","subject":"[PATCH v2 15/22] sequencer: release todo list on error paths","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T13:05:40Z","receivedAt":"2024-08-08T13:05:44Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We're not releasing the `todo_list` in `sequencer_pick_revisions()` when\nhitting an error path. Restructure the function to have a common exit\npath such that we can easily clean up the list and thus plug this memory\nleak.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n sequencer.c                     | 66 +++++++++++++++++++++++----------\n t/t3510-cherry-pick-sequence.sh |  1 +\n 2 files changed, 48 insertions(+), 19 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex cade9b0ca8..ea559c31f1 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5490,8 +5490,10 @@ int sequencer_pick_revisions(struct repository *r,\n \tint i, res;\n \n \tassert(opts->revs);\n-\tif (read_and_refresh_cache(r, opts))\n-\t\treturn -1;\n+\tif (read_and_refresh_cache(r, opts)) {\n+\t\tres = -1;\n+\t\tgoto out;\n+\t}\n \n \tfor (i = 0; i < opts->revs->pending.nr; i++) {\n \t\tstruct object_id oid;\n@@ -5506,11 +5508,14 @@ int sequencer_pick_revisions(struct repository *r,\n \t\t\t\tenum object_type type = oid_object_info(r,\n \t\t\t\t\t\t\t\t\t&oid,\n \t\t\t\t\t\t\t\t\tNULL);\n-\t\t\t\treturn error(_(\"%s: can't cherry-pick a %s\"),\n-\t\t\t\t\tname, type_name(type));\n+\t\t\t\tres = error(_(\"%s: can't cherry-pick a %s\"),\n+\t\t\t\t\t    name, type_name(type));\n+\t\t\t\tgoto out;\n \t\t\t}\n-\t\t} else\n-\t\t\treturn error(_(\"%s: bad revision\"), name);\n+\t\t} else {\n+\t\t\tres = error(_(\"%s: bad revision\"), name);\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \n \t/*\n@@ -5525,14 +5530,23 @@ int sequencer_pick_revisions(struct repository *r,\n \t    opts->revs->no_walk &&\n \t    !opts->revs->cmdline.rev->flags) {\n \t\tstruct commit *cmit;\n-\t\tif (prepare_revision_walk(opts->revs))\n-\t\t\treturn error(_(\"revision walk setup failed\"));\n+\n+\t\tif (prepare_revision_walk(opts->revs)) {\n+\t\t\tres = error(_(\"revision walk setup failed\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n \t\tcmit = get_revision(opts->revs);\n-\t\tif (!cmit)\n-\t\t\treturn error(_(\"empty commit set passed\"));\n+\t\tif (!cmit) {\n+\t\t\tres = error(_(\"empty commit set passed\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n \t\tif (get_revision(opts->revs))\n \t\t\tBUG(\"unexpected extra commit from walk\");\n-\t\treturn single_pick(r, cmit, opts);\n+\n+\t\tres = single_pick(r, cmit, opts);\n+\t\tgoto out;\n \t}\n \n \t/*\n@@ -5542,16 +5556,30 @@ int sequencer_pick_revisions(struct repository *r,\n \t */\n \n \tif (walk_revs_populate_todo(&todo_list, opts) ||\n-\t\t\tcreate_seq_dir(r) < 0)\n-\t\treturn -1;\n-\tif (repo_get_oid(r, \"HEAD\", &oid) && (opts->action == REPLAY_REVERT))\n-\t\treturn error(_(\"can't revert as initial commit\"));\n-\tif (save_head(oid_to_hex(&oid)))\n-\t\treturn -1;\n-\tif (save_opts(opts))\n-\t\treturn -1;\n+\t\t\tcreate_seq_dir(r) < 0) {\n+\t\tres = -1;\n+\t\tgoto out;\n+\t}\n+\n+\tif (repo_get_oid(r, \"HEAD\", &oid) && (opts->action == REPLAY_REVERT)) {\n+\t\tres = error(_(\"can't revert as initial commit\"));\n+\t\tgoto out;\n+\t}\n+\n+\tif (save_head(oid_to_hex(&oid))) {\n+\t\tres = -1;\n+\t\tgoto out;\n+\t}\n+\n+\tif (save_opts(opts)) {\n+\t\tres = -1;\n+\t\tgoto out;\n+\t}\n+\n \tupdate_abort_safety_file();\n \tres = pick_commits(r, &todo_list, opts);\n+\n+out:\n \ttodo_list_release(&todo_list);\n \treturn res;\n }\ndiff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh\nindex 7eb52b12ed..93c725bac3 100755\n--- a/t/t3510-cherry-pick-sequence.sh\n+++ b/t/t3510-cherry-pick-sequence.sh\n@@ -12,6 +12,7 @@ test_description='Test cherry-pick continuation features\n \n '\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # Repeat first match 10 times\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500456","messageId":"2755023742e33da6e420638ea1bc90cd31150407.1723121979.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723121979.git.ps@pks.im","subject":"[PATCH v2 16/22] unpack-trees: clear index when not propagating it","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T13:05:45Z","receivedAt":"2024-08-08T13:05:48Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When provided a pointer to a destination index, then `unpack_trees()`\nwill end up copying its `o->internal.result` index into the provided\npointer. In those cases it is thus not necessary to free the index, as\nwe have transferred ownership of it.\n\nThere are cases though where we do not end up transferring ownership of\nthe memory, but `clear_unpack_trees_porcelain()` will never discard the\nindex in that case and thus cause a memory leak. And right now it cannot\ndo so in the first place because we have no indicator of whether we did\nor didn't transfer ownership of the index.\n\nAdapt the code to zero out the index in case we transfer its ownership.\nLike this, we can now unconditionally discard the index when being asked\nto clear the `unpack_trees_options`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n t/t3705-add-sparse-checkout.sh | 1 +\n unpack-trees.c                 | 2 ++\n 2 files changed, 3 insertions(+)\n\ndiff --git a/t/t3705-add-sparse-checkout.sh b/t/t3705-add-sparse-checkout.sh\nindex 2bade9e804..6ae45a788d 100755\n--- a/t/t3705-add-sparse-checkout.sh\n+++ b/t/t3705-add-sparse-checkout.sh\n@@ -2,6 +2,7 @@\n \n test_description='git add in sparse checked out working trees'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n SPARSE_ENTRY_BLOB=\"\"\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 7dc884fafd..9a55cb6204 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -210,6 +210,7 @@ void clear_unpack_trees_porcelain(struct unpack_trees_options *opts)\n {\n \tstrvec_clear(&opts->internal.msgs_to_free);\n \tmemset(opts->internal.msgs, 0, sizeof(opts->internal.msgs));\n+\tdiscard_index(&opts->internal.result);\n }\n \n static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce,\n@@ -2082,6 +2083,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options\n \t\to->internal.result.updated_workdir = 1;\n \t\tdiscard_index(o->dst_index);\n \t\t*o->dst_index = o->internal.result;\n+\t\tmemset(&o->internal.result, 0, sizeof(o->internal.result));\n \t} else {\n \t\tdiscard_index(&o->internal.result);\n \t}\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500457","messageId":"edf6f148cd2f7bb140c878198a054ae7d9f708c2.1723121979.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723121979.git.ps@pks.im","subject":"[PATCH v2 17/22] diff: fix leak when parsing invalid ignore regex option","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T13:05:49Z","receivedAt":"2024-08-08T13:05:53Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When parsing invalid ignore regexes passed via the `-I` option we don't\nfree already-allocated memory, leading to a memory leak. Fix this.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n diff.c                  | 6 +++++-\n t/t4013-diff-various.sh | 1 +\n 2 files changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/diff.c b/diff.c\nindex ebb7538e04..9251c47b72 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -5464,9 +5464,13 @@ static int diff_opt_ignore_regex(const struct option *opt,\n \tregex_t *regex;\n \n \tBUG_ON_OPT_NEG(unset);\n+\n \tregex = xmalloc(sizeof(*regex));\n-\tif (regcomp(regex, arg, REG_EXTENDED | REG_NEWLINE))\n+\tif (regcomp(regex, arg, REG_EXTENDED | REG_NEWLINE)) {\n+\t\tfree(regex);\n \t\treturn error(_(\"invalid regex given to -I: '%s'\"), arg);\n+\t}\n+\n \tALLOC_GROW(options->ignore_regex, options->ignore_regex_nr + 1,\n \t\t   options->ignore_regex_alloc);\n \toptions->ignore_regex[options->ignore_regex_nr++] = regex;\ndiff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh\nindex 3855d68dbc..87d248d034 100755\n--- a/t/t4013-diff-various.sh\n+++ b/t/t4013-diff-various.sh\n@@ -8,6 +8,7 @@ test_description='Various diff formatting options'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-diff.sh\n \n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500458","messageId":"343e3bd4dffcde04d42ef005c715cada724bba91.1723121979.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723121979.git.ps@pks.im","subject":"[PATCH v2 18/22] builtin/format-patch: fix various trivial memory leaks","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T13:05:55Z","receivedAt":"2024-08-08T13:05:59Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"There are various memory leaks hit by git-format-patch(1). Basically all\nof them are trivial, except that un-setting `diffopt.no_free` requires\nus to unset the `diffopt.file` because we manually close it already.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/log.c           | 12 +++++++++---\n t/t4014-format-patch.sh |  1 +\n 2 files changed, 10 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex a73a767606..ff997a0d0e 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1833,6 +1833,7 @@ static struct commit *get_base_commit(const struct format_config *cfg,\n \t\t\t}\n \n \t\t\trev[i] = merge_base->item;\n+\t\t\tfree_commit_list(merge_base);\n \t\t}\n \n \t\tif (rev_nr % 2)\n@@ -2023,6 +2024,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \tconst char *rfc = NULL;\n \tint creation_factor = -1;\n \tconst char *signature = git_version_string;\n+\tchar *signature_to_free = NULL;\n \tchar *signature_file_arg = NULL;\n \tstruct keep_callback_data keep_callback_data = {\n \t\t.cfg = &cfg,\n@@ -2443,7 +2445,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \n \t\tif (strbuf_read_file(&buf, signature_file, 128) < 0)\n \t\t\tdie_errno(_(\"unable to read signature file '%s'\"), signature_file);\n-\t\tsignature = strbuf_detach(&buf, NULL);\n+\t\tsignature = signature_to_free = strbuf_detach(&buf, NULL);\n \t} else if (cfg.signature) {\n \t\tsignature = cfg.signature;\n \t}\n@@ -2548,12 +2550,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \t\t\telse\n \t\t\t\tprint_signature(signature, rev.diffopt.file);\n \t\t}\n-\t\tif (output_directory)\n+\t\tif (output_directory) {\n \t\t\tfclose(rev.diffopt.file);\n+\t\t\trev.diffopt.file = NULL;\n+\t\t}\n \t}\n \tstop_progress(&progress);\n \tfree(list);\n-\tfree(branch_name);\n \tif (ignore_if_in_upstream)\n \t\tfree_patch_ids(&ids);\n \n@@ -2565,11 +2568,14 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \tstrbuf_release(&rdiff_title);\n \tfree(description_file);\n \tfree(signature_file_arg);\n+\tfree(signature_to_free);\n+\tfree(branch_name);\n \tfree(to_free);\n \tfree(rev.message_id);\n \tif (rev.ref_message_ids)\n \t\tstring_list_clear(rev.ref_message_ids, 0);\n \tfree(rev.ref_message_ids);\n+\trev.diffopt.no_free = 0;\n \trelease_revisions(&rev);\n \tformat_config_release(&cfg);\n \treturn 0;\ndiff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh\nindex 884f83fb8a..1c46e963e4 100755\n--- a/t/t4014-format-patch.sh\n+++ b/t/t4014-format-patch.sh\n@@ -8,6 +8,7 @@ test_description='various format-patch tests'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-terminal.sh\n \n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500459","messageId":"be2c5b0bcaa8c327c11f49b0dbfcb441bfa1fe66.1723121979.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723121979.git.ps@pks.im","subject":"[PATCH v2 19/22] userdiff: fix leaking memory for configured diff drivers","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T13:05:59Z","receivedAt":"2024-08-08T13:06:03Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The userdiff structures may be initialized either statically on the\nstack or dynamically via configuration keys. In the latter case we end\nup leaking memory because we didn't have any infrastructure to discern\nthose strings which have been allocated statically and those which have\nbeen allocated dynamically.\n\nRefactor the code such that we have two pointers for each of these\nstrings: one that holds the value as accessed by other subsystems, and\none that points to the same string in case it has been allocated. Like\nthis, we can safely free the second pointer and thus plug those memory\nleaks.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n range-diff.c                     |  6 +++--\n t/t4018-diff-funcname.sh         |  1 +\n t/t4042-diff-textconv-caching.sh |  2 ++\n t/t4048-diff-combined-binary.sh  |  1 +\n t/t4209-log-pickaxe.sh           |  2 ++\n userdiff.c                       | 38 ++++++++++++++++++++++++--------\n userdiff.h                       |  4 ++++\n 7 files changed, 43 insertions(+), 11 deletions(-)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex 5f01605550..bbb0952264 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -450,8 +450,10 @@ static void output_pair_header(struct diff_options *diffopt,\n }\n \n static struct userdiff_driver section_headers = {\n-\t.funcname = { \"^ ## (.*) ##$\\n\"\n-\t\t      \"^.?@@ (.*)$\", REG_EXTENDED }\n+\t.funcname = {\n+\t\t.pattern = \"^ ## (.*) ##$\\n^.?@@ (.*)$\",\n+\t\t.cflags = REG_EXTENDED,\n+\t},\n };\n \n static struct diff_filespec *get_filespec(const char *name, const char *p)\ndiff --git a/t/t4018-diff-funcname.sh b/t/t4018-diff-funcname.sh\nindex e026fac1f4..8128c30e7f 100755\n--- a/t/t4018-diff-funcname.sh\n+++ b/t/t4018-diff-funcname.sh\n@@ -5,6 +5,7 @@\n \n test_description='Test custom diff function name patterns'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup' '\ndiff --git a/t/t4042-diff-textconv-caching.sh b/t/t4042-diff-textconv-caching.sh\nindex 8ebfa3c1be..a179205394 100755\n--- a/t/t4042-diff-textconv-caching.sh\n+++ b/t/t4042-diff-textconv-caching.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='test textconv caching'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n cat >helper <<'EOF'\ndiff --git a/t/t4048-diff-combined-binary.sh b/t/t4048-diff-combined-binary.sh\nindex 0260cf64f5..f399484bce 100755\n--- a/t/t4048-diff-combined-binary.sh\n+++ b/t/t4048-diff-combined-binary.sh\n@@ -4,6 +4,7 @@ test_description='combined and merge diff handle binary files and textconv'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup binary merge conflict' '\ndiff --git a/t/t4209-log-pickaxe.sh b/t/t4209-log-pickaxe.sh\nindex 64e1623733..b42fdc54fc 100755\n--- a/t/t4209-log-pickaxe.sh\n+++ b/t/t4209-log-pickaxe.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='log --grep/--author/--regexp-ignore-case/-S/-G'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_log () {\ndiff --git a/userdiff.c b/userdiff.c\nindex c4ebb9ff73..989629149f 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -399,8 +399,11 @@ static struct userdiff_driver *userdiff_find_by_namelen(const char *name, size_t\n static int parse_funcname(struct userdiff_funcname *f, const char *k,\n \t\tconst char *v, int cflags)\n {\n-\tif (git_config_string((char **) &f->pattern, k, v) < 0)\n+\tf->pattern = NULL;\n+\tFREE_AND_NULL(f->pattern_owned);\n+\tif (git_config_string(&f->pattern_owned, k, v) < 0)\n \t\treturn -1;\n+\tf->pattern = f->pattern_owned;\n \tf->cflags = cflags;\n \treturn 0;\n }\n@@ -444,20 +447,37 @@ int userdiff_config(const char *k, const char *v)\n \t\treturn parse_funcname(&drv->funcname, k, v, REG_EXTENDED);\n \tif (!strcmp(type, \"binary\"))\n \t\treturn parse_tristate(&drv->binary, k, v);\n-\tif (!strcmp(type, \"command\"))\n-\t\treturn git_config_string((char **) &drv->external.cmd, k, v);\n+\tif (!strcmp(type, \"command\")) {\n+\t\tFREE_AND_NULL(drv->external.cmd);\n+\t\treturn git_config_string(&drv->external.cmd, k, v);\n+\t}\n \tif (!strcmp(type, \"trustexitcode\")) {\n \t\tdrv->external.trust_exit_code = git_config_bool(k, v);\n \t\treturn 0;\n \t}\n-\tif (!strcmp(type, \"textconv\"))\n-\t\treturn git_config_string((char **) &drv->textconv, k, v);\n+\tif (!strcmp(type, \"textconv\")) {\n+\t\tint ret;\n+\t\tFREE_AND_NULL(drv->textconv_owned);\n+\t\tret = git_config_string(&drv->textconv_owned, k, v);\n+\t\tdrv->textconv = drv->textconv_owned;\n+\t\treturn ret;\n+\t}\n \tif (!strcmp(type, \"cachetextconv\"))\n \t\treturn parse_bool(&drv->textconv_want_cache, k, v);\n-\tif (!strcmp(type, \"wordregex\"))\n-\t\treturn git_config_string((char **) &drv->word_regex, k, v);\n-\tif (!strcmp(type, \"algorithm\"))\n-\t\treturn git_config_string((char **) &drv->algorithm, k, v);\n+\tif (!strcmp(type, \"wordregex\")) {\n+\t\tint ret;\n+\t\tFREE_AND_NULL(drv->word_regex_owned);\n+\t\tret = git_config_string(&drv->word_regex_owned, k, v);\n+\t\tdrv->word_regex = drv->word_regex_owned;\n+\t\treturn ret;\n+\t}\n+\tif (!strcmp(type, \"algorithm\")) {\n+\t\tint ret;\n+\t\tFREE_AND_NULL(drv->algorithm_owned);\n+\t\tret = git_config_string(&drv->algorithm_owned, k, v);\n+\t\tdrv->algorithm = drv->algorithm_owned;\n+\t\treturn ret;\n+\t}\n \n \treturn 0;\n }\ndiff --git a/userdiff.h b/userdiff.h\nindex 7565930337..827361b0bc 100644\n--- a/userdiff.h\n+++ b/userdiff.h\n@@ -8,6 +8,7 @@ struct repository;\n \n struct userdiff_funcname {\n \tconst char *pattern;\n+\tchar *pattern_owned;\n \tint cflags;\n };\n \n@@ -20,11 +21,14 @@ struct userdiff_driver {\n \tconst char *name;\n \tstruct external_diff external;\n \tconst char *algorithm;\n+\tchar *algorithm_owned;\n \tint binary;\n \tstruct userdiff_funcname funcname;\n \tconst char *word_regex;\n+\tchar *word_regex_owned;\n \tconst char *word_regex_multi_byte;\n \tconst char *textconv;\n+\tchar *textconv_owned;\n \tstruct notes_cache *textconv_cache;\n \tint textconv_want_cache;\n };\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500460","messageId":"78882038333ae40bc4520d60e42b590c68a5dd8d.1723121979.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723121979.git.ps@pks.im","subject":"[PATCH v2 20/22] builtin/log: fix leak when showing converted blob contents","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T13:06:07Z","receivedAt":"2024-08-08T13:06:11Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `show_blob_object()`, we proactively call `textconv_object()`. In\ncase we have a textconv driver for this blob we will end up showing the\nconverted contents, otherwise we'll show the un-converted contents of it\ninstead.\n\nWhen the object has been converted we never free the buffer containing\nthe converted contents. Fix this to plug this memory leak.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/log.c            | 1 +\n t/t4030-diff-textconv.sh | 2 ++\n 2 files changed, 3 insertions(+)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex ff997a0d0e..1a684b68f2 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -707,6 +707,7 @@ static int show_blob_object(const struct object_id *oid, struct rev_info *rev, c\n \n \twrite_or_die(1, buf, size);\n \tobject_context_release(&obj_context);\n+\tfree(buf);\n \treturn 0;\n }\n \ndiff --git a/t/t4030-diff-textconv.sh b/t/t4030-diff-textconv.sh\nindex a39a626664..29f6d610c2 100755\n--- a/t/t4030-diff-textconv.sh\n+++ b/t/t4030-diff-textconv.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='diff.*.textconv tests'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n find_diff() {\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500461","messageId":"245fc30afb4d9993381ecf53f8c081b17dde3539.1723121979.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723121979.git.ps@pks.im","subject":"[PATCH v2 21/22] diff: free state populated via options","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T13:06:12Z","receivedAt":"2024-08-08T13:06:15Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `objfind` and `anchors` members of `struct diff_options` are\npopulated via option parsing, but are never freed in `diff_free()`. Fix\nthis to plug those memory leaks.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n diff.c                   | 10 ++++++++++\n t/t4064-diff-oidfind.sh  |  2 ++\n t/t4065-diff-anchored.sh |  1 +\n t/t4069-remerge-diff.sh  |  1 +\n 4 files changed, 14 insertions(+)\n\ndiff --git a/diff.c b/diff.c\nindex 9251c47b72..4035a9374d 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -6717,6 +6717,16 @@ void diff_free(struct diff_options *options)\n \tif (options->no_free)\n \t\treturn;\n \n+\tif (options->objfind) {\n+\t\toidset_clear(options->objfind);\n+\t\tFREE_AND_NULL(options->objfind);\n+\t}\n+\n+\tfor (size_t i = 0; i < options->anchors_nr; i++)\n+\t\tfree(options->anchors[i]);\n+\tFREE_AND_NULL(options->anchors);\n+\toptions->anchors_nr = options->anchors_alloc = 0;\n+\n \tdiff_free_file(options);\n \tdiff_free_ignore_regex(options);\n \tclear_pathspec(&options->pathspec);\ndiff --git a/t/t4064-diff-oidfind.sh b/t/t4064-diff-oidfind.sh\nindex 6d8c8986fc..846f285f77 100755\n--- a/t/t4064-diff-oidfind.sh\n+++ b/t/t4064-diff-oidfind.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='test finding specific blobs in the revision walking'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup ' '\ndiff --git a/t/t4065-diff-anchored.sh b/t/t4065-diff-anchored.sh\nindex b3f510f040..647537c12e 100755\n--- a/t/t4065-diff-anchored.sh\n+++ b/t/t4065-diff-anchored.sh\n@@ -2,6 +2,7 @@\n \n test_description='anchored diff algorithm'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success '--anchored' '\ndiff --git a/t/t4069-remerge-diff.sh b/t/t4069-remerge-diff.sh\nindex 07323ebafe..888714bbd3 100755\n--- a/t/t4069-remerge-diff.sh\n+++ b/t/t4069-remerge-diff.sh\n@@ -2,6 +2,7 @@\n \n test_description='remerge-diff handling'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # This test is ort-specific\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500462","messageId":"343ddcd17b3e13dc7b8ab89f9c9c427bb2ed101c.1723121979.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723121979.git.ps@pks.im","subject":"[PATCH v2 22/22] builtin/diff: free symmetric diff members","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-08T13:06:16Z","receivedAt":"2024-08-08T13:06:20Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We populate a `struct symdiff` in case the user has requested a\nsymmetric diff. Part of this is to populate a `skip` bitmap that\nindicates whihc commits shall be ignored in the diff. But while this\nbitmap is dynamically allocated, we never free it.\n\nFix this by introducing and calling a new `symdiff_release()` function\nthat does this for us.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/diff.c                       | 10 +++++++++-\n t/t4068-diff-symmetric-merge-base.sh |  1 +\n t/t4108-apply-threeway.sh            |  1 +\n 3 files changed, 11 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/diff.c b/builtin/diff.c\nindex 9b6cdabe15..f87f68a5bc 100644\n--- a/builtin/diff.c\n+++ b/builtin/diff.c\n@@ -388,6 +388,13 @@ static void symdiff_prepare(struct rev_info *rev, struct symdiff *sym)\n \tsym->skip = map;\n }\n \n+static void symdiff_release(struct symdiff *sdiff)\n+{\n+\tif (!sdiff)\n+\t\treturn;\n+\tbitmap_free(sdiff->skip);\n+}\n+\n int cmd_diff(int argc, const char **argv, const char *prefix)\n {\n \tint i;\n@@ -398,7 +405,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)\n \tstruct object_array_entry *blob[2];\n \tint nongit = 0, no_index = 0;\n \tint result;\n-\tstruct symdiff sdiff;\n+\tstruct symdiff sdiff = {0};\n \n \t/*\n \t * We could get N tree-ish in the rev.pending_objects list.\n@@ -619,6 +626,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)\n \t\trefresh_index_quietly();\n \trelease_revisions(&rev);\n \tobject_array_clear(&ent);\n+\tsymdiff_release(&sdiff);\n \tUNLEAK(blob);\n \treturn result;\n }\ndiff --git a/t/t4068-diff-symmetric-merge-base.sh b/t/t4068-diff-symmetric-merge-base.sh\nindex eff63c16b0..4d6565e728 100755\n--- a/t/t4068-diff-symmetric-merge-base.sh\n+++ b/t/t4068-diff-symmetric-merge-base.sh\n@@ -5,6 +5,7 @@ test_description='behavior of diff with symmetric-diff setups and --merge-base'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # build these situations:\ndiff --git a/t/t4108-apply-threeway.sh b/t/t4108-apply-threeway.sh\nindex c558282bc0..3211e1e65f 100755\n--- a/t/t4108-apply-threeway.sh\n+++ b/t/t4108-apply-threeway.sh\n@@ -5,6 +5,7 @@ test_description='git apply --3way'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n print_sanitized_conflicted_diff () {\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500482","messageId":"xmqq1q2zko2a.fsf@gitster.g","threadId":"61910","inReplyTo":"ZrRR9mwyQQrw4Dg5@tanuki","subject":"Re: [PATCH 08/22] config: fix leaking comment character config","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-08-08T15:54:21Z","receivedAt":"2024-08-08T15:54:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Now that I revisit this commit I'm not quite happy with it anymore. We\n> still need to have the cast, which is somewhat awkward. I think the\n> better solution is to instead have a `comment_line_str_allocated`\n> variable that is non-constant. I'll adapt the code accordingly.\n>\n> An even better solution would be to have `struct strbuf` provide an\n> initializer that populates it with a string constant. But that feels\n> like a larger undertaking, so I'll leave that for the future.\n\nFWIW, I found the \"now we have a variable to refer to the address of\nthe string constant, we can compare to detect if we allocated and\nneed to free\" in this round is a good place to stop.\n\nI view the approach to use an auxiliary variable *_allocated is a\nregression compared to what we see here.  The approach makes it easy\nto forget to futz it when an allocated piece of memory is assigned\nto the main variable.\n"},{"id":"500484","messageId":"xmqqwmkrj8zb.fsf@gitster.g","threadId":"61910","inReplyTo":"ZrRSDaCClfh9dMZK@tanuki","subject":"Re: [PATCH 19/22] userdiff: fix leaking memory for configured diff drivers","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-08-08T16:05:28Z","receivedAt":"2024-08-08T16:05:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n>> > -\tif (git_config_string((char **) &f->pattern, k, v) < 0)\n>> > +\tf->pattern = NULL;\n>> > +\tFREE_AND_NULL(f->pattern_owned);\n>> > +\tif (git_config_string(&f->pattern_owned, k, v) < 0)\n>> >  \t\treturn -1;\n>> > +\tf->pattern = f->pattern_owned;\n>> >  \tf->cflags = cflags;\n>> >  \treturn 0;\n>> >  }\n>\n> Yup. We have a bunch of statically defined userdiff drivers, all of\n> which use string constants as patterns. We thus cannot reliably free\n> those and instead have to track the allocated strings in a separate\n> variable.\n\nIn other words, this is the usual \"foo is the variable to be used,\nand it may point at foo_to_free, when the value is an allocated\nstring\" pattern.  I doubt .pattern_to_free is a better name even in\nthe name of consistency---foo_to_free is certainly much better than\nfoo_owned as a name for a temporary variable in a small scope, but a\nstructure member is a much longer validity and I am OK if we decide\nto adopt the convention to call a structure member .foo_owned when\nit is used in this manner.\n\nThanks.\n\n"},{"id":"500491","messageId":"xmqqmslnht7o.fsf@gitster.g","threadId":"61910","inReplyTo":"e0685657-369f-4211-8518-cc41aa28dcb9@gmail.com","subject":"Re: [PATCH 15/22] sequencer: release todo list on error paths","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-08-08T16:31:23Z","receivedAt":"2024-08-08T16:31:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> Hi Patrick\n>\n> On 06/08/2024 10:00, Patrick Steinhardt wrote:\n>> We're not releasing the `todo_list` in `sequencer_pick_revisions()` when\n>> hitting an error path. Restructure the function to have a common exit\n>> path such that we can easily clean up the list and thus plug this memory\n>> leak.\n>\n> This looks good, I've left a couple of small formatting comments below\n> if you do end up re-rolling.\n\nOh, formatting nitpicks, my favourite ;-)\n\n>> @@ -5506,11 +5508,14 @@ int sequencer_pick_revisions(struct repository *r,\n>>   \t\t\t\tenum object_type type = oid_object_info(r,\n>>   \t\t\t\t\t\t\t\t\t&oid,\n>>   \t\t\t\t\t\t\t\t\tNULL);\n\nAlso, if we say\n\n\t\t\t\tenum object_type type;\n\n\t\t\t\ttype = oid_object_info(r, &oid, NULL);\n\nthe result is much easier on the eyes usign the same three lines.\nYes, initializing while declaring may look nicer and in some cases\nit may even be necessary, but not this one.\n\n>> -\t\t\t\treturn error(_(\"%s: can't cherry-pick a %s\"),\n>> +\t\t\t\tres = error(_(\"%s: can't cherry-pick a %s\"),\n>>   \t\t\t\t\tname, type_name(type));\n>\n> This line needs re-indenting to match the changes above.\n\nThanks.\n"},{"id":"500504","messageId":"xmqq34nfhrb9.fsf@gitster.g","threadId":"61910","inReplyTo":"8fbd72a1002d1a285847c62b5524041927a7b4d4.1723121979.git.ps@pks.im","subject":"Re: [PATCH v2 08/22] config: fix leaking comment character config","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-08-08T17:12:26Z","receivedAt":"2024-08-08T17:12:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> diff --git a/config.c b/config.c\n> index 6421894614..cb78b652ee 100644\n> --- a/config.c\n> +++ b/config.c\n> @@ -1596,7 +1596,9 @@ static int git_default_core_config(const char *var, const char *value,\n>  \t\telse if (value[0]) {\n>  \t\t\tif (strchr(value, '\\n'))\n>  \t\t\t\treturn error(_(\"%s cannot contain newline\"), var);\n> -\t\t\tcomment_line_str = xstrdup(value);\n> +\t\t\tfree(comment_line_str_allocated);\n> +\t\t\tcomment_line_str = comment_line_str_allocated =\n> +\t\t\t\txstrdup(value);\n\nIf you are to follow the _to_free pattern, you do not have to\nallocate here, no?  We borrow the value in the configset and point\nat it via comment_line_str, and clear comment_line_str_to_free\nbecause there is nothing to free now.  I.e.\n\n\t\t\tcomment_line_str = value;\n\t\t\tFREE_AND_NULL(comment_line_str_allocated);\n\nI still think the approach taken by the previous iteration was\nsimpler and much less error prone, though.\n"},{"id":"500617","messageId":"Zrm9ix5aN_g76Qxq@tanuki","threadId":"61910","inReplyTo":"xmqq34nfhrb9.fsf@gitster.g","subject":"Re: [PATCH v2 08/22] config: fix leaking comment character config","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-12T07:45:39Z","receivedAt":"2024-08-12T07:45:45Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Aug 08, 2024 at 10:12:26AM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > diff --git a/config.c b/config.c\n> > index 6421894614..cb78b652ee 100644\n> > --- a/config.c\n> > +++ b/config.c\n> > @@ -1596,7 +1596,9 @@ static int git_default_core_config(const char *var, const char *value,\n> >  \t\telse if (value[0]) {\n> >  \t\t\tif (strchr(value, '\\n'))\n> >  \t\t\t\treturn error(_(\"%s cannot contain newline\"), var);\n> > -\t\t\tcomment_line_str = xstrdup(value);\n> > +\t\t\tfree(comment_line_str_allocated);\n> > +\t\t\tcomment_line_str = comment_line_str_allocated =\n> > +\t\t\t\txstrdup(value);\n> \n> If you are to follow the _to_free pattern, you do not have to\n> allocate here, no?  We borrow the value in the configset and point\n> at it via comment_line_str, and clear comment_line_str_to_free\n> because there is nothing to free now.  I.e.\n> \n> \t\t\tcomment_line_str = value;\n> \t\t\tFREE_AND_NULL(comment_line_str_allocated);\n\nOnly if it is guaranteed that the configuration will never be re-read,\nwhich would end up discarding memory owned by the old string. Which\nshould be the case already, but to the best of my knowledge we do not\ndocument the expected lifetime of config strings anywhere.\n\n> I still think the approach taken by the previous iteration was\n> simpler and much less error prone, though.\n\nI personally prefer this iteration. I feel that it is way more\ndiscoverable to have an explicit indicator that something needs to be\nfreed, which the `_allocated` suffix brings us. With the old version,\nthe caller needs to become aware that the constant string may sometimes\nneed to be freed, and that sometimes is figured out by comparing to a\nmagic variable, which feels worse to me.\n\nUltimately, both solutions are okay-ish, but I don't consider either of\nthem to be great. As mentioned elsewhere, I think the best solution\nwould be to adapt the `struct strbuf` interface to have an initializer\nlike `STRBUF_INIT_CONST(\"foobar\")` that allows us to initialize it with\na string constant. There wouldn't be any need to have two variables\nanymore, and the `strbuf` API would handle the lifecycle of its contents\nfor us. In any case, I'd say this is a #leftoverbit and is better done\nin a subsequent patch series.\n\nI don't really think it makes sense to reroll this version to swap out\nthe patch for the first version again, but am happy to adapt if you\nprefer that.\n\nThanks!\n\nPatrick\n"},{"id":"500619","messageId":"CAOLa=ZQtJTd2v3YW1sJCwMafiM619Gqk8mb8qje8EFCxuV3+FQ@mail.gmail.com","threadId":"61910","inReplyTo":"2afa51f9ffedfa6dab51c9515f695ddfe0a9a4f9.1723121979.git.ps@pks.im","subject":"Re: [PATCH v2 01/22] remote: plug memory leak when aliasing URLs","fromName":"karthik nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-08-12T08:27:32Z","receivedAt":"2024-08-12T08:27:34Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> When we have a `url.*.insteadOf` configuration, then we end up aliasing\n> URLs when populating remotes. One place where this happens is in\n> `alias_all_urls()`, where we loop through all remotes and then alias\n> each of their URLs. The actual aliasing logic is then contained in\n> `alias_url()`, which returns an allocated string that contains the new\n> URL. This URL replaces the old URL that we have in the strvec that\n> contanis all remote URLs.\n>\n\ns/contanis/contains\n\n[snip]\n"},{"id":"500620","messageId":"CAOLa=ZRFYL6mWABumWM9pra+749tz7kOg3y9x5Pj9EdQxjC6eA@mail.gmail.com","threadId":"61910","inReplyTo":"43a38a2281f25db837dc9b5a59ecbe12214ac7bf.1723121979.git.ps@pks.im","subject":"Re: [PATCH v2 03/22] object-file: fix memory leak when reading corrupted headers","fromName":"karthik nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-08-12T08:43:22Z","receivedAt":"2024-08-12T08:43:24Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> When reading corrupt object headers in `read_loose_object()`, then we\n\ns/then//\n\n> bail out immediately. This causes a memory leak though because we would\n> have already initialized the zstream in `unpack_loose_header()`, and it\n> is the callers responsibility to finish the zstream even on error. While\n> this feels weird, other callsites do it correctly already.\n>\n> Fix this leak by ending the zstream even on errors. We may want to\n> revisit this interface in the future such that the callee handles this\n> for us already when there was an error.\n>\n\n[snip]\n"},{"id":"500622","messageId":"CAOLa=ZSPtce9WFCyxDP5Ct7Geqj-g5Q=zfi1R3-kcs5Ms8-7YA@mail.gmail.com","threadId":"61910","inReplyTo":"fa2d5c5d6b7b9018c3271a3c955852d1aeaf44d9.1723121979.git.ps@pks.im","subject":"Re: [PATCH v2 12/22] builtin/fast-export: fix leaking diff options","fromName":"karthik nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-08-12T09:05:25Z","receivedAt":"2024-08-12T09:05:27Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Before caling `handle_commit()` in a loop, we set `diffopt.no_free` such\n\ns/caling/calling\n\n> that its contents aren't getting freed inside of `handle_commit()`. We\n> never unset that flag though, which means that it'll ultimately leak\n> when calling `release_revisions()`.\n>\n> Fix this by unsetting the flag after the loop.\n>\n\n[snip]\n"},{"id":"500624","messageId":"CAOLa=ZSjBxVxmr7xiOFk0+2N4sTuxq9pFZt4ytK=L9kyw=Zvig@mail.gmail.com","threadId":"61910","inReplyTo":"343ddcd17b3e13dc7b8ab89f9c9c427bb2ed101c.1723121979.git.ps@pks.im","subject":"Re: [PATCH v2 22/22] builtin/diff: free symmetric diff members","fromName":"karthik nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-08-12T09:12:30Z","receivedAt":"2024-08-12T09:12:31Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> We populate a `struct symdiff` in case the user has requested a\n> symmetric diff. Part of this is to populate a `skip` bitmap that\n> indicates whihc commits shall be ignored in the diff. But while this\n\ns/whihc/which\n\n> bitmap is dynamically allocated, we never free it.\n>\n> Fix this by introducing and calling a new `symdiff_release()` function\n> that does this for us.\n>\n\n[snip]\n"},{"id":"500625","messageId":"CAOLa=ZSCLZU=eeBqx9cpHkUJ+SOHXToVwMKjmCKLc+54MfxdRQ@mail.gmail.com","threadId":"61910","inReplyTo":"cover.1723121979.git.ps@pks.im","subject":"Re: [PATCH v2 00/22] Memory leak fixes (pt.4)","fromName":"karthik nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-08-12T09:13:47Z","receivedAt":"2024-08-12T09:13:49Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Hi,\n>\n> this is the second version of my fourth batch of patches that fix\n> various memory leaks.\n>\n> Changes compared to v1:\n>\n>   - Adapt the memory leak fix for command characters to instead use a\n>     `comment_line_str_allocated` variable.\n>\n>   - Clarify some commit messages.\n>\n>   - Drop the TODO comment about `rebase.gpgsign`. Turns out that this is\n>     working as intended, as explained by Phillip.\n>\n> Thanks!\n>\n\nI went through the series and apart from some typos, everything looked\ngreat. I don't expect a reroll for those typos though, since they're\nminor.\n\n[snip]\n"},{"id":"500631","messageId":"4dbf731a-873b-4cab-b8ff-745bfa059060@gmail.com","threadId":"61910","inReplyTo":"cover.1723121979.git.ps@pks.im","subject":"Re: [PATCH v2 00/22] Memory leak fixes (pt.4)","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-08-12T14:01:43Z","receivedAt":"2024-08-12T14:01:47Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Patrick\n\nOn 08/08/2024 14:04, Patrick Steinhardt wrote:\n> Hi,\n> \n> this is the second version of my fourth batch of patches that fix\n> various memory leaks.\n> \n> Changes compared to v1:\n> \n>    - Adapt the memory leak fix for command characters to instead use a\n>      `comment_line_str_allocated` variable.\n> \n>    - Clarify some commit messages.\n> \n>    - Drop the TODO comment about `rebase.gpgsign`. Turns out that this is\n>      working as intended, as explained by Phillip.\n\nThe changes to the rebase and sequencer patches look good to me\n\nThanks\n\nPhillip\n\n> Thanks!\n> \n> Patrick\n> \n> Patrick Steinhardt (22):\n>    remote: plug memory leak when aliasing URLs\n>    git: fix leaking system paths\n>    object-file: fix memory leak when reading corrupted headers\n>    object-name: fix leaking symlink paths in object context\n>    bulk-checkin: fix leaking state TODO\n>    read-cache: fix leaking hashfile when writing index fails\n>    submodule-config: fix leaking name enrty when traversing submodules\n>    config: fix leaking comment character config\n>    builtin/rebase: fix leaking `commit.gpgsign` value\n>    builtin/notes: fix leaking `struct notes_tree` when merging notes\n>    builtin/fast-import: plug trivial memory leaks\n>    builtin/fast-export: fix leaking diff options\n>    builtin/fast-export: plug leaking tag names\n>    merge-ort: unconditionally release attributes index\n>    sequencer: release todo list on error paths\n>    unpack-trees: clear index when not propagating it\n>    diff: fix leak when parsing invalid ignore regex option\n>    builtin/format-patch: fix various trivial memory leaks\n>    userdiff: fix leaking memory for configured diff drivers\n>    builtin/log: fix leak when showing converted blob contents\n>    diff: free state populated via options\n>    builtin/diff: free symmetric diff members\n> \n>   builtin/commit.c                      |  7 +-\n>   builtin/diff.c                        | 10 ++-\n>   builtin/fast-export.c                 | 19 ++++--\n>   builtin/fast-import.c                 |  8 ++-\n>   builtin/log.c                         | 13 +++-\n>   builtin/notes.c                       |  9 ++-\n>   builtin/rebase.c                      |  1 +\n>   bulk-checkin.c                        |  2 +\n>   config.c                              |  4 +-\n>   csum-file.c                           |  2 +-\n>   csum-file.h                           | 10 +++\n>   diff.c                                | 16 ++++-\n>   environment.c                         |  1 +\n>   environment.h                         |  1 +\n>   git.c                                 | 12 +++-\n>   merge-ort.c                           |  3 +-\n>   object-file.c                         |  1 +\n>   object-name.c                         |  1 +\n>   range-diff.c                          |  6 +-\n>   read-cache.c                          | 97 ++++++++++++++++-----------\n>   remote.c                              |  2 +\n>   sequencer.c                           | 67 ++++++++++++------\n>   submodule-config.c                    | 18 +++--\n>   t/t0210-trace2-normal.sh              |  2 +-\n>   t/t1006-cat-file.sh                   |  1 +\n>   t/t1050-large.sh                      |  1 +\n>   t/t1450-fsck.sh                       |  1 +\n>   t/t1601-index-bogus.sh                |  2 +\n>   t/t2107-update-index-basic.sh         |  1 +\n>   t/t3310-notes-merge-manual-resolve.sh |  1 +\n>   t/t3311-notes-merge-fanout.sh         |  1 +\n>   t/t3404-rebase-interactive.sh         |  1 +\n>   t/t3435-rebase-gpg-sign.sh            |  1 +\n>   t/t3507-cherry-pick-conflict.sh       |  1 +\n>   t/t3510-cherry-pick-sequence.sh       |  1 +\n>   t/t3705-add-sparse-checkout.sh        |  1 +\n>   t/t4013-diff-various.sh               |  1 +\n>   t/t4014-format-patch.sh               |  1 +\n>   t/t4018-diff-funcname.sh              |  1 +\n>   t/t4030-diff-textconv.sh              |  2 +\n>   t/t4042-diff-textconv-caching.sh      |  2 +\n>   t/t4048-diff-combined-binary.sh       |  1 +\n>   t/t4064-diff-oidfind.sh               |  2 +\n>   t/t4065-diff-anchored.sh              |  1 +\n>   t/t4068-diff-symmetric-merge-base.sh  |  1 +\n>   t/t4069-remerge-diff.sh               |  1 +\n>   t/t4108-apply-threeway.sh             |  1 +\n>   t/t4209-log-pickaxe.sh                |  2 +\n>   t/t6421-merge-partial-clone.sh        |  1 +\n>   t/t6428-merge-conflicts-sparse.sh     |  1 +\n>   t/t7008-filter-branch-null-sha1.sh    |  1 +\n>   t/t7030-verify-tag.sh                 |  1 +\n>   t/t7817-grep-sparse-checkout.sh       |  1 +\n>   t/t9300-fast-import.sh                |  1 +\n>   t/t9304-fast-import-marks.sh          |  2 +\n>   t/t9351-fast-export-anonymize.sh      |  1 +\n>   unpack-trees.c                        |  2 +\n>   userdiff.c                            | 38 ++++++++---\n>   userdiff.h                            |  4 ++\n>   59 files changed, 288 insertions(+), 106 deletions(-)\n> \n> Range-diff against v1:\n>   1:  6e2fcd85c7 =  1:  2afa51f9ff remote: plug memory leak when aliasing URLs\n>   2:  9574995a24 =  2:  324140e4fd git: fix leaking system paths\n>   3:  f7e67d02d2 =  3:  43a38a2281 object-file: fix memory leak when reading corrupted headers\n>   4:  a9caaaed55 =  4:  9d3dc145e8 object-name: fix leaking symlink paths in object context\n>   5:  794af66103 =  5:  454139e7a4 bulk-checkin: fix leaking state TODO\n>   6:  2810cada0a =  6:  f8b7195796 read-cache: fix leaking hashfile when writing index fails\n>   7:  03f699cf39 =  7:  762fb5aa73 submodule-config: fix leaking name enrty when traversing submodules\n>   8:  a34c90a552 !  8:  8fbd72a100 config: fix leaking comment character config\n>      @@ Commit message\n>           without free'ing the previous value. In fact, it can't easily free the\n>           value in the first place because it may contain a string constant.\n>       \n>      -    Refactor the code so that we initialize the value with another array.\n>      -    This allows us to free the value in case the string is not pointing to\n>      -    that constant array anymore.\n>      +    Refactor the code such that we track allocated comment character strings\n>      +    via a separate non-constant variable `comment_line_str_allocated`. Adapt\n>      +    sites that set `comment_line_str` to set both and free the old value\n>      +    that was stored in `comment_line_str_allocated`.\n>       \n>           This memory leak is being hit in t3404. As there are still other memory\n>           leaks in that file we cannot yet mark it as passing with leak checking\n>      @@ Commit message\n>       \n>           Signed-off-by: Patrick Steinhardt <ps@pks.im>\n>       \n>      + ## builtin/commit.c ##\n>      +@@ builtin/commit.c: static void adjust_comment_line_char(const struct strbuf *sb)\n>      + \tconst char *p;\n>      +\n>      + \tif (!memchr(sb->buf, candidates[0], sb->len)) {\n>      +-\t\tcomment_line_str = xstrfmt(\"%c\", candidates[0]);\n>      ++\t\tfree(comment_line_str_allocated);\n>      ++\t\tcomment_line_str = comment_line_str_allocated =\n>      ++\t\t\txstrfmt(\"%c\", candidates[0]);\n>      + \t\treturn;\n>      + \t}\n>      +\n>      +@@ builtin/commit.c: static void adjust_comment_line_char(const struct strbuf *sb)\n>      + \tif (!*p)\n>      + \t\tdie(_(\"unable to select a comment character that is not used\\n\"\n>      + \t\t      \"in the current commit message\"));\n>      +-\tcomment_line_str = xstrfmt(\"%c\", *p);\n>      ++\tfree(comment_line_str_allocated);\n>      ++\tcomment_line_str = comment_line_str_allocated = xstrfmt(\"%c\", *p);\n>      + }\n>      +\n>      + static void prepare_amend_commit(struct commit *commit, struct strbuf *sb,\n>      +\n>        ## config.c ##\n>       @@ config.c: static int git_default_core_config(const char *var, const char *value,\n>        \t\telse if (value[0]) {\n>        \t\t\tif (strchr(value, '\\n'))\n>        \t\t\t\treturn error(_(\"%s cannot contain newline\"), var);\n>      -+\t\t\tif (comment_line_str != comment_line_str_default)\n>      -+\t\t\t\tfree((char *) comment_line_str);\n>      - \t\t\tcomment_line_str = xstrdup(value);\n>      +-\t\t\tcomment_line_str = xstrdup(value);\n>      ++\t\t\tfree(comment_line_str_allocated);\n>      ++\t\t\tcomment_line_str = comment_line_str_allocated =\n>      ++\t\t\t\txstrdup(value);\n>        \t\t\tauto_comment_line_char = 0;\n>        \t\t} else\n>      + \t\t\treturn error(_(\"%s must have at least one character\"), var);\n>       \n>        ## environment.c ##\n>       @@ environment.c: int protect_ntfs = PROTECT_NTFS_DEFAULT;\n>      -  * The character that begins a commented line in user-editable file\n>         * that is subject to stripspace.\n>         */\n>      --const char *comment_line_str = \"#\";\n>      -+const char comment_line_str_default[] = \"#\";\n>      -+const char *comment_line_str = comment_line_str_default;\n>      + const char *comment_line_str = \"#\";\n>      ++char *comment_line_str_allocated;\n>        int auto_comment_line_char;\n>        \n>        /* Parallel index stat data preload? */\n>       \n>        ## environment.h ##\n>       @@ environment.h: struct strvec;\n>      -  * The character that begins a commented line in user-editable file\n>         * that is subject to stripspace.\n>         */\n>      -+extern const char comment_line_str_default[];\n>        extern const char *comment_line_str;\n>      ++extern char *comment_line_str_allocated;\n>        extern int auto_comment_line_char;\n>        \n>      + /*\n>   9:  05290fc1f1 !  9:  e497b76e9c builtin/rebase: fix leaking `commit.gpgsign` value\n>      @@ Metadata\n>        ## Commit message ##\n>           builtin/rebase: fix leaking `commit.gpgsign` value\n>       \n>      -    In `get_replay_opts()`, we unconditionally override the `gpg_sign` field\n>      -    that already got populated by `sequencer_init_config()` in case the user\n>      -    has \"commit.gpgsign\" set in their config. It is kind of dubious whether\n>      -    this is the correct thing to do or a bug. What is clear though is that\n>      -    this creates a memory leak.\n>      +    In `get_replay_opts()`, we override the `gpg_sign` field that already\n>      +    got populated by `sequencer_init_config()` in case the user has\n>      +    \"commit.gpgsign\" set in their config. This creates a memory leak because\n>      +    we overwrite the previously assigned value, which may have already\n>      +    pointed to an allocated string.\n>       \n>      -    Let's mark this assignment with a TODO comment to figure out whether\n>      -    this needs to be fixed or not. Meanwhile though, let's plug the memory\n>      -    leak.\n>      +    Let's plug the memory leak by freeing the value before we overwrite it.\n>       \n>           Signed-off-by: Patrick Steinhardt <ps@pks.im>\n>       \n>      @@ builtin/rebase.c: static struct replay_opts get_replay_opts(const struct rebase_\n>        \treplay.committer_date_is_author_date =\n>        \t\t\t\t\topts->committer_date_is_author_date;\n>        \treplay.ignore_date = opts->ignore_date;\n>      -+\n>      -+\t/*\n>      -+\t * TODO: Is it really intentional that we unconditionally override\n>      -+\t * `replay.gpg_sign` even if it has already been initialized via the\n>      -+\t * configuration?\n>      -+\t */\n>       +\tfree(replay.gpg_sign);\n>        \treplay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);\n>      -+\n>        \treplay.reflog_action = xstrdup(opts->reflog_action);\n>        \tif (opts->strategy)\n>      - \t\treplay.strategy = xstrdup_or_null(opts->strategy);\n>       \n>        ## sequencer.c ##\n>       @@ sequencer.c: static int git_sequencer_config(const char *k, const char *v,\n> 10:  4f5d490074 = 10:  c886b666f7 builtin/notes: fix leaking `struct notes_tree` when merging notes\n> 11:  798b911f77 = 11:  d1c757157b builtin/fast-import: plug trivial memory leaks\n> 12:  660732d29d = 12:  fa2d5c5d6b builtin/fast-export: fix leaking diff options\n> 13:  64366155de = 13:  d9dd860d2a builtin/fast-export: plug leaking tag names\n> 14:  b12015b3c3 = 14:  8f6860485e merge-ort: unconditionally release attributes index\n> 15:  df4c21b49f ! 15:  ea6a350f31 sequencer: release todo list on error paths\n>      @@ sequencer.c: int sequencer_pick_revisions(struct repository *r,\n>        \t\t\t\t\t\t\t\t\t&oid,\n>        \t\t\t\t\t\t\t\t\tNULL);\n>       -\t\t\t\treturn error(_(\"%s: can't cherry-pick a %s\"),\n>      +-\t\t\t\t\tname, type_name(type));\n>       +\t\t\t\tres = error(_(\"%s: can't cherry-pick a %s\"),\n>      - \t\t\t\t\tname, type_name(type));\n>      ++\t\t\t\t\t    name, type_name(type));\n>       +\t\t\t\tgoto out;\n>        \t\t\t}\n>       -\t\t} else\n> 16:  1f8553fd43 = 16:  2755023742 unpack-trees: clear index when not propagating it\n> 17:  c6db8df324 = 17:  edf6f148cd diff: fix leak when parsing invalid ignore regex option\n> 18:  bf818a8a79 = 18:  343e3bd4df builtin/format-patch: fix various trivial memory leaks\n> 19:  ef780aa360 = 19:  be2c5b0bca userdiff: fix leaking memory for configured diff drivers\n> 20:  f3882986a3 = 20:  7888203833 builtin/log: fix leak when showing converted blob contents\n> 21:  a49bb2e0cc = 21:  245fc30afb diff: free state populated via options\n> 22:  fb52599404 = 22:  343ddcd17b builtin/diff: free symmetric diff members\n"},{"id":"500632","messageId":"ZroXRQX5ieXCmpD9@nand.local","threadId":"61910","inReplyTo":"2afa51f9ffedfa6dab51c9515f695ddfe0a9a4f9.1723121979.git.ps@pks.im","subject":"Re: [PATCH v2 01/22] remote: plug memory leak when aliasing URLs","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2024-08-12T14:08:05Z","receivedAt":"2024-08-12T14:08:09Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Thu, Aug 08, 2024 at 03:04:33PM +0200, Patrick Steinhardt wrote:\n> When we have a `url.*.insteadOf` configuration, then we end up aliasing\n> URLs when populating remotes. One place where this happens is in\n> `alias_all_urls()`, where we loop through all remotes and then alias\n> each of their URLs. The actual aliasing logic is then contained in\n> `alias_url()`, which returns an allocated string that contains the new\n> URL. This URL replaces the old URL that we have in the strvec that\n> contanis all remote URLs.\n>\n> We replace the remote URLs via `strvec_replace()`, which does not hand\n> over ownership of the new string to the vector. Still, we didn't free\n> the aliased URL and thus have a memory leak here. Fix it by freeing the\n> aliased string.\n\nThanks for the detailed explanation here.\n\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  remote.c                 | 2 ++\n>  t/t0210-trace2-normal.sh | 2 +-\n>  2 files changed, 3 insertions(+), 1 deletion(-)\n>\n> diff --git a/remote.c b/remote.c\n> index f43cf5e7a4..3b898edd23 100644\n> --- a/remote.c\n> +++ b/remote.c\n> @@ -499,6 +499,7 @@ static void alias_all_urls(struct remote_state *remote_state)\n>  \t\t\tif (alias)\n>  \t\t\t\tstrvec_replace(&remote_state->remotes[i]->pushurl,\n>  \t\t\t\t\t       j, alias);\n> +\t\t\tfree(alias);\n>  \t\t}\n>  \t\tadd_pushurl_aliases = remote_state->remotes[i]->pushurl.nr == 0;\n>  \t\tfor (j = 0; j < remote_state->remotes[i]->url.nr; j++) {\n> @@ -512,6 +513,7 @@ static void alias_all_urls(struct remote_state *remote_state)\n>  \t\t\tif (alias)\n>  \t\t\t\tstrvec_replace(&remote_state->remotes[i]->url,\n>  \t\t\t\t\t       j, alias);\n> +\t\t\tfree(alias);\n>  \t\t}\n>  \t}\n>  }\n\nThese both make sense to me, since alias_url() allocates the string it\nreturns via xstrfmt(), so having the caller free it makes sense.\n\nI was wondering if there was a nice way to neaten up these two call\npaths that both call alias_url(), check for NULL, call strbuf_replace(),\nand then free the result. But I think the result here would be pretty\nawkward from my attempts at it, so I think this patch looks good as-is.\n\nThanks,\nTaylor\n"},{"id":"500633","messageId":"ZroX+SSgTe7YnWHS@nand.local","threadId":"61910","inReplyTo":"324140e4fdee54fbf0054e76abb287b7212fd21e.1723121979.git.ps@pks.im","subject":"Re: [PATCH v2 02/22] git: fix leaking system paths","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2024-08-12T14:11:05Z","receivedAt":"2024-08-12T14:11:07Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Thu, Aug 08, 2024 at 03:04:39PM +0200, Patrick Steinhardt wrote:\n> Git has some flags to make it output system paths as they have been\n> compiled into Git. This is done by calling `system_path()`, which\n> returns an allocated string. This string isn't ever free'd though,\n> creating a memory leak.\n>\n> Plug those leaks. While they are surfaced by t0211, there are more\n> memory leaks looming exposed by that test suite and it thus does not yet\n> pass with the memory leak checker enabled.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  git.c | 12 +++++++++---\n>  1 file changed, 9 insertions(+), 3 deletions(-)\n>\n> diff --git a/git.c b/git.c\n> index e35af9b0e5..5eab88b472 100644\n> --- a/git.c\n> +++ b/git.c\n> @@ -173,15 +173,21 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n>  \t\t\t\texit(0);\n>  \t\t\t}\n>  \t\t} else if (!strcmp(cmd, \"--html-path\")) {\n> -\t\t\tputs(system_path(GIT_HTML_PATH));\n> +\t\t\tchar *path = system_path(GIT_HTML_PATH);\n> +\t\t\tputs(path);\n> +\t\t\tfree(path);\n>  \t\t\ttrace2_cmd_name(\"_query_\");\n>  \t\t\texit(0);\n>  \t\t} else if (!strcmp(cmd, \"--man-path\")) {\n> -\t\t\tputs(system_path(GIT_MAN_PATH));\n> +\t\t\tchar *path = system_path(GIT_MAN_PATH);\n> +\t\t\tputs(path);\n> +\t\t\tfree(path);\n>  \t\t\ttrace2_cmd_name(\"_query_\");\n>  \t\t\texit(0);\n>  \t\t} else if (!strcmp(cmd, \"--info-path\")) {\n> -\t\t\tputs(system_path(GIT_INFO_PATH));\n> +\t\t\tchar *path = system_path(GIT_INFO_PATH);\n> +\t\t\tputs(path);\n> +\t\t\tfree(path);\n>  \t\t\ttrace2_cmd_name(\"_query_\");\n>  \t\t\texit(0);\n>  \t\t} else if (!strcmp(cmd, \"-p\") || !strcmp(cmd, \"--paginate\")) {\n\n\nMakes sense, though I wonder if this would be slightly cleaner to write\nlike so (applies on top of this patch):\n\n--- 8< ---\ndiff --git a/git.c b/git.c\nindex 5eab88b472..9a618a2740 100644\n--- a/git.c\n+++ b/git.c\n@@ -143,6 +143,13 @@ void setup_auto_pager(const char *cmd, int def)\n \tcommit_pager_choice();\n }\n\n+static void print_system_path(const char *path)\n+{\n+\tchar *s_path = system_path(path);\n+\tputs(s_path);\n+\tfree(s_path);\n+}\n+\n static int handle_options(const char ***argv, int *argc, int *envchanged)\n {\n \tconst char **orig_argv = *argv;\n@@ -173,21 +180,15 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\t\texit(0);\n \t\t\t}\n \t\t} else if (!strcmp(cmd, \"--html-path\")) {\n-\t\t\tchar *path = system_path(GIT_HTML_PATH);\n-\t\t\tputs(path);\n-\t\t\tfree(path);\n+\t\t\tprint_system_path(GIT_HTML_PATH);\n \t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"--man-path\")) {\n-\t\t\tchar *path = system_path(GIT_MAN_PATH);\n-\t\t\tputs(path);\n-\t\t\tfree(path);\n+\t\t\tprint_system_path(GIT_MAN_PATH);\n \t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"--info-path\")) {\n-\t\t\tchar *path = system_path(GIT_INFO_PATH);\n-\t\t\tputs(path);\n-\t\t\tfree(path);\n+\t\t\tprint_system_path(GIT_INFO_PATH);\n \t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"-p\") || !strcmp(cmd, \"--paginate\")) {\n--- >8 ---\n\nThanks,\nTaylor\n"},{"id":"500636","messageId":"20240812143710.GA3800522@coredump.intra.peff.net","threadId":"61910","inReplyTo":"2afa51f9ffedfa6dab51c9515f695ddfe0a9a4f9.1723121979.git.ps@pks.im","subject":"Re: [PATCH v2 01/22] remote: plug memory leak when aliasing URLs","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2024-08-12T14:37:10Z","receivedAt":"2024-08-12T14:37:11Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Aug 08, 2024 at 03:04:33PM +0200, Patrick Steinhardt wrote:\n\n> When we have a `url.*.insteadOf` configuration, then we end up aliasing\n> URLs when populating remotes. One place where this happens is in\n> `alias_all_urls()`, where we loop through all remotes and then alias\n> each of their URLs. The actual aliasing logic is then contained in\n> `alias_url()`, which returns an allocated string that contains the new\n> URL. This URL replaces the old URL that we have in the strvec that\n> contanis all remote URLs.\n> \n> We replace the remote URLs via `strvec_replace()`, which does not hand\n> over ownership of the new string to the vector. Still, we didn't free\n> the aliased URL and thus have a memory leak here. Fix it by freeing the\n> aliased string.\n\nThanks, this one is my fault. When I replaced the open-coded replacement\nin 8e804415fd (remote: use strvecs to store remote url/pushurl,\n2024-06-14), for some reason I thought that strvec_replace() would take\nownership of the pointer. We could make a \"_nodup()\" variant, but it is\nprobably not worth the extra API complexity.\n\nCuriously, these are the only calls for strvec_replace(). You added it\nin 11ce77b5cc (strvec: add functions to replace and remove strings,\n2024-05-27) but I don't see them used in any iteration of that patch\nseries. So yet another option is to change the semantics of\nstrvec_replace(), but I think that is an even worse idea. ;)\n\n-Peff\n"},{"id":"500643","messageId":"xmqqbk1x7nbs.fsf@gitster.g","threadId":"61910","inReplyTo":"CAOLa=ZSCLZU=eeBqx9cpHkUJ+SOHXToVwMKjmCKLc+54MfxdRQ@mail.gmail.com","subject":"Re: [PATCH v2 00/22] Memory leak fixes (pt.4)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-08-12T15:49:59Z","receivedAt":"2024-08-12T15:50:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"karthik nayak <karthik.188@gmail.com> writes:\n\n> I went through the series and apart from some typos, everything looked\n> great. I don't expect a reroll for those typos though, since they're\n> minor.\n\nThanks for a review.  A final reroll that shows only the typofixes\nin interdiff/range-diff is not a huge burden, but having to deal\nwith many separate \"here is a typo\", \"here is another typo\" patches\nover a period is annoying and it is even worse that many readers\nhave to get their reading distracted when seeing these leftover\ntypoes, and get annoyed but yet not annoyed seriously enough to send\nthese typofix patches to leave them unfixed to cause other readers'\nreading hiccupped.  Let's always remember that there are 100x more\nreaders than those of us who write.\n\nThanks.\n\n"},{"id":"500644","messageId":"xmqq7ccl7nav.fsf@gitster.g","threadId":"61910","inReplyTo":"4dbf731a-873b-4cab-b8ff-745bfa059060@gmail.com","subject":"Re: [PATCH v2 00/22] Memory leak fixes (pt.4)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-08-12T15:50:32Z","receivedAt":"2024-08-12T15:50:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> On 08/08/2024 14:04, Patrick Steinhardt wrote:\n>> Hi,\n>> this is the second version of my fourth batch of patches that fix\n>> various memory leaks.\n>> Changes compared to v1:\n>>    - Adapt the memory leak fix for command characters to instead use\n>> a\n>>      `comment_line_str_allocated` variable.\n>>    - Clarify some commit messages.\n>>    - Drop the TODO comment about `rebase.gpgsign`. Turns out that\n>> this is\n>>      working as intended, as explained by Phillip.\n>\n> The changes to the rebase and sequencer patches look good to me\n>\n> Thanks\n>\n> Phillip\n\nThanks for a review.\n"},{"id":"500657","messageId":"xmqqbk1x1nyi.fsf@gitster.g","threadId":"61910","inReplyTo":"Zrm9ix5aN_g76Qxq@tanuki","subject":"Re: [PATCH v2 08/22] config: fix leaking comment character config","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-08-12T20:32:53Z","receivedAt":"2024-08-12T20:32:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Thu, Aug 08, 2024 at 10:12:26AM -0700, Junio C Hamano wrote:\n>> Patrick Steinhardt <ps@pks.im> writes:\n>> \n>> > diff --git a/config.c b/config.c\n>> > index 6421894614..cb78b652ee 100644\n>> > --- a/config.c\n>> > +++ b/config.c\n>> > @@ -1596,7 +1596,9 @@ static int git_default_core_config(const char *var, const char *value,\n>> >  \t\telse if (value[0]) {\n>> >  \t\t\tif (strchr(value, '\\n'))\n>> >  \t\t\t\treturn error(_(\"%s cannot contain newline\"), var);\n>> > -\t\t\tcomment_line_str = xstrdup(value);\n>> > +\t\t\tfree(comment_line_str_allocated);\n>> > +\t\t\tcomment_line_str = comment_line_str_allocated =\n>> > +\t\t\t\txstrdup(value);\n>> \n>> If you are to follow the _to_free pattern, you do not have to\n>> allocate here, no?  We borrow the value in the configset and point\n>> at it via comment_line_str, and clear comment_line_str_to_free\n>> because there is nothing to free now.  I.e.\n>> \n>> \t\t\tcomment_line_str = value;\n>> \t\t\tFREE_AND_NULL(comment_line_str_allocated);\n>\n> Only if it is guaranteed that the configuration will never be re-read,\n> which would end up discarding memory owned by the old string. Which\n> should be the case already, but to the best of my knowledge we do not\n> document the expected lifetime of config strings anywhere.\n\nThen let's mark it as #leftoverbits to document it.  Many other code\npaths depend on it.\n\n>> I still think the approach taken by the previous iteration was\n>> simpler and much less error prone, though.\n>\n> I personally prefer this iteration.\n\nIf so, then let's fully take advantage of the fact that you have a\nto-free variable dedicated for the comment_line_str variable.\n\nI still think it is a maintenance burden to keep them always in sync\n(which is another thing the developers have to remember---when they\nare updating _this_ particular variable, an extra rule applies and\nthey need to take care of this _allocated thing associated with it),\nand the first approach, by not forcing all the other assignment code\npaths to worry about it, simplifies the mental model for developers\ngreatly (i.e. we know we do not own the initial value, but\neverything else we allocate thus we free everything but the initial\nvalue), in exchange for a slightly wasteful allocation.\n\nThe approach in the second patch is worse in two counds compared to\nthe original.  It does wasteful allocation (which we do not have\nto---the fix was shown above).  It also burdens the developers to\nknow that they have to manually manage the _allocated half of the\ntwo-variable pair.\n"},{"id":"500687","messageId":"Zrr8wmYpY02lM19k@tanuki","threadId":"61910","inReplyTo":"xmqqbk1x7nbs.fsf@gitster.g","subject":"Re: [PATCH v2 00/22] Memory leak fixes (pt.4)","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T06:27:23Z","receivedAt":"2024-08-13T06:27:27Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Aug 12, 2024 at 08:49:59AM -0700, Junio C Hamano wrote:\n> karthik nayak <karthik.188@gmail.com> writes:\n> \n> > I went through the series and apart from some typos, everything looked\n> > great. I don't expect a reroll for those typos though, since they're\n> > minor.\n> \n> Thanks for a review.  A final reroll that shows only the typofixes\n> in interdiff/range-diff is not a huge burden, but having to deal\n> with many separate \"here is a typo\", \"here is another typo\" patches\n> over a period is annoying and it is even worse that many readers\n> have to get their reading distracted when seeing these leftover\n> typoes, and get annoyed but yet not annoyed seriously enough to send\n> these typofix patches to leave them unfixed to cause other readers'\n> reading hiccupped.  Let's always remember that there are 100x more\n> readers than those of us who write.\n\nClue taken, I'll finally bite the bullet and set up spell correction in\nmy editor. I'll also reroll this series later today.\n\nPatrick\n"},{"id":"500688","messageId":"Zrr9oxgg3w8JyUpw@tanuki","threadId":"61910","inReplyTo":"ZroX+SSgTe7YnWHS@nand.local","subject":"Re: [PATCH v2 02/22] git: fix leaking system paths","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T06:30:59Z","receivedAt":"2024-08-13T06:31:04Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Aug 12, 2024 at 10:11:05AM -0400, Taylor Blau wrote:\n> On Thu, Aug 08, 2024 at 03:04:39PM +0200, Patrick Steinhardt wrote:\n> > Git has some flags to make it output system paths as they have been\n> > compiled into Git. This is done by calling `system_path()`, which\n> > returns an allocated string. This string isn't ever free'd though,\n> > creating a memory leak.\n> >\n> > Plug those leaks. While they are surfaced by t0211, there are more\n> > memory leaks looming exposed by that test suite and it thus does not yet\n> > pass with the memory leak checker enabled.\n> >\n> > Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> > ---\n> >  git.c | 12 +++++++++---\n> >  1 file changed, 9 insertions(+), 3 deletions(-)\n> >\n> > diff --git a/git.c b/git.c\n> > index e35af9b0e5..5eab88b472 100644\n> > --- a/git.c\n> > +++ b/git.c\n> > @@ -173,15 +173,21 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n> >  \t\t\t\texit(0);\n> >  \t\t\t}\n> >  \t\t} else if (!strcmp(cmd, \"--html-path\")) {\n> > -\t\t\tputs(system_path(GIT_HTML_PATH));\n> > +\t\t\tchar *path = system_path(GIT_HTML_PATH);\n> > +\t\t\tputs(path);\n> > +\t\t\tfree(path);\n> >  \t\t\ttrace2_cmd_name(\"_query_\");\n> >  \t\t\texit(0);\n> >  \t\t} else if (!strcmp(cmd, \"--man-path\")) {\n> > -\t\t\tputs(system_path(GIT_MAN_PATH));\n> > +\t\t\tchar *path = system_path(GIT_MAN_PATH);\n> > +\t\t\tputs(path);\n> > +\t\t\tfree(path);\n> >  \t\t\ttrace2_cmd_name(\"_query_\");\n> >  \t\t\texit(0);\n> >  \t\t} else if (!strcmp(cmd, \"--info-path\")) {\n> > -\t\t\tputs(system_path(GIT_INFO_PATH));\n> > +\t\t\tchar *path = system_path(GIT_INFO_PATH);\n> > +\t\t\tputs(path);\n> > +\t\t\tfree(path);\n> >  \t\t\ttrace2_cmd_name(\"_query_\");\n> >  \t\t\texit(0);\n> >  \t\t} else if (!strcmp(cmd, \"-p\") || !strcmp(cmd, \"--paginate\")) {\n> \n> \n> Makes sense, though I wonder if this would be slightly cleaner to write\n> like so (applies on top of this patch):\n\nIt is cleaner indeed, thanks for the proposal!\n\nPatrick\n"},{"id":"500689","messageId":"Zrr-YpD-Yzowmqq3@tanuki","threadId":"61910","inReplyTo":"20240812143710.GA3800522@coredump.intra.peff.net","subject":"Re: [PATCH v2 01/22] remote: plug memory leak when aliasing URLs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T06:34:23Z","receivedAt":"2024-08-13T06:34:27Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Aug 12, 2024 at 10:37:10AM -0400, Jeff King wrote:\n> On Thu, Aug 08, 2024 at 03:04:33PM +0200, Patrick Steinhardt wrote:\n> \n> > When we have a `url.*.insteadOf` configuration, then we end up aliasing\n> > URLs when populating remotes. One place where this happens is in\n> > `alias_all_urls()`, where we loop through all remotes and then alias\n> > each of their URLs. The actual aliasing logic is then contained in\n> > `alias_url()`, which returns an allocated string that contains the new\n> > URL. This URL replaces the old URL that we have in the strvec that\n> > contanis all remote URLs.\n> > \n> > We replace the remote URLs via `strvec_replace()`, which does not hand\n> > over ownership of the new string to the vector. Still, we didn't free\n> > the aliased URL and thus have a memory leak here. Fix it by freeing the\n> > aliased string.\n> \n> Thanks, this one is my fault. When I replaced the open-coded replacement\n> in 8e804415fd (remote: use strvecs to store remote url/pushurl,\n> 2024-06-14), for some reason I thought that strvec_replace() would take\n> ownership of the pointer. We could make a \"_nodup()\" variant, but it is\n> probably not worth the extra API complexity.\n> \n> Curiously, these are the only calls for strvec_replace(). You added it\n> in 11ce77b5cc (strvec: add functions to replace and remove strings,\n> 2024-05-27) but I don't see them used in any iteration of that patch\n> series. So yet another option is to change the semantics of\n> strvec_replace(), but I think that is an even worse idea. ;)\n\nOh, interesting. I certainly wanted to use it back then, but guess that\nlater iterations removed those callsites again. In any case, it is being\nused now, so at least it isn't dead code :)\n\nPatrick\n"},{"id":"500690","messageId":"ZrsDFB1FzMvesTBH@tanuki","threadId":"61910","inReplyTo":"xmqqbk1x1nyi.fsf@gitster.g","subject":"Re: [PATCH v2 08/22] config: fix leaking comment character config","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T06:54:12Z","receivedAt":"2024-08-13T06:54:18Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Aug 12, 2024 at 01:32:53PM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > On Thu, Aug 08, 2024 at 10:12:26AM -0700, Junio C Hamano wrote:\n> >> Patrick Steinhardt <ps@pks.im> writes:\n> >> \n> >> > diff --git a/config.c b/config.c\n> >> > index 6421894614..cb78b652ee 100644\n> >> > --- a/config.c\n> >> > +++ b/config.c\n> >> > @@ -1596,7 +1596,9 @@ static int git_default_core_config(const char *var, const char *value,\n> >> >  \t\telse if (value[0]) {\n> >> >  \t\t\tif (strchr(value, '\\n'))\n> >> >  \t\t\t\treturn error(_(\"%s cannot contain newline\"), var);\n> >> > -\t\t\tcomment_line_str = xstrdup(value);\n> >> > +\t\t\tfree(comment_line_str_allocated);\n> >> > +\t\t\tcomment_line_str = comment_line_str_allocated =\n> >> > +\t\t\t\txstrdup(value);\n> >> \n> >> If you are to follow the _to_free pattern, you do not have to\n> >> allocate here, no?  We borrow the value in the configset and point\n> >> at it via comment_line_str, and clear comment_line_str_to_free\n> >> because there is nothing to free now.  I.e.\n> >> \n> >> \t\t\tcomment_line_str = value;\n> >> \t\t\tFREE_AND_NULL(comment_line_str_allocated);\n> >\n> > Only if it is guaranteed that the configuration will never be re-read,\n> > which would end up discarding memory owned by the old string. Which\n> > should be the case already, but to the best of my knowledge we do not\n> > document the expected lifetime of config strings anywhere.\n> \n> Then let's mark it as #leftoverbits to document it.  Many other code\n> paths depend on it.\n\nOkay.\n\n> >> I still think the approach taken by the previous iteration was\n> >> simpler and much less error prone, though.\n> >\n> > I personally prefer this iteration.\n> \n> If so, then let's fully take advantage of the fact that you have a\n> to-free variable dedicated for the comment_line_str variable.\n\nCan do.\n\n> I still think it is a maintenance burden to keep them always in sync\n> (which is another thing the developers have to remember---when they\n> are updating _this_ particular variable, an extra rule applies and\n> they need to take care of this _allocated thing associated with it),\n> and the first approach, by not forcing all the other assignment code\n> paths to worry about it, simplifies the mental model for developers\n> greatly (i.e. we know we do not own the initial value, but\n> everything else we allocate thus we free everything but the initial\n> value), in exchange for a slightly wasteful allocation.\n> \n> The approach in the second patch is worse in two counds compared to\n> the original.  It does wasteful allocation (which we do not have\n> to---the fix was shown above).  It also burdens the developers to\n> know that they have to manually manage the _allocated half of the\n> two-variable pair.\n\nWell, the developer has to manage the allocation in both versions of\nthis patch series. In the first iteration it just wasn't as bad because\nI didn't bother to adjust all sites where we set up the comment string.\nSo I was just about to convert it back to the first iteration, but then\nagain saw that we now have to carry this ugly construct everywhere:\n\n    if (comment_line_str != comment_line_str_default)\n        free((char *) comment_line_str);\n    comme_line_str = xstrdup(value);\n\nvs\n\n    free(comment_line_str_to_free);\n    comment_line_str = comment_line_str_to_free = xstrdup(value);\n\nI certainly think that the maintenance headache of the first version is\nhigher than having to maintain both variables. The worst that can happen\nfor the second version is that we leak memory because we don't update\nthe `_to_free` string. The worst that can happen in the above code\nsnippet is that one isn't aware of the condition of when the string\nneeds to be freed and then unconditionally frees it, leading to a\nsegfault.\n\nBut as said, ultimately I think neither of these versions is great.\n\nPatrick\n"},{"id":"500736","messageId":"cover.1723540931.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"[PATCH v3 00/22] Memory leak fixes (pt.4)","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T09:31:02Z","receivedAt":"2024-08-13T09:31:07Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis is the third version of my fourth batch of patches that fix\nvarious memory leaks.\n\nChanges compared to v2:\n\n  - Various typo fixes in commit messages.\n\n  - Introduce `print_system_path()` as proposed by Taylor, which removes\n    some of the repetition when printing system ptahs.\n\n  - Micro-optimize one allocation for comment char strings away. Also,\n    rename the variable to `comment_line_str_to_free` to better match\n    how we call such variables in other places.\n\nThanks!\n\nPatrick\n\nPatrick Steinhardt (22):\n  remote: plug memory leak when aliasing URLs\n  git: fix leaking system paths\n  object-file: fix memory leak when reading corrupted headers\n  object-name: fix leaking symlink paths in object context\n  bulk-checkin: fix leaking state TODO\n  read-cache: fix leaking hashfile when writing index fails\n  submodule-config: fix leaking name entry when traversing submodules\n  config: fix leaking comment character config\n  builtin/rebase: fix leaking `commit.gpgsign` value\n  builtin/notes: fix leaking `struct notes_tree` when merging notes\n  builtin/fast-import: plug trivial memory leaks\n  builtin/fast-export: fix leaking diff options\n  builtin/fast-export: plug leaking tag names\n  merge-ort: unconditionally release attributes index\n  sequencer: release todo list on error paths\n  unpack-trees: clear index when not propagating it\n  diff: fix leak when parsing invalid ignore regex option\n  builtin/format-patch: fix various trivial memory leaks\n  userdiff: fix leaking memory for configured diff drivers\n  builtin/log: fix leak when showing converted blob contents\n  diff: free state populated via options\n  builtin/diff: free symmetric diff members\n\n builtin/commit.c                      |  7 +-\n builtin/diff.c                        | 10 ++-\n builtin/fast-export.c                 | 19 ++++--\n builtin/fast-import.c                 |  8 ++-\n builtin/log.c                         | 13 +++-\n builtin/notes.c                       |  9 ++-\n builtin/rebase.c                      |  1 +\n bulk-checkin.c                        |  2 +\n config.c                              |  3 +-\n csum-file.c                           |  2 +-\n csum-file.h                           | 10 +++\n diff.c                                | 16 ++++-\n environment.c                         |  1 +\n environment.h                         |  1 +\n git.c                                 | 13 +++-\n merge-ort.c                           |  3 +-\n object-file.c                         |  1 +\n object-name.c                         |  1 +\n range-diff.c                          |  6 +-\n read-cache.c                          | 97 ++++++++++++++++-----------\n remote.c                              |  2 +\n sequencer.c                           | 67 ++++++++++++------\n submodule-config.c                    | 18 +++--\n t/t0210-trace2-normal.sh              |  2 +-\n t/t1006-cat-file.sh                   |  1 +\n t/t1050-large.sh                      |  1 +\n t/t1450-fsck.sh                       |  1 +\n t/t1601-index-bogus.sh                |  2 +\n t/t2107-update-index-basic.sh         |  1 +\n t/t3310-notes-merge-manual-resolve.sh |  1 +\n t/t3311-notes-merge-fanout.sh         |  1 +\n t/t3404-rebase-interactive.sh         |  1 +\n t/t3435-rebase-gpg-sign.sh            |  1 +\n t/t3507-cherry-pick-conflict.sh       |  1 +\n t/t3510-cherry-pick-sequence.sh       |  1 +\n t/t3705-add-sparse-checkout.sh        |  1 +\n t/t4013-diff-various.sh               |  1 +\n t/t4014-format-patch.sh               |  1 +\n t/t4018-diff-funcname.sh              |  1 +\n t/t4030-diff-textconv.sh              |  2 +\n t/t4042-diff-textconv-caching.sh      |  2 +\n t/t4048-diff-combined-binary.sh       |  1 +\n t/t4064-diff-oidfind.sh               |  2 +\n t/t4065-diff-anchored.sh              |  1 +\n t/t4068-diff-symmetric-merge-base.sh  |  1 +\n t/t4069-remerge-diff.sh               |  1 +\n t/t4108-apply-threeway.sh             |  1 +\n t/t4209-log-pickaxe.sh                |  2 +\n t/t6421-merge-partial-clone.sh        |  1 +\n t/t6428-merge-conflicts-sparse.sh     |  1 +\n t/t7008-filter-branch-null-sha1.sh    |  1 +\n t/t7030-verify-tag.sh                 |  1 +\n t/t7817-grep-sparse-checkout.sh       |  1 +\n t/t9300-fast-import.sh                |  1 +\n t/t9304-fast-import-marks.sh          |  2 +\n t/t9351-fast-export-anonymize.sh      |  1 +\n unpack-trees.c                        |  2 +\n userdiff.c                            | 38 ++++++++---\n userdiff.h                            |  4 ++\n 59 files changed, 288 insertions(+), 106 deletions(-)\n\nRange-diff against v2:\n 1:  2afa51f9ff !  1:  02f6da020f remote: plug memory leak when aliasing URLs\n    @@ Commit message\n         each of their URLs. The actual aliasing logic is then contained in\n         `alias_url()`, which returns an allocated string that contains the new\n         URL. This URL replaces the old URL that we have in the strvec that\n    -    contanis all remote URLs.\n    +    contains all remote URLs.\n     \n         We replace the remote URLs via `strvec_replace()`, which does not hand\n         over ownership of the new string to the vector. Still, we didn't free\n 2:  324140e4fd !  2:  f36d895948 git: fix leaking system paths\n    @@ Commit message\n         memory leaks looming exposed by that test suite and it thus does not yet\n         pass with the memory leak checker enabled.\n     \n    +    Helped-by: Taylor Blau <me@ttaylorr.com>\n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n      ## git.c ##\n    +@@ git.c: void setup_auto_pager(const char *cmd, int def)\n    + \tcommit_pager_choice();\n    + }\n    + \n    ++static void print_system_path(const char *path)\n    ++{\n    ++\tchar *s_path = system_path(path);\n    ++\tputs(s_path);\n    ++\tfree(s_path);\n    ++}\n    ++\n    + static int handle_options(const char ***argv, int *argc, int *envchanged)\n    + {\n    + \tconst char **orig_argv = *argv;\n     @@ git.c: static int handle_options(const char ***argv, int *argc, int *envchanged)\n      \t\t\t\texit(0);\n      \t\t\t}\n      \t\t} else if (!strcmp(cmd, \"--html-path\")) {\n     -\t\t\tputs(system_path(GIT_HTML_PATH));\n    -+\t\t\tchar *path = system_path(GIT_HTML_PATH);\n    -+\t\t\tputs(path);\n    -+\t\t\tfree(path);\n    ++\t\t\tprint_system_path(GIT_HTML_PATH);\n      \t\t\ttrace2_cmd_name(\"_query_\");\n      \t\t\texit(0);\n      \t\t} else if (!strcmp(cmd, \"--man-path\")) {\n     -\t\t\tputs(system_path(GIT_MAN_PATH));\n    -+\t\t\tchar *path = system_path(GIT_MAN_PATH);\n    -+\t\t\tputs(path);\n    -+\t\t\tfree(path);\n    ++\t\t\tprint_system_path(GIT_MAN_PATH);\n      \t\t\ttrace2_cmd_name(\"_query_\");\n      \t\t\texit(0);\n      \t\t} else if (!strcmp(cmd, \"--info-path\")) {\n     -\t\t\tputs(system_path(GIT_INFO_PATH));\n    -+\t\t\tchar *path = system_path(GIT_INFO_PATH);\n    -+\t\t\tputs(path);\n    -+\t\t\tfree(path);\n    ++\t\t\tprint_system_path(GIT_INFO_PATH);\n      \t\t\ttrace2_cmd_name(\"_query_\");\n      \t\t\texit(0);\n      \t\t} else if (!strcmp(cmd, \"-p\") || !strcmp(cmd, \"--paginate\")) {\n 3:  43a38a2281 !  3:  0415ac986d object-file: fix memory leak when reading corrupted headers\n    @@ Metadata\n      ## Commit message ##\n         object-file: fix memory leak when reading corrupted headers\n     \n    -    When reading corrupt object headers in `read_loose_object()`, then we\n    -    bail out immediately. This causes a memory leak though because we would\n    -    have already initialized the zstream in `unpack_loose_header()`, and it\n    -    is the callers responsibility to finish the zstream even on error. While\n    +    When reading corrupt object headers in `read_loose_object()`, we bail\n    +    out immediately. This causes a memory leak though because we would have\n    +    already initialized the zstream in `unpack_loose_header()`, and it is\n    +    the callers responsibility to finish the zstream even on error. While\n         this feels weird, other callsites do it correctly already.\n     \n         Fix this leak by ending the zstream even on errors. We may want to\n 4:  9d3dc145e8 =  4:  e5130e50a9 object-name: fix leaking symlink paths in object context\n 5:  454139e7a4 =  5:  276c828ad1 bulk-checkin: fix leaking state TODO\n 6:  f8b7195796 =  6:  ed0608e705 read-cache: fix leaking hashfile when writing index fails\n 7:  762fb5aa73 !  7:  b7a7f88c7d submodule-config: fix leaking name enrty when traversing submodules\n    @@ Metadata\n     Author: Patrick Steinhardt <ps@pks.im>\n     \n      ## Commit message ##\n    -    submodule-config: fix leaking name enrty when traversing submodules\n    +    submodule-config: fix leaking name entry when traversing submodules\n     \n         We traverse through submodules in the tree via `tree_entry()`, passing\n         to it a `struct name_entry` that it is supposed to populate with the\n 8:  8fbd72a100 !  8:  9054a459a1 config: fix leaking comment character config\n    @@ Commit message\n         value in the first place because it may contain a string constant.\n     \n         Refactor the code such that we track allocated comment character strings\n    -    via a separate non-constant variable `comment_line_str_allocated`. Adapt\n    +    via a separate non-constant variable `comment_line_str_to_free`. Adapt\n         sites that set `comment_line_str` to set both and free the old value\n    -    that was stored in `comment_line_str_allocated`.\n    +    that was stored in `comment_line_str_to_free`.\n     \n         This memory leak is being hit in t3404. As there are still other memory\n         leaks in that file we cannot yet mark it as passing with leak checking\n    @@ builtin/commit.c: static void adjust_comment_line_char(const struct strbuf *sb)\n      \n      \tif (!memchr(sb->buf, candidates[0], sb->len)) {\n     -\t\tcomment_line_str = xstrfmt(\"%c\", candidates[0]);\n    -+\t\tfree(comment_line_str_allocated);\n    -+\t\tcomment_line_str = comment_line_str_allocated =\n    ++\t\tfree(comment_line_str_to_free);\n    ++\t\tcomment_line_str = comment_line_str_to_free =\n     +\t\t\txstrfmt(\"%c\", candidates[0]);\n      \t\treturn;\n      \t}\n    @@ builtin/commit.c: static void adjust_comment_line_char(const struct strbuf *sb)\n      \t\tdie(_(\"unable to select a comment character that is not used\\n\"\n      \t\t      \"in the current commit message\"));\n     -\tcomment_line_str = xstrfmt(\"%c\", *p);\n    -+\tfree(comment_line_str_allocated);\n    -+\tcomment_line_str = comment_line_str_allocated = xstrfmt(\"%c\", *p);\n    ++\tfree(comment_line_str_to_free);\n    ++\tcomment_line_str = comment_line_str_to_free = xstrfmt(\"%c\", *p);\n      }\n      \n      static void prepare_amend_commit(struct commit *commit, struct strbuf *sb,\n    @@ config.c: static int git_default_core_config(const char *var, const char *value,\n      \t\t\tif (strchr(value, '\\n'))\n      \t\t\t\treturn error(_(\"%s cannot contain newline\"), var);\n     -\t\t\tcomment_line_str = xstrdup(value);\n    -+\t\t\tfree(comment_line_str_allocated);\n    -+\t\t\tcomment_line_str = comment_line_str_allocated =\n    -+\t\t\t\txstrdup(value);\n    ++\t\t\tcomment_line_str = value;\n    ++\t\t\tFREE_AND_NULL(comment_line_str_to_free);\n      \t\t\tauto_comment_line_char = 0;\n      \t\t} else\n      \t\t\treturn error(_(\"%s must have at least one character\"), var);\n    @@ environment.c: int protect_ntfs = PROTECT_NTFS_DEFAULT;\n       * that is subject to stripspace.\n       */\n      const char *comment_line_str = \"#\";\n    -+char *comment_line_str_allocated;\n    ++char *comment_line_str_to_free;\n      int auto_comment_line_char;\n      \n      /* Parallel index stat data preload? */\n    @@ environment.h: struct strvec;\n       * that is subject to stripspace.\n       */\n      extern const char *comment_line_str;\n    -+extern char *comment_line_str_allocated;\n    ++extern char *comment_line_str_to_free;\n      extern int auto_comment_line_char;\n      \n      /*\n 9:  e497b76e9c =  9:  1d3957a5eb builtin/rebase: fix leaking `commit.gpgsign` value\n10:  c886b666f7 = 10:  0af1bab5a1 builtin/notes: fix leaking `struct notes_tree` when merging notes\n11:  d1c757157b = 11:  30d4e9ed43 builtin/fast-import: plug trivial memory leaks\n12:  fa2d5c5d6b ! 12:  9591fb7b5e builtin/fast-export: fix leaking diff options\n    @@ Metadata\n      ## Commit message ##\n         builtin/fast-export: fix leaking diff options\n     \n    -    Before caling `handle_commit()` in a loop, we set `diffopt.no_free` such\n    -    that its contents aren't getting freed inside of `handle_commit()`. We\n    -    never unset that flag though, which means that it'll ultimately leak\n    +    Before calling `handle_commit()` in a loop, we set `diffopt.no_free`\n    +    such that its contents aren't getting freed inside of `handle_commit()`.\n    +    We never unset that flag though, which means that it'll ultimately leak\n         when calling `release_revisions()`.\n     \n         Fix this by unsetting the flag after the loop.\n13:  d9dd860d2a = 13:  254bbb7f6f builtin/fast-export: plug leaking tag names\n14:  8f6860485e = 14:  334c4ed71a merge-ort: unconditionally release attributes index\n15:  ea6a350f31 = 15:  9f08a859fb sequencer: release todo list on error paths\n16:  2755023742 = 16:  5d4934b1a9 unpack-trees: clear index when not propagating it\n17:  edf6f148cd = 17:  e1b6a24fbe diff: fix leak when parsing invalid ignore regex option\n18:  343e3bd4df = 18:  c048b54a2c builtin/format-patch: fix various trivial memory leaks\n19:  be2c5b0bca = 19:  39b2921e3e userdiff: fix leaking memory for configured diff drivers\n20:  7888203833 = 20:  50dea1c98a builtin/log: fix leak when showing converted blob contents\n21:  245fc30afb = 21:  d5cb4ad580 diff: free state populated via options\n22:  343ddcd17b ! 22:  31e38ba4e1 builtin/diff: free symmetric diff members\n    @@ Commit message\n     \n         We populate a `struct symdiff` in case the user has requested a\n         symmetric diff. Part of this is to populate a `skip` bitmap that\n    -    indicates whihc commits shall be ignored in the diff. But while this\n    +    indicates which commits shall be ignored in the diff. But while this\n         bitmap is dynamically allocated, we never free it.\n     \n         Fix this by introducing and calling a new `symdiff_release()` function\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500737","messageId":"02f6da020f083b3cedcdff192595438ecf82bf56.1723540931.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723540931.git.ps@pks.im","subject":"[PATCH v3 01/22] remote: plug memory leak when aliasing URLs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T09:31:05Z","receivedAt":"2024-08-13T09:31:08Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When we have a `url.*.insteadOf` configuration, then we end up aliasing\nURLs when populating remotes. One place where this happens is in\n`alias_all_urls()`, where we loop through all remotes and then alias\neach of their URLs. The actual aliasing logic is then contained in\n`alias_url()`, which returns an allocated string that contains the new\nURL. This URL replaces the old URL that we have in the strvec that\ncontains all remote URLs.\n\nWe replace the remote URLs via `strvec_replace()`, which does not hand\nover ownership of the new string to the vector. Still, we didn't free\nthe aliased URL and thus have a memory leak here. Fix it by freeing the\naliased string.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n remote.c                 | 2 ++\n t/t0210-trace2-normal.sh | 2 +-\n 2 files changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/remote.c b/remote.c\nindex f43cf5e7a4..3b898edd23 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -499,6 +499,7 @@ static void alias_all_urls(struct remote_state *remote_state)\n \t\t\tif (alias)\n \t\t\t\tstrvec_replace(&remote_state->remotes[i]->pushurl,\n \t\t\t\t\t       j, alias);\n+\t\t\tfree(alias);\n \t\t}\n \t\tadd_pushurl_aliases = remote_state->remotes[i]->pushurl.nr == 0;\n \t\tfor (j = 0; j < remote_state->remotes[i]->url.nr; j++) {\n@@ -512,6 +513,7 @@ static void alias_all_urls(struct remote_state *remote_state)\n \t\t\tif (alias)\n \t\t\t\tstrvec_replace(&remote_state->remotes[i]->url,\n \t\t\t\t\t       j, alias);\n+\t\t\tfree(alias);\n \t\t}\n \t}\n }\ndiff --git a/t/t0210-trace2-normal.sh b/t/t0210-trace2-normal.sh\nindex c312657a12..b9adc94aab 100755\n--- a/t/t0210-trace2-normal.sh\n+++ b/t/t0210-trace2-normal.sh\n@@ -2,7 +2,7 @@\n \n test_description='test trace2 facility (normal target)'\n \n-TEST_PASSES_SANITIZE_LEAK=false\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # Turn off any inherited trace2 settings for this test.\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500738","messageId":"f36d895948a2b26148062a3de2997fb6d23301d3.1723540931.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723540931.git.ps@pks.im","subject":"[PATCH v3 02/22] git: fix leaking system paths","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T09:31:08Z","receivedAt":"2024-08-13T09:31:12Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Git has some flags to make it output system paths as they have been\ncompiled into Git. This is done by calling `system_path()`, which\nreturns an allocated string. This string isn't ever free'd though,\ncreating a memory leak.\n\nPlug those leaks. While they are surfaced by t0211, there are more\nmemory leaks looming exposed by that test suite and it thus does not yet\npass with the memory leak checker enabled.\n\nHelped-by: Taylor Blau <me@ttaylorr.com>\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n git.c | 13 ++++++++++---\n 1 file changed, 10 insertions(+), 3 deletions(-)\n\ndiff --git a/git.c b/git.c\nindex e35af9b0e5..9a618a2740 100644\n--- a/git.c\n+++ b/git.c\n@@ -143,6 +143,13 @@ void setup_auto_pager(const char *cmd, int def)\n \tcommit_pager_choice();\n }\n \n+static void print_system_path(const char *path)\n+{\n+\tchar *s_path = system_path(path);\n+\tputs(s_path);\n+\tfree(s_path);\n+}\n+\n static int handle_options(const char ***argv, int *argc, int *envchanged)\n {\n \tconst char **orig_argv = *argv;\n@@ -173,15 +180,15 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\t\texit(0);\n \t\t\t}\n \t\t} else if (!strcmp(cmd, \"--html-path\")) {\n-\t\t\tputs(system_path(GIT_HTML_PATH));\n+\t\t\tprint_system_path(GIT_HTML_PATH);\n \t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"--man-path\")) {\n-\t\t\tputs(system_path(GIT_MAN_PATH));\n+\t\t\tprint_system_path(GIT_MAN_PATH);\n \t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"--info-path\")) {\n-\t\t\tputs(system_path(GIT_INFO_PATH));\n+\t\t\tprint_system_path(GIT_INFO_PATH);\n \t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"-p\") || !strcmp(cmd, \"--paginate\")) {\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500739","messageId":"0415ac986dc8c04917502a1bb0b03afcca47ec67.1723540931.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723540931.git.ps@pks.im","subject":"[PATCH v3 03/22] object-file: fix memory leak when reading corrupted headers","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T09:31:11Z","receivedAt":"2024-08-13T09:31:14Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When reading corrupt object headers in `read_loose_object()`, we bail\nout immediately. This causes a memory leak though because we would have\nalready initialized the zstream in `unpack_loose_header()`, and it is\nthe callers responsibility to finish the zstream even on error. While\nthis feels weird, other callsites do it correctly already.\n\nFix this leak by ending the zstream even on errors. We may want to\nrevisit this interface in the future such that the callee handles this\nfor us already when there was an error.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c   | 1 +\n t/t1450-fsck.sh | 1 +\n 2 files changed, 2 insertions(+)\n\ndiff --git a/object-file.c b/object-file.c\nindex 065103be3e..7c65c435cd 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -2954,6 +2954,7 @@ int read_loose_object(const char *path,\n \tif (unpack_loose_header(&stream, map, mapsize, hdr, sizeof(hdr),\n \t\t\t\tNULL) != ULHR_OK) {\n \t\terror(_(\"unable to unpack header of %s\"), path);\n+\t\tgit_inflate_end(&stream);\n \t\tgoto out;\n \t}\n \ndiff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh\nindex 8a456b1142..280cbf3e03 100755\n--- a/t/t1450-fsck.sh\n+++ b/t/t1450-fsck.sh\n@@ -6,6 +6,7 @@ test_description='git fsck random collection of tests\n * (main) A\n '\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success setup '\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500740","messageId":"e5130e50a9f296720960b2a8d7be14fc01a0fd30.1723540931.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723540931.git.ps@pks.im","subject":"[PATCH v3 04/22] object-name: fix leaking symlink paths in object context","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T09:31:14Z","receivedAt":"2024-08-13T09:31:17Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The object context may be populated with symlink contents when reading a\nsymlink, but the associated strbuf doesn't ever get released when\nreleasing the object context, causing a memory leak. Plug it.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-name.c       | 1 +\n t/t1006-cat-file.sh | 1 +\n 2 files changed, 2 insertions(+)\n\ndiff --git a/object-name.c b/object-name.c\nindex 240a93e7ce..e39fa50e47 100644\n--- a/object-name.c\n+++ b/object-name.c\n@@ -1765,6 +1765,7 @@ int strbuf_check_branch_ref(struct strbuf *sb, const char *name)\n void object_context_release(struct object_context *ctx)\n {\n \tfree(ctx->path);\n+\tstrbuf_release(&ctx->symlink_path);\n }\n \n /*\ndiff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\nindex ff9bf213aa..d36cd7c086 100755\n--- a/t/t1006-cat-file.sh\n+++ b/t/t1006-cat-file.sh\n@@ -2,6 +2,7 @@\n \n test_description='git cat-file'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_cmdmode_usage () {\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500741","messageId":"276c828ad18c422c7cca0503e336c8aee8aeb149.1723540931.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723540931.git.ps@pks.im","subject":"[PATCH v3 05/22] bulk-checkin: fix leaking state TODO","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T09:31:17Z","receivedAt":"2024-08-13T09:31:23Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When flushing a bulk-checking to disk we also reset the `struct\nbulk_checkin_packfile` state. But while we free some of its members,\nothers aren't being free'd, leading to memory leaks:\n\n  - The temporary packfile name is not getting freed.\n\n  - The `struct hashfile` only gets freed in case we end up calling\n    `finalize_hashfile()`. There are code paths though where that is not\n    the case, namely when nothing has been written. For this, we need to\n    make `free_hashfile()` public.\n\nFix those leaks.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n bulk-checkin.c   |  2 ++\n csum-file.c      |  2 +-\n csum-file.h      | 10 ++++++++++\n t/t1050-large.sh |  1 +\n 4 files changed, 14 insertions(+), 1 deletion(-)\n\ndiff --git a/bulk-checkin.c b/bulk-checkin.c\nindex da8673199b..9089c214fa 100644\n--- a/bulk-checkin.c\n+++ b/bulk-checkin.c\n@@ -61,6 +61,7 @@ static void flush_bulk_checkin_packfile(struct bulk_checkin_packfile *state)\n \n \tif (state->nr_written == 0) {\n \t\tclose(state->f->fd);\n+\t\tfree_hashfile(state->f);\n \t\tunlink(state->pack_tmp_name);\n \t\tgoto clear_exit;\n \t} else if (state->nr_written == 1) {\n@@ -83,6 +84,7 @@ static void flush_bulk_checkin_packfile(struct bulk_checkin_packfile *state)\n \t\tfree(state->written[i]);\n \n clear_exit:\n+\tfree(state->pack_tmp_name);\n \tfree(state->written);\n \tmemset(state, 0, sizeof(*state));\n \ndiff --git a/csum-file.c b/csum-file.c\nindex 8abbf01325..7e0ece1305 100644\n--- a/csum-file.c\n+++ b/csum-file.c\n@@ -56,7 +56,7 @@ void hashflush(struct hashfile *f)\n \t}\n }\n \n-static void free_hashfile(struct hashfile *f)\n+void free_hashfile(struct hashfile *f)\n {\n \tfree(f->buffer);\n \tfree(f->check_buffer);\ndiff --git a/csum-file.h b/csum-file.h\nindex 566e05cbd2..ca553eba17 100644\n--- a/csum-file.h\n+++ b/csum-file.h\n@@ -46,6 +46,16 @@ int hashfile_truncate(struct hashfile *, struct hashfile_checkpoint *);\n struct hashfile *hashfd(int fd, const char *name);\n struct hashfile *hashfd_check(const char *name);\n struct hashfile *hashfd_throughput(int fd, const char *name, struct progress *tp);\n+\n+/*\n+ * Free the hashfile without flushing its contents to disk. This only\n+ * needs to be called when not calling `finalize_hashfile()`.\n+ */\n+void free_hashfile(struct hashfile *f);\n+\n+/*\n+ * Finalize the hashfile by flushing data to disk and free'ing it.\n+ */\n int finalize_hashfile(struct hashfile *, unsigned char *, enum fsync_component, unsigned int);\n void hashwrite(struct hashfile *, const void *, unsigned int);\n void hashflush(struct hashfile *f);\ndiff --git a/t/t1050-large.sh b/t/t1050-large.sh\nindex c71932b024..ed638f6644 100755\n--- a/t/t1050-large.sh\n+++ b/t/t1050-large.sh\n@@ -3,6 +3,7 @@\n \n test_description='adding and checking out large blobs'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'core.bigFileThreshold must be non-negative' '\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500742","messageId":"ed0608e7059c925e50daf5dc454ced781a09a948.1723540931.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723540931.git.ps@pks.im","subject":"[PATCH v3 06/22] read-cache: fix leaking hashfile when writing index fails","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T09:31:22Z","receivedAt":"2024-08-13T09:31:25Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `do_write_index()`, we use a `struct hashfile` to write the index\nwith a trailer hash. In case the write fails though, we never clean up\nthe allocated `hashfile` state and thus leak memory.\n\nRefactor the code to have a common exit path where we can free this and\nother allocated memory. While at it, refactor our use of `strbuf`s such\nthat we reuse the same buffer to avoid some unneeded allocations.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n read-cache.c                       | 97 ++++++++++++++++++------------\n t/t1601-index-bogus.sh             |  2 +\n t/t2107-update-index-basic.sh      |  1 +\n t/t7008-filter-branch-null-sha1.sh |  1 +\n 4 files changed, 62 insertions(+), 39 deletions(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex 48bf24f87c..36821fe5b5 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -2840,8 +2840,9 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \tint csum_fsync_flag;\n \tint ieot_entries = 1;\n \tstruct index_entry_offset_table *ieot = NULL;\n-\tint nr, nr_threads;\n \tstruct repository *r = istate->repo;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tint nr, nr_threads, ret;\n \n \tf = hashfd(tempfile->fd, tempfile->filename.buf);\n \n@@ -2962,8 +2963,8 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \tstrbuf_release(&previous_name_buf);\n \n \tif (err) {\n-\t\tfree(ieot);\n-\t\treturn err;\n+\t\tret = err;\n+\t\tgoto out;\n \t}\n \n \toffset = hashfile_total(f);\n@@ -2985,20 +2986,20 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \t * index.\n \t */\n \tif (ieot) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\twrite_ieot_extension(&sb, ieot);\n \t\terr = write_index_ext_header(f, eoie_c, CACHE_EXT_INDEXENTRYOFFSETTABLE, sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tfree(ieot);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \n \tif (write_extensions & WRITE_SPLIT_INDEX_EXTENSION &&\n \t    istate->split_index) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\tif (istate->sparse_index)\n \t\t\tdie(_(\"cannot write split index for a sparse index\"));\n@@ -3007,59 +3008,66 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \t\t\twrite_index_ext_header(f, eoie_c, CACHE_EXT_LINK,\n \t\t\t\t\t       sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \tif (write_extensions & WRITE_CACHE_TREE_EXTENSION &&\n \t    !drop_cache_tree && istate->cache_tree) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\tcache_tree_write(&sb, istate->cache_tree);\n \t\terr = write_index_ext_header(f, eoie_c, CACHE_EXT_TREE, sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \tif (write_extensions & WRITE_RESOLVE_UNDO_EXTENSION &&\n \t    istate->resolve_undo) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\tresolve_undo_write(&sb, istate->resolve_undo);\n \t\terr = write_index_ext_header(f, eoie_c, CACHE_EXT_RESOLVE_UNDO,\n \t\t\t\t\t     sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \tif (write_extensions & WRITE_UNTRACKED_CACHE_EXTENSION &&\n \t    istate->untracked) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\twrite_untracked_extension(&sb, istate->untracked);\n \t\terr = write_index_ext_header(f, eoie_c, CACHE_EXT_UNTRACKED,\n \t\t\t\t\t     sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \tif (write_extensions & WRITE_FSMONITOR_EXTENSION &&\n \t    istate->fsmonitor_last_update) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\twrite_fsmonitor_extension(&sb, istate);\n \t\terr = write_index_ext_header(f, eoie_c, CACHE_EXT_FSMONITOR, sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \tif (istate->sparse_index) {\n-\t\tif (write_index_ext_header(f, eoie_c, CACHE_EXT_SPARSE_DIRECTORIES, 0) < 0)\n-\t\t\treturn -1;\n+\t\tif (write_index_ext_header(f, eoie_c, CACHE_EXT_SPARSE_DIRECTORIES, 0) < 0) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \n \t/*\n@@ -3069,14 +3077,15 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \t * when loading the shared index.\n \t */\n \tif (eoie_c) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\twrite_eoie_extension(&sb, eoie_c, offset);\n \t\terr = write_index_ext_header(f, NULL, CACHE_EXT_ENDOFINDEXENTRIES, sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \n \tcsum_fsync_flag = 0;\n@@ -3085,13 +3094,16 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \n \tfinalize_hashfile(f, istate->oid.hash, FSYNC_COMPONENT_INDEX,\n \t\t\t  CSUM_HASH_IN_STREAM | csum_fsync_flag);\n+\tf = NULL;\n \n \tif (close_tempfile_gently(tempfile)) {\n-\t\terror(_(\"could not close '%s'\"), get_tempfile_path(tempfile));\n-\t\treturn -1;\n+\t\tret = error(_(\"could not close '%s'\"), get_tempfile_path(tempfile));\n+\t\tgoto out;\n+\t}\n+\tif (stat(get_tempfile_path(tempfile), &st)) {\n+\t\tret = -1;\n+\t\tgoto out;\n \t}\n-\tif (stat(get_tempfile_path(tempfile), &st))\n-\t\treturn -1;\n \tistate->timestamp.sec = (unsigned int)st.st_mtime;\n \tistate->timestamp.nsec = ST_MTIME_NSEC(st);\n \ttrace_performance_since(start, \"write index, changed mask = %x\", istate->cache_changed);\n@@ -3105,7 +3117,14 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \ttrace2_data_intmax(\"index\", the_repository, \"write/cache_nr\",\n \t\t\t   istate->cache_nr);\n \n-\treturn 0;\n+\tret = 0;\n+\n+out:\n+\tif (f)\n+\t\tfree_hashfile(f);\n+\tstrbuf_release(&sb);\n+\tfree(ieot);\n+\treturn ret;\n }\n \n void set_alternate_index_output(const char *name)\ndiff --git a/t/t1601-index-bogus.sh b/t/t1601-index-bogus.sh\nindex 4171f1e141..5dcc101882 100755\n--- a/t/t1601-index-bogus.sh\n+++ b/t/t1601-index-bogus.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='test handling of bogus index entries'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'create tree with null sha1' '\ndiff --git a/t/t2107-update-index-basic.sh b/t/t2107-update-index-basic.sh\nindex cc72ead79f..f0eab13f96 100755\n--- a/t/t2107-update-index-basic.sh\n+++ b/t/t2107-update-index-basic.sh\n@@ -5,6 +5,7 @@ test_description='basic update-index tests\n Tests for command-line parsing and basic operation.\n '\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'update-index --nonsense fails' '\ndiff --git a/t/t7008-filter-branch-null-sha1.sh b/t/t7008-filter-branch-null-sha1.sh\nindex 93fbc92b8d..0ce8fd2c89 100755\n--- a/t/t7008-filter-branch-null-sha1.sh\n+++ b/t/t7008-filter-branch-null-sha1.sh\n@@ -2,6 +2,7 @@\n \n test_description='filter-branch removal of trees with null sha1'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup: base commits' '\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500743","messageId":"b7a7f88c7d8abe6f0460df20d37001ec26b6d8e9.1723540931.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723540931.git.ps@pks.im","subject":"[PATCH v3 07/22] submodule-config: fix leaking name entry when traversing submodules","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T09:31:25Z","receivedAt":"2024-08-13T09:31:29Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We traverse through submodules in the tree via `tree_entry()`, passing\nto it a `struct name_entry` that it is supposed to populate with the\ntree entry's contents. We unnecessarily allocate this variable instead\nof passing a variable that is allocated on the stack, and the ultimately\ndon't even free that variable. This is unnecessary and leaks memory.\n\nConvert the variable to instead be allocated on the stack to plug the\nmemory leak.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n submodule-config.c | 18 ++++++++----------\n 1 file changed, 8 insertions(+), 10 deletions(-)\n\ndiff --git a/submodule-config.c b/submodule-config.c\nindex 9b0bb0b9f4..c8f2bb2bdd 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -899,27 +899,25 @@ static void traverse_tree_submodules(struct repository *r,\n {\n \tstruct tree_desc tree;\n \tstruct submodule_tree_entry *st_entry;\n-\tstruct name_entry *name_entry;\n+\tstruct name_entry name_entry;\n \tchar *tree_path = NULL;\n \n-\tname_entry = xmalloc(sizeof(*name_entry));\n-\n \tfill_tree_descriptor(r, &tree, treeish_name);\n-\twhile (tree_entry(&tree, name_entry)) {\n+\twhile (tree_entry(&tree, &name_entry)) {\n \t\tif (prefix)\n \t\t\ttree_path =\n-\t\t\t\tmkpathdup(\"%s/%s\", prefix, name_entry->path);\n+\t\t\t\tmkpathdup(\"%s/%s\", prefix, name_entry.path);\n \t\telse\n-\t\t\ttree_path = xstrdup(name_entry->path);\n+\t\t\ttree_path = xstrdup(name_entry.path);\n \n-\t\tif (S_ISGITLINK(name_entry->mode) &&\n+\t\tif (S_ISGITLINK(name_entry.mode) &&\n \t\t    is_tree_submodule_active(r, root_tree, tree_path)) {\n \t\t\tALLOC_GROW(out->entries, out->entry_nr + 1,\n \t\t\t\t   out->entry_alloc);\n \t\t\tst_entry = &out->entries[out->entry_nr++];\n \n \t\t\tst_entry->name_entry = xmalloc(sizeof(*st_entry->name_entry));\n-\t\t\t*st_entry->name_entry = *name_entry;\n+\t\t\t*st_entry->name_entry = name_entry;\n \t\t\tst_entry->submodule =\n \t\t\t\tsubmodule_from_path(r, root_tree, tree_path);\n \t\t\tst_entry->repo = xmalloc(sizeof(*st_entry->repo));\n@@ -927,9 +925,9 @@ static void traverse_tree_submodules(struct repository *r,\n \t\t\t\t\t\troot_tree))\n \t\t\t\tFREE_AND_NULL(st_entry->repo);\n \n-\t\t} else if (S_ISDIR(name_entry->mode))\n+\t\t} else if (S_ISDIR(name_entry.mode))\n \t\t\ttraverse_tree_submodules(r, root_tree, tree_path,\n-\t\t\t\t\t\t &name_entry->oid, out);\n+\t\t\t\t\t\t &name_entry.oid, out);\n \t\tfree(tree_path);\n \t}\n }\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500744","messageId":"9054a459a1419e333ae8f950b9c9c2bf894c55f5.1723540931.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723540931.git.ps@pks.im","subject":"[PATCH v3 08/22] config: fix leaking comment character config","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T09:31:28Z","receivedAt":"2024-08-13T09:31:31Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When the comment line character has been specified multiple times in the\nconfiguration, then `git_default_core_config()` will cause a memory leak\nbecause it unconditionally copies the string into `comment_line_str`\nwithout free'ing the previous value. In fact, it can't easily free the\nvalue in the first place because it may contain a string constant.\n\nRefactor the code such that we track allocated comment character strings\nvia a separate non-constant variable `comment_line_str_to_free`. Adapt\nsites that set `comment_line_str` to set both and free the old value\nthat was stored in `comment_line_str_to_free`.\n\nThis memory leak is being hit in t3404. As there are still other memory\nleaks in that file we cannot yet mark it as passing with leak checking\nenabled.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/commit.c | 7 +++++--\n config.c         | 3 ++-\n environment.c    | 1 +\n environment.h    | 1 +\n 4 files changed, 9 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 66427ba82d..b2033c4887 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -684,7 +684,9 @@ static void adjust_comment_line_char(const struct strbuf *sb)\n \tconst char *p;\n \n \tif (!memchr(sb->buf, candidates[0], sb->len)) {\n-\t\tcomment_line_str = xstrfmt(\"%c\", candidates[0]);\n+\t\tfree(comment_line_str_to_free);\n+\t\tcomment_line_str = comment_line_str_to_free =\n+\t\t\txstrfmt(\"%c\", candidates[0]);\n \t\treturn;\n \t}\n \n@@ -705,7 +707,8 @@ static void adjust_comment_line_char(const struct strbuf *sb)\n \tif (!*p)\n \t\tdie(_(\"unable to select a comment character that is not used\\n\"\n \t\t      \"in the current commit message\"));\n-\tcomment_line_str = xstrfmt(\"%c\", *p);\n+\tfree(comment_line_str_to_free);\n+\tcomment_line_str = comment_line_str_to_free = xstrfmt(\"%c\", *p);\n }\n \n static void prepare_amend_commit(struct commit *commit, struct strbuf *sb,\ndiff --git a/config.c b/config.c\nindex 6421894614..205660a8fb 100644\n--- a/config.c\n+++ b/config.c\n@@ -1596,7 +1596,8 @@ static int git_default_core_config(const char *var, const char *value,\n \t\telse if (value[0]) {\n \t\t\tif (strchr(value, '\\n'))\n \t\t\t\treturn error(_(\"%s cannot contain newline\"), var);\n-\t\t\tcomment_line_str = xstrdup(value);\n+\t\t\tcomment_line_str = value;\n+\t\t\tFREE_AND_NULL(comment_line_str_to_free);\n \t\t\tauto_comment_line_char = 0;\n \t\t} else\n \t\t\treturn error(_(\"%s must have at least one character\"), var);\ndiff --git a/environment.c b/environment.c\nindex 5cea2c9f54..1d6c48b52d 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -114,6 +114,7 @@ int protect_ntfs = PROTECT_NTFS_DEFAULT;\n  * that is subject to stripspace.\n  */\n const char *comment_line_str = \"#\";\n+char *comment_line_str_to_free;\n int auto_comment_line_char;\n \n /* Parallel index stat data preload? */\ndiff --git a/environment.h b/environment.h\nindex e9f01d4d11..0148738ed6 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -9,6 +9,7 @@ struct strvec;\n  * that is subject to stripspace.\n  */\n extern const char *comment_line_str;\n+extern char *comment_line_str_to_free;\n extern int auto_comment_line_char;\n \n /*\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500745","messageId":"1d3957a5ebb527d672a66f6f6226b6668924a2fa.1723540931.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723540931.git.ps@pks.im","subject":"[PATCH v3 09/22] builtin/rebase: fix leaking `commit.gpgsign` value","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T09:31:31Z","receivedAt":"2024-08-13T09:31:35Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `get_replay_opts()`, we override the `gpg_sign` field that already\ngot populated by `sequencer_init_config()` in case the user has\n\"commit.gpgsign\" set in their config. This creates a memory leak because\nwe overwrite the previously assigned value, which may have already\npointed to an allocated string.\n\nLet's plug the memory leak by freeing the value before we overwrite it.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/rebase.c              | 1 +\n sequencer.c                   | 1 +\n t/t3404-rebase-interactive.sh | 1 +\n t/t3435-rebase-gpg-sign.sh    | 1 +\n t/t7030-verify-tag.sh         | 1 +\n 5 files changed, 5 insertions(+)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex e3a8e74cfc..2f01d5d3a6 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -186,6 +186,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n \treplay.committer_date_is_author_date =\n \t\t\t\t\topts->committer_date_is_author_date;\n \treplay.ignore_date = opts->ignore_date;\n+\tfree(replay.gpg_sign);\n \treplay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);\n \treplay.reflog_action = xstrdup(opts->reflog_action);\n \tif (opts->strategy)\ndiff --git a/sequencer.c b/sequencer.c\nindex 0291920f0b..cade9b0ca8 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -303,6 +303,7 @@ static int git_sequencer_config(const char *k, const char *v,\n \t}\n \n \tif (!strcmp(k, \"commit.gpgsign\")) {\n+\t\tfree(opts->gpg_sign);\n \t\topts->gpg_sign = git_config_bool(k, v) ? xstrdup(\"\") : NULL;\n \t\treturn 0;\n \t}\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex f92baad138..f171af3061 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -26,6 +26,7 @@ Initial setup:\n  touch file \"conflict\".\n '\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n . \"$TEST_DIRECTORY\"/lib-rebase.sh\ndiff --git a/t/t3435-rebase-gpg-sign.sh b/t/t3435-rebase-gpg-sign.sh\nindex 6aa2aeb628..6e329fea7c 100755\n--- a/t/t3435-rebase-gpg-sign.sh\n+++ b/t/t3435-rebase-gpg-sign.sh\n@@ -8,6 +8,7 @@ test_description='test rebase --[no-]gpg-sign'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY/lib-rebase.sh\"\n . \"$TEST_DIRECTORY/lib-gpg.sh\"\ndiff --git a/t/t7030-verify-tag.sh b/t/t7030-verify-tag.sh\nindex 6f526c37c2..effa826744 100755\n--- a/t/t7030-verify-tag.sh\n+++ b/t/t7030-verify-tag.sh\n@@ -4,6 +4,7 @@ test_description='signed tag tests'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY/lib-gpg.sh\"\n \n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500746","messageId":"0af1bab5a1432eb636dc0fa7b538fa39661cb34d.1723540931.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723540931.git.ps@pks.im","subject":"[PATCH v3 10/22] builtin/notes: fix leaking `struct notes_tree` when merging notes","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T09:31:34Z","receivedAt":"2024-08-13T09:31:40Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We allocate a `struct notes_tree` in `merge_commit()` which we then\ninitialize via `init_notes()`. It's not really necessary to allocate the\nstructure though given that we never pass ownership to the caller.\nFurthermore, the allocation leads to a memory leak because despite its\nname, `free_notes()` doesn't free the `notes_tree` but only clears it.\n\nFix this issue by converting the code to use an on-stack variable.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/notes.c                       | 9 ++++-----\n t/t3310-notes-merge-manual-resolve.sh | 1 +\n t/t3311-notes-merge-fanout.sh         | 1 +\n 3 files changed, 6 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex d9c356e354..81cbaeec6b 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -807,7 +807,7 @@ static int merge_commit(struct notes_merge_options *o)\n {\n \tstruct strbuf msg = STRBUF_INIT;\n \tstruct object_id oid, parent_oid;\n-\tstruct notes_tree *t;\n+\tstruct notes_tree t = {0};\n \tstruct commit *partial;\n \tstruct pretty_print_context pretty_ctx;\n \tvoid *local_ref_to_free;\n@@ -830,8 +830,7 @@ static int merge_commit(struct notes_merge_options *o)\n \telse\n \t\toidclr(&parent_oid, the_repository->hash_algo);\n \n-\tCALLOC_ARRAY(t, 1);\n-\tinit_notes(t, \"NOTES_MERGE_PARTIAL\", combine_notes_overwrite, 0);\n+\tinit_notes(&t, \"NOTES_MERGE_PARTIAL\", combine_notes_overwrite, 0);\n \n \to->local_ref = local_ref_to_free =\n \t\trefs_resolve_refdup(get_main_ref_store(the_repository),\n@@ -839,7 +838,7 @@ static int merge_commit(struct notes_merge_options *o)\n \tif (!o->local_ref)\n \t\tdie(_(\"failed to resolve NOTES_MERGE_REF\"));\n \n-\tif (notes_merge_commit(o, t, partial, &oid))\n+\tif (notes_merge_commit(o, &t, partial, &oid))\n \t\tdie(_(\"failed to finalize notes merge\"));\n \n \t/* Reuse existing commit message in reflog message */\n@@ -853,7 +852,7 @@ static int merge_commit(struct notes_merge_options *o)\n \t\t\tis_null_oid(&parent_oid) ? NULL : &parent_oid,\n \t\t\t0, UPDATE_REFS_DIE_ON_ERR);\n \n-\tfree_notes(t);\n+\tfree_notes(&t);\n \tstrbuf_release(&msg);\n \tret = merge_abort(o);\n \tfree(local_ref_to_free);\ndiff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh\nindex 597df5ebc0..04866b89be 100755\n--- a/t/t3310-notes-merge-manual-resolve.sh\n+++ b/t/t3310-notes-merge-manual-resolve.sh\n@@ -5,6 +5,7 @@\n \n test_description='Test notes merging with manual conflict resolution'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # Set up a notes merge scenario with different kinds of conflicts\ndiff --git a/t/t3311-notes-merge-fanout.sh b/t/t3311-notes-merge-fanout.sh\nindex 5b675417e9..ce4144db0f 100755\n--- a/t/t3311-notes-merge-fanout.sh\n+++ b/t/t3311-notes-merge-fanout.sh\n@@ -5,6 +5,7 @@\n \n test_description='Test notes merging at various fanout levels'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n verify_notes () {\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500747","messageId":"30d4e9ed437b70a46d9b625cdb9bc4230aca298f.1723540931.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723540931.git.ps@pks.im","subject":"[PATCH v3 11/22] builtin/fast-import: plug trivial memory leaks","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T09:31:39Z","receivedAt":"2024-08-13T09:31:42Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Plug some trivial memory leaks in git-fast-import(1).\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fast-import.c        | 8 ++++++--\n t/t9300-fast-import.sh       | 1 +\n t/t9304-fast-import-marks.sh | 2 ++\n 3 files changed, 9 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex d21c4053a7..6dfeb01665 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -206,8 +206,8 @@ static unsigned int object_entry_alloc = 5000;\n static struct object_entry_pool *blocks;\n static struct hashmap object_table;\n static struct mark_set *marks;\n-static const char *export_marks_file;\n-static const char *import_marks_file;\n+static char *export_marks_file;\n+static char *import_marks_file;\n static int import_marks_file_from_stream;\n static int import_marks_file_ignore_missing;\n static int import_marks_file_done;\n@@ -3274,6 +3274,7 @@ static void option_import_marks(const char *marks,\n \t\t\tread_marks();\n \t}\n \n+\tfree(import_marks_file);\n \timport_marks_file = make_fast_import_path(marks);\n \timport_marks_file_from_stream = from_stream;\n \timport_marks_file_ignore_missing = ignore_missing;\n@@ -3316,6 +3317,7 @@ static void option_active_branches(const char *branches)\n \n static void option_export_marks(const char *marks)\n {\n+\tfree(export_marks_file);\n \texport_marks_file = make_fast_import_path(marks);\n }\n \n@@ -3357,6 +3359,8 @@ static void option_rewrite_submodules(const char *arg, struct string_list *list)\n \tfree(f);\n \n \tstring_list_insert(list, s)->util = ms;\n+\n+\tfree(s);\n }\n \n static int parse_one_option(const char *option)\ndiff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh\nindex 1e68426852..3b3c371740 100755\n--- a/t/t9300-fast-import.sh\n+++ b/t/t9300-fast-import.sh\n@@ -7,6 +7,7 @@ test_description='test git fast-import utility'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-diff.sh ;# test-lib chdir's into trash\n \ndiff --git a/t/t9304-fast-import-marks.sh b/t/t9304-fast-import-marks.sh\nindex 410a871c52..1f776a80f3 100755\n--- a/t/t9304-fast-import-marks.sh\n+++ b/t/t9304-fast-import-marks.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='test exotic situations with marks'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup dump of basic history' '\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500748","messageId":"9591fb7b5e1dac2f989bd10ef2c13a191571a060.1723540931.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723540931.git.ps@pks.im","subject":"[PATCH v3 12/22] builtin/fast-export: fix leaking diff options","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T09:31:42Z","receivedAt":"2024-08-13T09:31:46Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Before calling `handle_commit()` in a loop, we set `diffopt.no_free`\nsuch that its contents aren't getting freed inside of `handle_commit()`.\nWe never unset that flag though, which means that it'll ultimately leak\nwhen calling `release_revisions()`.\n\nFix this by unsetting the flag after the loop.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fast-export.c | 2 ++\n 1 file changed, 2 insertions(+)\n\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex 4b6e8c6832..fe92d2436c 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -1278,9 +1278,11 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)\n \trevs.diffopt.format_callback = show_filemodify;\n \trevs.diffopt.format_callback_data = &paths_of_changed_objects;\n \trevs.diffopt.flags.recursive = 1;\n+\n \trevs.diffopt.no_free = 1;\n \twhile ((commit = get_revision(&revs)))\n \t\thandle_commit(commit, &revs, &paths_of_changed_objects);\n+\trevs.diffopt.no_free = 0;\n \n \thandle_tags_and_duplicates(&extra_refs);\n \thandle_tags_and_duplicates(&tag_refs);\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500749","messageId":"254bbb7f6f20ed85b1ec894ffa515ab1ae4f38eb.1723540931.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723540931.git.ps@pks.im","subject":"[PATCH v3 13/22] builtin/fast-export: plug leaking tag names","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T09:31:45Z","receivedAt":"2024-08-13T09:31:48Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When resolving revisions in `get_tags_and_duplicates()`, we only\npartially manage the lifetime of `full_name`. In fact, managing its\nlifetime properly is almost impossible because we put direct pointers to\nthat variable into multiple lists without duplicating the string. The\nconsequence is that these strings will ultimately leak.\n\nRefactor the code to make the lists we put those names into duplicate\nthe memory. This allows us to properly free the string as required and\nthus plugs the memory leak.\n\nWhile this requires us to allocate more data overall, it shouldn't be\nall that bad given that the number of allocations corresponds with the\nnumber of command line parameters, which typically aren't all that many.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fast-export.c            | 17 ++++++++++++-----\n t/t9351-fast-export-anonymize.sh |  1 +\n 2 files changed, 13 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex fe92d2436c..f253b79322 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -42,8 +42,8 @@ static int full_tree;\n static int reference_excluded_commits;\n static int show_original_ids;\n static int mark_tags;\n-static struct string_list extra_refs = STRING_LIST_INIT_NODUP;\n-static struct string_list tag_refs = STRING_LIST_INIT_NODUP;\n+static struct string_list extra_refs = STRING_LIST_INIT_DUP;\n+static struct string_list tag_refs = STRING_LIST_INIT_DUP;\n static struct refspec refspecs = REFSPEC_INIT_FETCH;\n static int anonymize;\n static struct hashmap anonymized_seeds;\n@@ -901,7 +901,7 @@ static void handle_tag(const char *name, struct tag *tag)\n \tfree(buf);\n }\n \n-static struct commit *get_commit(struct rev_cmdline_entry *e, char *full_name)\n+static struct commit *get_commit(struct rev_cmdline_entry *e, const char *full_name)\n {\n \tswitch (e->item->type) {\n \tcase OBJ_COMMIT:\n@@ -932,14 +932,16 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)\n \t\tstruct rev_cmdline_entry *e = info->rev + i;\n \t\tstruct object_id oid;\n \t\tstruct commit *commit;\n-\t\tchar *full_name;\n+\t\tchar *full_name = NULL;\n \n \t\tif (e->flags & UNINTERESTING)\n \t\t\tcontinue;\n \n \t\tif (repo_dwim_ref(the_repository, e->name, strlen(e->name),\n-\t\t\t\t  &oid, &full_name, 0) != 1)\n+\t\t\t\t  &oid, &full_name, 0) != 1) {\n+\t\t\tfree(full_name);\n \t\t\tcontinue;\n+\t\t}\n \n \t\tif (refspecs.nr) {\n \t\t\tchar *private;\n@@ -955,6 +957,7 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)\n \t\t\twarning(\"%s: Unexpected object of type %s, skipping.\",\n \t\t\t\te->name,\n \t\t\t\ttype_name(e->item->type));\n+\t\t\tfree(full_name);\n \t\t\tcontinue;\n \t\t}\n \n@@ -963,10 +966,12 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)\n \t\t\tbreak;\n \t\tcase OBJ_BLOB:\n \t\t\texport_blob(&commit->object.oid);\n+\t\t\tfree(full_name);\n \t\t\tcontinue;\n \t\tdefault: /* OBJ_TAG (nested tags) is already handled */\n \t\t\twarning(\"Tag points to object of unexpected type %s, skipping.\",\n \t\t\t\ttype_name(commit->object.type));\n+\t\t\tfree(full_name);\n \t\t\tcontinue;\n \t\t}\n \n@@ -979,6 +984,8 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)\n \n \t\tif (!*revision_sources_at(&revision_sources, commit))\n \t\t\t*revision_sources_at(&revision_sources, commit) = full_name;\n+\t\telse\n+\t\t\tfree(full_name);\n \t}\n \n \tstring_list_sort(&extra_refs);\ndiff --git a/t/t9351-fast-export-anonymize.sh b/t/t9351-fast-export-anonymize.sh\nindex 156a647484..c0d9d7be75 100755\n--- a/t/t9351-fast-export-anonymize.sh\n+++ b/t/t9351-fast-export-anonymize.sh\n@@ -4,6 +4,7 @@ test_description='basic tests for fast-export --anonymize'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup simple repo' '\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500750","messageId":"334c4ed71a1e51f9bbbc567e13c10b5fd0c303ed.1723540931.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723540931.git.ps@pks.im","subject":"[PATCH v3 14/22] merge-ort: unconditionally release attributes index","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T09:31:48Z","receivedAt":"2024-08-13T09:31:52Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We conditionally release the index used for reading gitattributes in\nmerge-ort based on whether or the index has been populated. This check\nuses `cache_nr` as a condition. This isn't sufficient though, as the\nvariable may be zero even when some other parts of the index have been\npopulated. This leads to memory leaks when sparse checkouts are in use,\nas we may not end up releasing the sparse checkout patterns.\n\nFix this issue by unconditionally releasing the index.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n merge-ort.c                       | 3 +--\n t/t3507-cherry-pick-conflict.sh   | 1 +\n t/t6421-merge-partial-clone.sh    | 1 +\n t/t6428-merge-conflicts-sparse.sh | 1 +\n t/t7817-grep-sparse-checkout.sh   | 1 +\n 5 files changed, 5 insertions(+), 2 deletions(-)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex e9d01ac7f7..3752c7e595 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -689,8 +689,7 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,\n \t */\n \tstrmap_clear_func(&opti->conflicted, 0);\n \n-\tif (opti->attr_index.cache_nr) /* true iff opt->renormalize */\n-\t\tdiscard_index(&opti->attr_index);\n+\tdiscard_index(&opti->attr_index);\n \n \t/* Free memory used by various renames maps */\n \tfor (i = MERGE_SIDE1; i <= MERGE_SIDE2; ++i) {\ndiff --git a/t/t3507-cherry-pick-conflict.sh b/t/t3507-cherry-pick-conflict.sh\nindex f3947b400a..10e9c91dbb 100755\n--- a/t/t3507-cherry-pick-conflict.sh\n+++ b/t/t3507-cherry-pick-conflict.sh\n@@ -13,6 +13,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n TEST_CREATE_REPO_NO_TEMPLATE=1\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n pristine_detach () {\ndiff --git a/t/t6421-merge-partial-clone.sh b/t/t6421-merge-partial-clone.sh\nindex 711b709e75..020375c805 100755\n--- a/t/t6421-merge-partial-clone.sh\n+++ b/t/t6421-merge-partial-clone.sh\n@@ -26,6 +26,7 @@ test_description=\"limiting blob downloads when merging with partial clones\"\n #                     underscore notation is to differentiate different\n #                     files that might be renamed into each other's paths.)\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-merge.sh\n \ndiff --git a/t/t6428-merge-conflicts-sparse.sh b/t/t6428-merge-conflicts-sparse.sh\nindex 9919c3fa7c..8a79bc2e92 100755\n--- a/t/t6428-merge-conflicts-sparse.sh\n+++ b/t/t6428-merge-conflicts-sparse.sh\n@@ -22,6 +22,7 @@ test_description=\"merge cases\"\n #                     underscore notation is to differentiate different\n #                     files that might be renamed into each other's paths.)\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-merge.sh\n \ndiff --git a/t/t7817-grep-sparse-checkout.sh b/t/t7817-grep-sparse-checkout.sh\nindex eb59564565..0ba7817fb7 100755\n--- a/t/t7817-grep-sparse-checkout.sh\n+++ b/t/t7817-grep-sparse-checkout.sh\n@@ -33,6 +33,7 @@ should leave the following structure in the working tree:\n But note that sub2 should have the SKIP_WORKTREE bit set.\n '\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup' '\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500751","messageId":"9f08a859fb7481187310331c008317c4626a3a82.1723540931.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723540931.git.ps@pks.im","subject":"[PATCH v3 15/22] sequencer: release todo list on error paths","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T09:31:51Z","receivedAt":"2024-08-13T09:31:57Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We're not releasing the `todo_list` in `sequencer_pick_revisions()` when\nhitting an error path. Restructure the function to have a common exit\npath such that we can easily clean up the list and thus plug this memory\nleak.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n sequencer.c                     | 66 +++++++++++++++++++++++----------\n t/t3510-cherry-pick-sequence.sh |  1 +\n 2 files changed, 48 insertions(+), 19 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex cade9b0ca8..ea559c31f1 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5490,8 +5490,10 @@ int sequencer_pick_revisions(struct repository *r,\n \tint i, res;\n \n \tassert(opts->revs);\n-\tif (read_and_refresh_cache(r, opts))\n-\t\treturn -1;\n+\tif (read_and_refresh_cache(r, opts)) {\n+\t\tres = -1;\n+\t\tgoto out;\n+\t}\n \n \tfor (i = 0; i < opts->revs->pending.nr; i++) {\n \t\tstruct object_id oid;\n@@ -5506,11 +5508,14 @@ int sequencer_pick_revisions(struct repository *r,\n \t\t\t\tenum object_type type = oid_object_info(r,\n \t\t\t\t\t\t\t\t\t&oid,\n \t\t\t\t\t\t\t\t\tNULL);\n-\t\t\t\treturn error(_(\"%s: can't cherry-pick a %s\"),\n-\t\t\t\t\tname, type_name(type));\n+\t\t\t\tres = error(_(\"%s: can't cherry-pick a %s\"),\n+\t\t\t\t\t    name, type_name(type));\n+\t\t\t\tgoto out;\n \t\t\t}\n-\t\t} else\n-\t\t\treturn error(_(\"%s: bad revision\"), name);\n+\t\t} else {\n+\t\t\tres = error(_(\"%s: bad revision\"), name);\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \n \t/*\n@@ -5525,14 +5530,23 @@ int sequencer_pick_revisions(struct repository *r,\n \t    opts->revs->no_walk &&\n \t    !opts->revs->cmdline.rev->flags) {\n \t\tstruct commit *cmit;\n-\t\tif (prepare_revision_walk(opts->revs))\n-\t\t\treturn error(_(\"revision walk setup failed\"));\n+\n+\t\tif (prepare_revision_walk(opts->revs)) {\n+\t\t\tres = error(_(\"revision walk setup failed\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n \t\tcmit = get_revision(opts->revs);\n-\t\tif (!cmit)\n-\t\t\treturn error(_(\"empty commit set passed\"));\n+\t\tif (!cmit) {\n+\t\t\tres = error(_(\"empty commit set passed\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n \t\tif (get_revision(opts->revs))\n \t\t\tBUG(\"unexpected extra commit from walk\");\n-\t\treturn single_pick(r, cmit, opts);\n+\n+\t\tres = single_pick(r, cmit, opts);\n+\t\tgoto out;\n \t}\n \n \t/*\n@@ -5542,16 +5556,30 @@ int sequencer_pick_revisions(struct repository *r,\n \t */\n \n \tif (walk_revs_populate_todo(&todo_list, opts) ||\n-\t\t\tcreate_seq_dir(r) < 0)\n-\t\treturn -1;\n-\tif (repo_get_oid(r, \"HEAD\", &oid) && (opts->action == REPLAY_REVERT))\n-\t\treturn error(_(\"can't revert as initial commit\"));\n-\tif (save_head(oid_to_hex(&oid)))\n-\t\treturn -1;\n-\tif (save_opts(opts))\n-\t\treturn -1;\n+\t\t\tcreate_seq_dir(r) < 0) {\n+\t\tres = -1;\n+\t\tgoto out;\n+\t}\n+\n+\tif (repo_get_oid(r, \"HEAD\", &oid) && (opts->action == REPLAY_REVERT)) {\n+\t\tres = error(_(\"can't revert as initial commit\"));\n+\t\tgoto out;\n+\t}\n+\n+\tif (save_head(oid_to_hex(&oid))) {\n+\t\tres = -1;\n+\t\tgoto out;\n+\t}\n+\n+\tif (save_opts(opts)) {\n+\t\tres = -1;\n+\t\tgoto out;\n+\t}\n+\n \tupdate_abort_safety_file();\n \tres = pick_commits(r, &todo_list, opts);\n+\n+out:\n \ttodo_list_release(&todo_list);\n \treturn res;\n }\ndiff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh\nindex 7eb52b12ed..93c725bac3 100755\n--- a/t/t3510-cherry-pick-sequence.sh\n+++ b/t/t3510-cherry-pick-sequence.sh\n@@ -12,6 +12,7 @@ test_description='Test cherry-pick continuation features\n \n '\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # Repeat first match 10 times\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500752","messageId":"5d4934b1a9cf402cbae2f25ef4e0254d45e31e21.1723540931.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723540931.git.ps@pks.im","subject":"[PATCH v3 16/22] unpack-trees: clear index when not propagating it","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T09:31:56Z","receivedAt":"2024-08-13T09:31:59Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When provided a pointer to a destination index, then `unpack_trees()`\nwill end up copying its `o->internal.result` index into the provided\npointer. In those cases it is thus not necessary to free the index, as\nwe have transferred ownership of it.\n\nThere are cases though where we do not end up transferring ownership of\nthe memory, but `clear_unpack_trees_porcelain()` will never discard the\nindex in that case and thus cause a memory leak. And right now it cannot\ndo so in the first place because we have no indicator of whether we did\nor didn't transfer ownership of the index.\n\nAdapt the code to zero out the index in case we transfer its ownership.\nLike this, we can now unconditionally discard the index when being asked\nto clear the `unpack_trees_options`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n t/t3705-add-sparse-checkout.sh | 1 +\n unpack-trees.c                 | 2 ++\n 2 files changed, 3 insertions(+)\n\ndiff --git a/t/t3705-add-sparse-checkout.sh b/t/t3705-add-sparse-checkout.sh\nindex 2bade9e804..6ae45a788d 100755\n--- a/t/t3705-add-sparse-checkout.sh\n+++ b/t/t3705-add-sparse-checkout.sh\n@@ -2,6 +2,7 @@\n \n test_description='git add in sparse checked out working trees'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n SPARSE_ENTRY_BLOB=\"\"\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 7dc884fafd..9a55cb6204 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -210,6 +210,7 @@ void clear_unpack_trees_porcelain(struct unpack_trees_options *opts)\n {\n \tstrvec_clear(&opts->internal.msgs_to_free);\n \tmemset(opts->internal.msgs, 0, sizeof(opts->internal.msgs));\n+\tdiscard_index(&opts->internal.result);\n }\n \n static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce,\n@@ -2082,6 +2083,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options\n \t\to->internal.result.updated_workdir = 1;\n \t\tdiscard_index(o->dst_index);\n \t\t*o->dst_index = o->internal.result;\n+\t\tmemset(&o->internal.result, 0, sizeof(o->internal.result));\n \t} else {\n \t\tdiscard_index(&o->internal.result);\n \t}\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500753","messageId":"e1b6a24fbe507c88695deca8ec71546bbd4e12d6.1723540931.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723540931.git.ps@pks.im","subject":"[PATCH v3 17/22] diff: fix leak when parsing invalid ignore regex option","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T09:31:59Z","receivedAt":"2024-08-13T09:32:03Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When parsing invalid ignore regexes passed via the `-I` option we don't\nfree already-allocated memory, leading to a memory leak. Fix this.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n diff.c                  | 6 +++++-\n t/t4013-diff-various.sh | 1 +\n 2 files changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/diff.c b/diff.c\nindex ebb7538e04..9251c47b72 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -5464,9 +5464,13 @@ static int diff_opt_ignore_regex(const struct option *opt,\n \tregex_t *regex;\n \n \tBUG_ON_OPT_NEG(unset);\n+\n \tregex = xmalloc(sizeof(*regex));\n-\tif (regcomp(regex, arg, REG_EXTENDED | REG_NEWLINE))\n+\tif (regcomp(regex, arg, REG_EXTENDED | REG_NEWLINE)) {\n+\t\tfree(regex);\n \t\treturn error(_(\"invalid regex given to -I: '%s'\"), arg);\n+\t}\n+\n \tALLOC_GROW(options->ignore_regex, options->ignore_regex_nr + 1,\n \t\t   options->ignore_regex_alloc);\n \toptions->ignore_regex[options->ignore_regex_nr++] = regex;\ndiff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh\nindex 3855d68dbc..87d248d034 100755\n--- a/t/t4013-diff-various.sh\n+++ b/t/t4013-diff-various.sh\n@@ -8,6 +8,7 @@ test_description='Various diff formatting options'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-diff.sh\n \n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500754","messageId":"c048b54a2c493658b2dd256b301491a79cfa99a1.1723540931.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723540931.git.ps@pks.im","subject":"[PATCH v3 18/22] builtin/format-patch: fix various trivial memory leaks","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T09:32:02Z","receivedAt":"2024-08-13T09:32:06Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"There are various memory leaks hit by git-format-patch(1). Basically all\nof them are trivial, except that un-setting `diffopt.no_free` requires\nus to unset the `diffopt.file` because we manually close it already.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/log.c           | 12 +++++++++---\n t/t4014-format-patch.sh |  1 +\n 2 files changed, 10 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex a73a767606..ff997a0d0e 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1833,6 +1833,7 @@ static struct commit *get_base_commit(const struct format_config *cfg,\n \t\t\t}\n \n \t\t\trev[i] = merge_base->item;\n+\t\t\tfree_commit_list(merge_base);\n \t\t}\n \n \t\tif (rev_nr % 2)\n@@ -2023,6 +2024,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \tconst char *rfc = NULL;\n \tint creation_factor = -1;\n \tconst char *signature = git_version_string;\n+\tchar *signature_to_free = NULL;\n \tchar *signature_file_arg = NULL;\n \tstruct keep_callback_data keep_callback_data = {\n \t\t.cfg = &cfg,\n@@ -2443,7 +2445,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \n \t\tif (strbuf_read_file(&buf, signature_file, 128) < 0)\n \t\t\tdie_errno(_(\"unable to read signature file '%s'\"), signature_file);\n-\t\tsignature = strbuf_detach(&buf, NULL);\n+\t\tsignature = signature_to_free = strbuf_detach(&buf, NULL);\n \t} else if (cfg.signature) {\n \t\tsignature = cfg.signature;\n \t}\n@@ -2548,12 +2550,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \t\t\telse\n \t\t\t\tprint_signature(signature, rev.diffopt.file);\n \t\t}\n-\t\tif (output_directory)\n+\t\tif (output_directory) {\n \t\t\tfclose(rev.diffopt.file);\n+\t\t\trev.diffopt.file = NULL;\n+\t\t}\n \t}\n \tstop_progress(&progress);\n \tfree(list);\n-\tfree(branch_name);\n \tif (ignore_if_in_upstream)\n \t\tfree_patch_ids(&ids);\n \n@@ -2565,11 +2568,14 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \tstrbuf_release(&rdiff_title);\n \tfree(description_file);\n \tfree(signature_file_arg);\n+\tfree(signature_to_free);\n+\tfree(branch_name);\n \tfree(to_free);\n \tfree(rev.message_id);\n \tif (rev.ref_message_ids)\n \t\tstring_list_clear(rev.ref_message_ids, 0);\n \tfree(rev.ref_message_ids);\n+\trev.diffopt.no_free = 0;\n \trelease_revisions(&rev);\n \tformat_config_release(&cfg);\n \treturn 0;\ndiff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh\nindex 884f83fb8a..1c46e963e4 100755\n--- a/t/t4014-format-patch.sh\n+++ b/t/t4014-format-patch.sh\n@@ -8,6 +8,7 @@ test_description='various format-patch tests'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-terminal.sh\n \n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500755","messageId":"39b2921e3e6aeb7bc5d6d08ed9861d26b738a42c.1723540931.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723540931.git.ps@pks.im","subject":"[PATCH v3 19/22] userdiff: fix leaking memory for configured diff drivers","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T09:32:05Z","receivedAt":"2024-08-13T09:32:09Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The userdiff structures may be initialized either statically on the\nstack or dynamically via configuration keys. In the latter case we end\nup leaking memory because we didn't have any infrastructure to discern\nthose strings which have been allocated statically and those which have\nbeen allocated dynamically.\n\nRefactor the code such that we have two pointers for each of these\nstrings: one that holds the value as accessed by other subsystems, and\none that points to the same string in case it has been allocated. Like\nthis, we can safely free the second pointer and thus plug those memory\nleaks.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n range-diff.c                     |  6 +++--\n t/t4018-diff-funcname.sh         |  1 +\n t/t4042-diff-textconv-caching.sh |  2 ++\n t/t4048-diff-combined-binary.sh  |  1 +\n t/t4209-log-pickaxe.sh           |  2 ++\n userdiff.c                       | 38 ++++++++++++++++++++++++--------\n userdiff.h                       |  4 ++++\n 7 files changed, 43 insertions(+), 11 deletions(-)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex 5f01605550..bbb0952264 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -450,8 +450,10 @@ static void output_pair_header(struct diff_options *diffopt,\n }\n \n static struct userdiff_driver section_headers = {\n-\t.funcname = { \"^ ## (.*) ##$\\n\"\n-\t\t      \"^.?@@ (.*)$\", REG_EXTENDED }\n+\t.funcname = {\n+\t\t.pattern = \"^ ## (.*) ##$\\n^.?@@ (.*)$\",\n+\t\t.cflags = REG_EXTENDED,\n+\t},\n };\n \n static struct diff_filespec *get_filespec(const char *name, const char *p)\ndiff --git a/t/t4018-diff-funcname.sh b/t/t4018-diff-funcname.sh\nindex e026fac1f4..8128c30e7f 100755\n--- a/t/t4018-diff-funcname.sh\n+++ b/t/t4018-diff-funcname.sh\n@@ -5,6 +5,7 @@\n \n test_description='Test custom diff function name patterns'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup' '\ndiff --git a/t/t4042-diff-textconv-caching.sh b/t/t4042-diff-textconv-caching.sh\nindex 8ebfa3c1be..a179205394 100755\n--- a/t/t4042-diff-textconv-caching.sh\n+++ b/t/t4042-diff-textconv-caching.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='test textconv caching'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n cat >helper <<'EOF'\ndiff --git a/t/t4048-diff-combined-binary.sh b/t/t4048-diff-combined-binary.sh\nindex 0260cf64f5..f399484bce 100755\n--- a/t/t4048-diff-combined-binary.sh\n+++ b/t/t4048-diff-combined-binary.sh\n@@ -4,6 +4,7 @@ test_description='combined and merge diff handle binary files and textconv'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup binary merge conflict' '\ndiff --git a/t/t4209-log-pickaxe.sh b/t/t4209-log-pickaxe.sh\nindex 64e1623733..b42fdc54fc 100755\n--- a/t/t4209-log-pickaxe.sh\n+++ b/t/t4209-log-pickaxe.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='log --grep/--author/--regexp-ignore-case/-S/-G'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_log () {\ndiff --git a/userdiff.c b/userdiff.c\nindex c4ebb9ff73..989629149f 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -399,8 +399,11 @@ static struct userdiff_driver *userdiff_find_by_namelen(const char *name, size_t\n static int parse_funcname(struct userdiff_funcname *f, const char *k,\n \t\tconst char *v, int cflags)\n {\n-\tif (git_config_string((char **) &f->pattern, k, v) < 0)\n+\tf->pattern = NULL;\n+\tFREE_AND_NULL(f->pattern_owned);\n+\tif (git_config_string(&f->pattern_owned, k, v) < 0)\n \t\treturn -1;\n+\tf->pattern = f->pattern_owned;\n \tf->cflags = cflags;\n \treturn 0;\n }\n@@ -444,20 +447,37 @@ int userdiff_config(const char *k, const char *v)\n \t\treturn parse_funcname(&drv->funcname, k, v, REG_EXTENDED);\n \tif (!strcmp(type, \"binary\"))\n \t\treturn parse_tristate(&drv->binary, k, v);\n-\tif (!strcmp(type, \"command\"))\n-\t\treturn git_config_string((char **) &drv->external.cmd, k, v);\n+\tif (!strcmp(type, \"command\")) {\n+\t\tFREE_AND_NULL(drv->external.cmd);\n+\t\treturn git_config_string(&drv->external.cmd, k, v);\n+\t}\n \tif (!strcmp(type, \"trustexitcode\")) {\n \t\tdrv->external.trust_exit_code = git_config_bool(k, v);\n \t\treturn 0;\n \t}\n-\tif (!strcmp(type, \"textconv\"))\n-\t\treturn git_config_string((char **) &drv->textconv, k, v);\n+\tif (!strcmp(type, \"textconv\")) {\n+\t\tint ret;\n+\t\tFREE_AND_NULL(drv->textconv_owned);\n+\t\tret = git_config_string(&drv->textconv_owned, k, v);\n+\t\tdrv->textconv = drv->textconv_owned;\n+\t\treturn ret;\n+\t}\n \tif (!strcmp(type, \"cachetextconv\"))\n \t\treturn parse_bool(&drv->textconv_want_cache, k, v);\n-\tif (!strcmp(type, \"wordregex\"))\n-\t\treturn git_config_string((char **) &drv->word_regex, k, v);\n-\tif (!strcmp(type, \"algorithm\"))\n-\t\treturn git_config_string((char **) &drv->algorithm, k, v);\n+\tif (!strcmp(type, \"wordregex\")) {\n+\t\tint ret;\n+\t\tFREE_AND_NULL(drv->word_regex_owned);\n+\t\tret = git_config_string(&drv->word_regex_owned, k, v);\n+\t\tdrv->word_regex = drv->word_regex_owned;\n+\t\treturn ret;\n+\t}\n+\tif (!strcmp(type, \"algorithm\")) {\n+\t\tint ret;\n+\t\tFREE_AND_NULL(drv->algorithm_owned);\n+\t\tret = git_config_string(&drv->algorithm_owned, k, v);\n+\t\tdrv->algorithm = drv->algorithm_owned;\n+\t\treturn ret;\n+\t}\n \n \treturn 0;\n }\ndiff --git a/userdiff.h b/userdiff.h\nindex 7565930337..827361b0bc 100644\n--- a/userdiff.h\n+++ b/userdiff.h\n@@ -8,6 +8,7 @@ struct repository;\n \n struct userdiff_funcname {\n \tconst char *pattern;\n+\tchar *pattern_owned;\n \tint cflags;\n };\n \n@@ -20,11 +21,14 @@ struct userdiff_driver {\n \tconst char *name;\n \tstruct external_diff external;\n \tconst char *algorithm;\n+\tchar *algorithm_owned;\n \tint binary;\n \tstruct userdiff_funcname funcname;\n \tconst char *word_regex;\n+\tchar *word_regex_owned;\n \tconst char *word_regex_multi_byte;\n \tconst char *textconv;\n+\tchar *textconv_owned;\n \tstruct notes_cache *textconv_cache;\n \tint textconv_want_cache;\n };\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500756","messageId":"50dea1c98a207498c5c48e1fb9eb63ad15960e6c.1723540931.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723540931.git.ps@pks.im","subject":"[PATCH v3 20/22] builtin/log: fix leak when showing converted blob contents","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T09:32:08Z","receivedAt":"2024-08-13T09:32:13Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `show_blob_object()`, we proactively call `textconv_object()`. In\ncase we have a textconv driver for this blob we will end up showing the\nconverted contents, otherwise we'll show the un-converted contents of it\ninstead.\n\nWhen the object has been converted we never free the buffer containing\nthe converted contents. Fix this to plug this memory leak.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/log.c            | 1 +\n t/t4030-diff-textconv.sh | 2 ++\n 2 files changed, 3 insertions(+)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex ff997a0d0e..1a684b68f2 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -707,6 +707,7 @@ static int show_blob_object(const struct object_id *oid, struct rev_info *rev, c\n \n \twrite_or_die(1, buf, size);\n \tobject_context_release(&obj_context);\n+\tfree(buf);\n \treturn 0;\n }\n \ndiff --git a/t/t4030-diff-textconv.sh b/t/t4030-diff-textconv.sh\nindex a39a626664..29f6d610c2 100755\n--- a/t/t4030-diff-textconv.sh\n+++ b/t/t4030-diff-textconv.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='diff.*.textconv tests'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n find_diff() {\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500757","messageId":"d5cb4ad580c7370a4ee48d2f5075a1764972c353.1723540931.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723540931.git.ps@pks.im","subject":"[PATCH v3 21/22] diff: free state populated via options","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T09:32:13Z","receivedAt":"2024-08-13T09:32:17Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `objfind` and `anchors` members of `struct diff_options` are\npopulated via option parsing, but are never freed in `diff_free()`. Fix\nthis to plug those memory leaks.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n diff.c                   | 10 ++++++++++\n t/t4064-diff-oidfind.sh  |  2 ++\n t/t4065-diff-anchored.sh |  1 +\n t/t4069-remerge-diff.sh  |  1 +\n 4 files changed, 14 insertions(+)\n\ndiff --git a/diff.c b/diff.c\nindex 9251c47b72..4035a9374d 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -6717,6 +6717,16 @@ void diff_free(struct diff_options *options)\n \tif (options->no_free)\n \t\treturn;\n \n+\tif (options->objfind) {\n+\t\toidset_clear(options->objfind);\n+\t\tFREE_AND_NULL(options->objfind);\n+\t}\n+\n+\tfor (size_t i = 0; i < options->anchors_nr; i++)\n+\t\tfree(options->anchors[i]);\n+\tFREE_AND_NULL(options->anchors);\n+\toptions->anchors_nr = options->anchors_alloc = 0;\n+\n \tdiff_free_file(options);\n \tdiff_free_ignore_regex(options);\n \tclear_pathspec(&options->pathspec);\ndiff --git a/t/t4064-diff-oidfind.sh b/t/t4064-diff-oidfind.sh\nindex 6d8c8986fc..846f285f77 100755\n--- a/t/t4064-diff-oidfind.sh\n+++ b/t/t4064-diff-oidfind.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='test finding specific blobs in the revision walking'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup ' '\ndiff --git a/t/t4065-diff-anchored.sh b/t/t4065-diff-anchored.sh\nindex b3f510f040..647537c12e 100755\n--- a/t/t4065-diff-anchored.sh\n+++ b/t/t4065-diff-anchored.sh\n@@ -2,6 +2,7 @@\n \n test_description='anchored diff algorithm'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success '--anchored' '\ndiff --git a/t/t4069-remerge-diff.sh b/t/t4069-remerge-diff.sh\nindex 07323ebafe..888714bbd3 100755\n--- a/t/t4069-remerge-diff.sh\n+++ b/t/t4069-remerge-diff.sh\n@@ -2,6 +2,7 @@\n \n test_description='remerge-diff handling'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # This test is ort-specific\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500758","messageId":"31e38ba4e150c9bc9e3aa1073869881ccba9035e.1723540931.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723540931.git.ps@pks.im","subject":"[PATCH v3 22/22] builtin/diff: free symmetric diff members","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-13T09:32:16Z","receivedAt":"2024-08-13T09:32:20Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We populate a `struct symdiff` in case the user has requested a\nsymmetric diff. Part of this is to populate a `skip` bitmap that\nindicates which commits shall be ignored in the diff. But while this\nbitmap is dynamically allocated, we never free it.\n\nFix this by introducing and calling a new `symdiff_release()` function\nthat does this for us.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/diff.c                       | 10 +++++++++-\n t/t4068-diff-symmetric-merge-base.sh |  1 +\n t/t4108-apply-threeway.sh            |  1 +\n 3 files changed, 11 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/diff.c b/builtin/diff.c\nindex 9b6cdabe15..f87f68a5bc 100644\n--- a/builtin/diff.c\n+++ b/builtin/diff.c\n@@ -388,6 +388,13 @@ static void symdiff_prepare(struct rev_info *rev, struct symdiff *sym)\n \tsym->skip = map;\n }\n \n+static void symdiff_release(struct symdiff *sdiff)\n+{\n+\tif (!sdiff)\n+\t\treturn;\n+\tbitmap_free(sdiff->skip);\n+}\n+\n int cmd_diff(int argc, const char **argv, const char *prefix)\n {\n \tint i;\n@@ -398,7 +405,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)\n \tstruct object_array_entry *blob[2];\n \tint nongit = 0, no_index = 0;\n \tint result;\n-\tstruct symdiff sdiff;\n+\tstruct symdiff sdiff = {0};\n \n \t/*\n \t * We could get N tree-ish in the rev.pending_objects list.\n@@ -619,6 +626,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)\n \t\trefresh_index_quietly();\n \trelease_revisions(&rev);\n \tobject_array_clear(&ent);\n+\tsymdiff_release(&sdiff);\n \tUNLEAK(blob);\n \treturn result;\n }\ndiff --git a/t/t4068-diff-symmetric-merge-base.sh b/t/t4068-diff-symmetric-merge-base.sh\nindex eff63c16b0..4d6565e728 100755\n--- a/t/t4068-diff-symmetric-merge-base.sh\n+++ b/t/t4068-diff-symmetric-merge-base.sh\n@@ -5,6 +5,7 @@ test_description='behavior of diff with symmetric-diff setups and --merge-base'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # build these situations:\ndiff --git a/t/t4108-apply-threeway.sh b/t/t4108-apply-threeway.sh\nindex c558282bc0..3211e1e65f 100755\n--- a/t/t4108-apply-threeway.sh\n+++ b/t/t4108-apply-threeway.sh\n@@ -5,6 +5,7 @@ test_description='git apply --3way'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n print_sanitized_conflicted_diff () {\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500798","messageId":"xmqqh6bo8l73.fsf@gitster.g","threadId":"61910","inReplyTo":"Zrr9oxgg3w8JyUpw@tanuki","subject":"Re: [PATCH v2 02/22] git: fix leaking system paths","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-08-13T16:02:56Z","receivedAt":"2024-08-13T16:03:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n>> Makes sense, though I wonder if this would be slightly cleaner to write\n>> like so (applies on top of this patch):\n>\n> It is cleaner indeed, thanks for the proposal!\n\nYup, it is only 3 repetitions but they are repetitions nevertheless.\nA small helper function that gets rid of them is worth it.\n\nThanks.\n"},{"id":"500799","messageId":"xmqq1q2s8k56.fsf@gitster.g","threadId":"61910","inReplyTo":"31e38ba4e150c9bc9e3aa1073869881ccba9035e.1723540931.git.ps@pks.im","subject":"Re: [PATCH v3 22/22] builtin/diff: free symmetric diff members","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-08-13T16:25:41Z","receivedAt":"2024-08-13T16:25:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> We populate a `struct symdiff` in case the user has requested a\n> symmetric diff. Part of this is to populate a `skip` bitmap that\n> indicates which commits shall be ignored in the diff. But while this\n> bitmap is dynamically allocated, we never free it.\n>\n> Fix this by introducing and calling a new `symdiff_release()` function\n> that does this for us.\n\nOK.\n\n> +static void symdiff_release(struct symdiff *sdiff)\n> +{\n> +\tif (!sdiff)\n> +\t\treturn;\n> +\tbitmap_free(sdiff->skip);\n> +}\n\nHmph, wouldn't it be a BUG if any caller feeds a NULL pointer to it,\nthough?  When symdiff was prepared but not used, sdiff->skip will be\nNULL but sdiff is never NULL even in such a case.\n\n> @@ -398,7 +405,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)\n>  \tstruct object_array_entry *blob[2];\n>  \tint nongit = 0, no_index = 0;\n>  \tint result;\n> -\tstruct symdiff sdiff;\n> +\tstruct symdiff sdiff = {0};\n\nAnd symdiff_prepare() at least clears its .skip member to NULL, so\nthis pre-initialization is probably not needed.  If we are preparing\nourselves for future changes of the flow in this function (e.g.\ngoto's that jump to the clean-up label from which symdiff_release()\nis always called, even when we did not call symdiff_prepare() on\nthis thing), this is probably not sufficient to convey that\nintention (instead I'd use an explicit \".skip = NULL\" to say \"we\nmight not even call _prepare() but this one is prepared to be passed\nto _release() even in such a case\").\n\nGiven that there is no such goto exists, and that _prepare() always\nsets up the .skip member appropriately, I wonder if we are much\nbetter off leaving sdiff uninitialized at the declaration site here.\nIf we add such a goto that bypasses _prepare() in the future, the\ncompiler will notice that we are passing an uninitialized sdiff to\n_release(), no?\n\n> @@ -619,6 +626,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)\n>  \t\trefresh_index_quietly();\n>  \trelease_revisions(&rev);\n>  \tobject_array_clear(&ent);\n> +\tsymdiff_release(&sdiff);\n>  \tUNLEAK(blob);\n>  \treturn result;\n>  }\n\nOther than that, this looks cleanly done.  Thanks.\n"},{"id":"500800","messageId":"xmqqv80475b2.fsf@gitster.g","threadId":"61910","inReplyTo":"d5cb4ad580c7370a4ee48d2f5075a1764972c353.1723540931.git.ps@pks.im","subject":"Re: [PATCH v3 21/22] diff: free state populated via options","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-08-13T16:31:29Z","receivedAt":"2024-08-13T16:31:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The `objfind` and `anchors` members of `struct diff_options` are\n> populated via option parsing, but are never freed in `diff_free()`. Fix\n> this to plug those memory leaks.\n\nThanks.\n\nEven though \"diff\" is generally my bailiwick, I've never paid\nattention to these features (hence the resources they consume and\nleak).  The patch looks good to me.\n\n"},{"id":"500801","messageId":"xmqqplqc755r.fsf@gitster.g","threadId":"61910","inReplyTo":"9591fb7b5e1dac2f989bd10ef2c13a191571a060.1723540931.git.ps@pks.im","subject":"Re: [PATCH v3 12/22] builtin/fast-export: fix leaking diff options","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-08-13T16:34:40Z","receivedAt":"2024-08-13T16:34:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Before calling `handle_commit()` in a loop, we set `diffopt.no_free`\n> such that its contents aren't getting freed inside of `handle_commit()`.\n> We never unset that flag though, which means that it'll ultimately leak\n> when calling `release_revisions()`.\n>\n> Fix this by unsetting the flag after the loop.\n\nIf I grep for \n\n    $ git grep -nH -E -e '(\\.|->)no_free' \\*.c\n\nI notice that in a lot of places there is a pattern of doing\n\n    set .no_free to 1\n    cause a bunch of diff using the same set of options\n    set .no_free to 0\n    call diff_free().\n\nI am curious why we do not need any diff_free() here?\n\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/fast-export.c | 2 ++\n>  1 file changed, 2 insertions(+)\n>\n> diff --git a/builtin/fast-export.c b/builtin/fast-export.c\n> index 4b6e8c6832..fe92d2436c 100644\n> --- a/builtin/fast-export.c\n> +++ b/builtin/fast-export.c\n> @@ -1278,9 +1278,11 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)\n>  \trevs.diffopt.format_callback = show_filemodify;\n>  \trevs.diffopt.format_callback_data = &paths_of_changed_objects;\n>  \trevs.diffopt.flags.recursive = 1;\n> +\n>  \trevs.diffopt.no_free = 1;\n>  \twhile ((commit = get_revision(&revs)))\n>  \t\thandle_commit(commit, &revs, &paths_of_changed_objects);\n> +\trevs.diffopt.no_free = 0;\n>  \n>  \thandle_tags_and_duplicates(&extra_refs);\n>  \thandle_tags_and_duplicates(&tag_refs);\n"},{"id":"500802","messageId":"xmqqed6s747l.fsf@gitster.g","threadId":"61910","inReplyTo":"c048b54a2c493658b2dd256b301491a79cfa99a1.1723540931.git.ps@pks.im","subject":"Re: [PATCH v3 18/22] builtin/format-patch: fix various trivial memory leaks","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-08-13T16:55:10Z","receivedAt":"2024-08-13T16:55:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> There are various memory leaks hit by git-format-patch(1). Basically all\n> of them are trivial, except that un-setting `diffopt.no_free` requires\n> us to unset the `diffopt.file` because we manually close it already.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/log.c           | 12 +++++++++---\n>  t/t4014-format-patch.sh |  1 +\n>  2 files changed, 10 insertions(+), 3 deletions(-)\n>\n> diff --git a/builtin/log.c b/builtin/log.c\n> index a73a767606..ff997a0d0e 100644\n> --- a/builtin/log.c\n> +++ b/builtin/log.c\n> @@ -1833,6 +1833,7 @@ static struct commit *get_base_commit(const struct format_config *cfg,\n>  \t\t\t}\n>  \n>  \t\t\trev[i] = merge_base->item;\n> +\t\t\tfree_commit_list(merge_base);\n>  \t\t}\n>  \n>  \t\tif (rev_nr % 2)\n\nThis is correct, but isn't merge_base leaking when there are\nmultiple and we are not dying on failure?  Perhaps something along\nthis line?\n\n\t\t\tstruct commit_list *merge_base = NULL;\n\t\t\tif (repo_get_merge_bases(the_repository,\n\t\t\t\t\t\t rev[2 * i],\n\t\t\t\t\t\t rev[2 * i + 1], &merge_base) < 0 ||\n\t\t\t    !merge_base || merge_base->next) {\n\t\t\t\tif (die_on_failure) {\n\t\t\t\t\tdie(_(\"failed to find exact merge base\"));\n\t\t\t\t} else {\n                 +               \tfree_commit_list(merge_base);\n\t\t\t\t\tfree(rev);\n\t\t\t\t\treturn NULL;\n\t\t\t\t}\n\t\t\t}\n\n> @@ -2548,12 +2550,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n>  \t\t\telse\n>  \t\t\t\tprint_signature(signature, rev.diffopt.file);\n>  \t\t}\n> -\t\tif (output_directory)\n> +\t\tif (output_directory) {\n>  \t\t\tfclose(rev.diffopt.file);\n> +\t\t\trev.diffopt.file = NULL;\n\nIs this a leakfix, or just a general code hygiene improvement?\n\n> +\t\t}\n>  \t}\n>  \tstop_progress(&progress);\n>  \tfree(list);\n> -\tfree(branch_name);\n>  \tif (ignore_if_in_upstream)\n>  \t\tfree_patch_ids(&ids);\n\nGood eyes. branch_name can be set and then \"goto done\" can jump this\none over, so it makes sense to move it below and make it part of the\ncentralized clean-up.  list is not leaking in the current code, and\nthere is no \"goto done\" or \"return\" after it gets allocated before\nthis point, so it can stay here.  On the other hand, it appears to\nme that everything below stop_progress() we see above can be moved\nbelow the \"done:\" label, except for that ids may still be left\nuninitialized without getting populated by get_patch_ids() when\nignore-if-in-upstream is asked but head and upstream are the same\nwhen we jump to the \"done:\" label, so it needs a bit more work _if_\nwe wanted to go that route.  I think the postimage of this patch,\ni.e.  freeing the \"list\" and \"ids\" here before the \"done:\" label, is\na good place to stop.\n\nThanks.\n"},{"id":"500803","messageId":"xmqqa5hg746o.fsf@gitster.g","threadId":"61910","inReplyTo":"c048b54a2c493658b2dd256b301491a79cfa99a1.1723540931.git.ps@pks.im","subject":"Re: [PATCH v3 18/22] builtin/format-patch: fix various trivial memory leaks","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-08-13T16:55:43Z","receivedAt":"2024-08-13T16:55:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> There are various memory leaks hit by git-format-patch(1). Basically all\n> of them are trivial, except that un-setting `diffopt.no_free` requires\n> us to unset the `diffopt.file` because we manually close it already.\n\nAh, I misread the patch.  Clearly diffopt.file is about\ndouble-closing.  Makes sense.\n"},{"id":"500804","messageId":"xmqq5xs4741h.fsf@gitster.g","threadId":"61910","inReplyTo":"cover.1723540931.git.ps@pks.im","subject":"Re: [PATCH v3 00/22] Memory leak fixes (pt.4)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-08-13T16:58:50Z","receivedAt":"2024-08-13T16:58:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Patrick Steinhardt (22):\n>   remote: plug memory leak when aliasing URLs\n>   git: fix leaking system paths\n>   object-file: fix memory leak when reading corrupted headers\n>   object-name: fix leaking symlink paths in object context\n>   bulk-checkin: fix leaking state TODO\n>   read-cache: fix leaking hashfile when writing index fails\n>   submodule-config: fix leaking name entry when traversing submodules\n>   config: fix leaking comment character config\n>   builtin/rebase: fix leaking `commit.gpgsign` value\n>   builtin/notes: fix leaking `struct notes_tree` when merging notes\n>   builtin/fast-import: plug trivial memory leaks\n>   builtin/fast-export: fix leaking diff options\n>   builtin/fast-export: plug leaking tag names\n>   merge-ort: unconditionally release attributes index\n>   sequencer: release todo list on error paths\n>   unpack-trees: clear index when not propagating it\n>   diff: fix leak when parsing invalid ignore regex option\n>   builtin/format-patch: fix various trivial memory leaks\n>   userdiff: fix leaking memory for configured diff drivers\n>   builtin/log: fix leak when showing converted blob contents\n>   diff: free state populated via options\n>   builtin/diff: free symmetric diff members\n\nThanks for a pleasant read.  I had a few minor comments but they\nlikely show more of my misreading of the patches than real problems\n;-)\n\n"},{"id":"500837","messageId":"Zrw3Z-HyrwynS11X@tanuki","threadId":"61910","inReplyTo":"xmqqplqc755r.fsf@gitster.g","subject":"Re: [PATCH v3 12/22] builtin/fast-export: fix leaking diff options","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-14T04:49:52Z","receivedAt":"2024-08-14T04:49:58Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Aug 13, 2024 at 09:34:40AM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > Before calling `handle_commit()` in a loop, we set `diffopt.no_free`\n> > such that its contents aren't getting freed inside of `handle_commit()`.\n> > We never unset that flag though, which means that it'll ultimately leak\n> > when calling `release_revisions()`.\n> >\n> > Fix this by unsetting the flag after the loop.\n> \n> If I grep for \n> \n>     $ git grep -nH -E -e '(\\.|->)no_free' \\*.c\n> \n> I notice that in a lot of places there is a pattern of doing\n> \n>     set .no_free to 1\n>     cause a bunch of diff using the same set of options\n>     set .no_free to 0\n>     call diff_free().\n> \n> I am curious why we do not need any diff_free() here?\n\nBecause it's already being called via `release_revisions()`.\n\nPatrick\n"},{"id":"500838","messageId":"Zrw47_cAtpVZ0Inh@tanuki","threadId":"61910","inReplyTo":"xmqqed6s747l.fsf@gitster.g","subject":"Re: [PATCH v3 18/22] builtin/format-patch: fix various trivial memory leaks","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-14T04:56:25Z","receivedAt":"2024-08-14T04:56:29Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Aug 13, 2024 at 09:55:10AM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > There are various memory leaks hit by git-format-patch(1). Basically all\n> > of them are trivial, except that un-setting `diffopt.no_free` requires\n> > us to unset the `diffopt.file` because we manually close it already.\n> >\n> > Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> > ---\n> >  builtin/log.c           | 12 +++++++++---\n> >  t/t4014-format-patch.sh |  1 +\n> >  2 files changed, 10 insertions(+), 3 deletions(-)\n> >\n> > diff --git a/builtin/log.c b/builtin/log.c\n> > index a73a767606..ff997a0d0e 100644\n> > --- a/builtin/log.c\n> > +++ b/builtin/log.c\n> > @@ -1833,6 +1833,7 @@ static struct commit *get_base_commit(const struct format_config *cfg,\n> >  \t\t\t}\n> >  \n> >  \t\t\trev[i] = merge_base->item;\n> > +\t\t\tfree_commit_list(merge_base);\n> >  \t\t}\n> >  \n> >  \t\tif (rev_nr % 2)\n> \n> This is correct, but isn't merge_base leaking when there are\n> multiple and we are not dying on failure?  Perhaps something along\n> this line?\n\nYes, good catch.\n\n> \t\t\tstruct commit_list *merge_base = NULL;\n> \t\t\tif (repo_get_merge_bases(the_repository,\n> \t\t\t\t\t\t rev[2 * i],\n> \t\t\t\t\t\t rev[2 * i + 1], &merge_base) < 0 ||\n> \t\t\t    !merge_base || merge_base->next) {\n> \t\t\t\tif (die_on_failure) {\n> \t\t\t\t\tdie(_(\"failed to find exact merge base\"));\n> \t\t\t\t} else {\n>                  +               \tfree_commit_list(merge_base);\n> \t\t\t\t\tfree(rev);\n> \t\t\t\t\treturn NULL;\n> \t\t\t\t}\n> \t\t\t}\n> \n> > @@ -2548,12 +2550,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n> >  \t\t\telse\n> >  \t\t\t\tprint_signature(signature, rev.diffopt.file);\n> >  \t\t}\n> > -\t\tif (output_directory)\n> > +\t\tif (output_directory) {\n> >  \t\t\tfclose(rev.diffopt.file);\n> > +\t\t\trev.diffopt.file = NULL;\n> \n> Is this a leakfix, or just a general code hygiene improvement?\n\nNot a leak fix, but required because of the leak fix. As we now unset\n`rev.diffopt.no_free`, `release_revisions()` will call `diff_free()` and\ntry to close the file pointer. But as we already did, it would cause a\nsegfault as we now try to close it twice.\n\nPatrick\n"},{"id":"500841","messageId":"Zrw6M7wvPFfzv3fO@tanuki","threadId":"61910","inReplyTo":"xmqq1q2s8k56.fsf@gitster.g","subject":"Re: [PATCH v3 22/22] builtin/diff: free symmetric diff members","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-14T05:01:48Z","receivedAt":"2024-08-14T05:01:54Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Aug 13, 2024 at 09:25:41AM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > We populate a `struct symdiff` in case the user has requested a\n> > symmetric diff. Part of this is to populate a `skip` bitmap that\n> > indicates which commits shall be ignored in the diff. But while this\n> > bitmap is dynamically allocated, we never free it.\n> >\n> > Fix this by introducing and calling a new `symdiff_release()` function\n> > that does this for us.\n> \n> OK.\n> \n> > +static void symdiff_release(struct symdiff *sdiff)\n> > +{\n> > +\tif (!sdiff)\n> > +\t\treturn;\n> > +\tbitmap_free(sdiff->skip);\n> > +}\n> \n> Hmph, wouldn't it be a BUG if any caller feeds a NULL pointer to it,\n> though?  When symdiff was prepared but not used, sdiff->skip will be\n> NULL but sdiff is never NULL even in such a case.\n\nGood point. It does make sense for `_free()` functions to handle NULL\npointers, but doesn't quite for `_release()` ones.\n\n> > @@ -398,7 +405,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)\n> >  \tstruct object_array_entry *blob[2];\n> >  \tint nongit = 0, no_index = 0;\n> >  \tint result;\n> > -\tstruct symdiff sdiff;\n> > +\tstruct symdiff sdiff = {0};\n> \n> And symdiff_prepare() at least clears its .skip member to NULL, so\n> this pre-initialization is probably not needed.  If we are preparing\n> ourselves for future changes of the flow in this function (e.g.\n> goto's that jump to the clean-up label from which symdiff_release()\n> is always called, even when we did not call symdiff_prepare() on\n> this thing), this is probably not sufficient to convey that\n> intention (instead I'd use an explicit \".skip = NULL\" to say \"we\n> might not even call _prepare() but this one is prepared to be passed\n> to _release() even in such a case\").\n> \n> Given that there is no such goto exists, and that _prepare() always\n> sets up the .skip member appropriately, I wonder if we are much\n> better off leaving sdiff uninitialized at the declaration site here.\n> If we add such a goto that bypasses _prepare() in the future, the\n> compiler will notice that we are passing an uninitialized sdiff to\n> _release(), no?\n\nYou'd hope it does, but it certainly depends on your compiler flags.\nVarious hardening flags for example implicitly initialize variables, and\nI have a feeling that this also causes them to not emit any warnings\nanymore. At least I only spot such warnings in CI.\n\nIn any case, yes, we can drop the initialization here.\n\nPatrick\n"},{"id":"500844","messageId":"cover.1723614263.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1722933642.git.ps@pks.im","subject":"[PATCH v4 00/22] Memory leak fixes (pt.4)","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-14T06:51:47Z","receivedAt":"2024-08-14T06:51:54Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis is the fourth version of my fourth set of memory leak fixes. There\nare only minor changes compared to v3:\n\n  - Amend a commit message to explain that `release_revisions()` takes\n    care of releasing `rev.diffopt` for us.\n\n  - Fix another memory leak in `get_base_commit()`.\n\n  - Remove `NULL` pointer check in `symdiff_release()` and stop\n    zero-initializing `struct symdiff`.\n\nThanks!\n\nPatrick\n\nPatrick Steinhardt (22):\n  remote: plug memory leak when aliasing URLs\n  git: fix leaking system paths\n  object-file: fix memory leak when reading corrupted headers\n  object-name: fix leaking symlink paths in object context\n  bulk-checkin: fix leaking state TODO\n  read-cache: fix leaking hashfile when writing index fails\n  submodule-config: fix leaking name entry when traversing submodules\n  config: fix leaking comment character config\n  builtin/rebase: fix leaking `commit.gpgsign` value\n  builtin/notes: fix leaking `struct notes_tree` when merging notes\n  builtin/fast-import: plug trivial memory leaks\n  builtin/fast-export: fix leaking diff options\n  builtin/fast-export: plug leaking tag names\n  merge-ort: unconditionally release attributes index\n  sequencer: release todo list on error paths\n  unpack-trees: clear index when not propagating it\n  diff: fix leak when parsing invalid ignore regex option\n  builtin/format-patch: fix various trivial memory leaks\n  userdiff: fix leaking memory for configured diff drivers\n  builtin/log: fix leak when showing converted blob contents\n  diff: free state populated via options\n  builtin/diff: free symmetric diff members\n\n builtin/commit.c                      |  7 +-\n builtin/diff.c                        |  6 ++\n builtin/fast-export.c                 | 19 ++++--\n builtin/fast-import.c                 |  8 ++-\n builtin/log.c                         | 14 +++-\n builtin/notes.c                       |  9 ++-\n builtin/rebase.c                      |  1 +\n bulk-checkin.c                        |  2 +\n config.c                              |  3 +-\n csum-file.c                           |  2 +-\n csum-file.h                           | 10 +++\n diff.c                                | 16 ++++-\n environment.c                         |  1 +\n environment.h                         |  1 +\n git.c                                 | 13 +++-\n merge-ort.c                           |  3 +-\n object-file.c                         |  1 +\n object-name.c                         |  1 +\n range-diff.c                          |  6 +-\n read-cache.c                          | 97 ++++++++++++++++-----------\n remote.c                              |  2 +\n sequencer.c                           | 67 ++++++++++++------\n submodule-config.c                    | 18 +++--\n t/t0210-trace2-normal.sh              |  2 +-\n t/t1006-cat-file.sh                   |  1 +\n t/t1050-large.sh                      |  1 +\n t/t1450-fsck.sh                       |  1 +\n t/t1601-index-bogus.sh                |  2 +\n t/t2107-update-index-basic.sh         |  1 +\n t/t3310-notes-merge-manual-resolve.sh |  1 +\n t/t3311-notes-merge-fanout.sh         |  1 +\n t/t3404-rebase-interactive.sh         |  1 +\n t/t3435-rebase-gpg-sign.sh            |  1 +\n t/t3507-cherry-pick-conflict.sh       |  1 +\n t/t3510-cherry-pick-sequence.sh       |  1 +\n t/t3705-add-sparse-checkout.sh        |  1 +\n t/t4013-diff-various.sh               |  1 +\n t/t4014-format-patch.sh               |  1 +\n t/t4018-diff-funcname.sh              |  1 +\n t/t4030-diff-textconv.sh              |  2 +\n t/t4042-diff-textconv-caching.sh      |  2 +\n t/t4048-diff-combined-binary.sh       |  1 +\n t/t4064-diff-oidfind.sh               |  2 +\n t/t4065-diff-anchored.sh              |  1 +\n t/t4068-diff-symmetric-merge-base.sh  |  1 +\n t/t4069-remerge-diff.sh               |  1 +\n t/t4108-apply-threeway.sh             |  1 +\n t/t4209-log-pickaxe.sh                |  2 +\n t/t6421-merge-partial-clone.sh        |  1 +\n t/t6428-merge-conflicts-sparse.sh     |  1 +\n t/t7008-filter-branch-null-sha1.sh    |  1 +\n t/t7030-verify-tag.sh                 |  1 +\n t/t7817-grep-sparse-checkout.sh       |  1 +\n t/t9300-fast-import.sh                |  1 +\n t/t9304-fast-import-marks.sh          |  2 +\n t/t9351-fast-export-anonymize.sh      |  1 +\n unpack-trees.c                        |  2 +\n userdiff.c                            | 38 ++++++++---\n userdiff.h                            |  4 ++\n 59 files changed, 286 insertions(+), 105 deletions(-)\n\nRange-diff against v3:\n 1:  02f6da020f =  1:  02f6da020f remote: plug memory leak when aliasing URLs\n 2:  f36d895948 =  2:  f36d895948 git: fix leaking system paths\n 3:  0415ac986d =  3:  0415ac986d object-file: fix memory leak when reading corrupted headers\n 4:  e5130e50a9 =  4:  e5130e50a9 object-name: fix leaking symlink paths in object context\n 5:  276c828ad1 =  5:  276c828ad1 bulk-checkin: fix leaking state TODO\n 6:  ed0608e705 =  6:  ed0608e705 read-cache: fix leaking hashfile when writing index fails\n 7:  b7a7f88c7d =  7:  b7a7f88c7d submodule-config: fix leaking name entry when traversing submodules\n 8:  9054a459a1 =  8:  9054a459a1 config: fix leaking comment character config\n 9:  1d3957a5eb =  9:  1d3957a5eb builtin/rebase: fix leaking `commit.gpgsign` value\n10:  0af1bab5a1 = 10:  0af1bab5a1 builtin/notes: fix leaking `struct notes_tree` when merging notes\n11:  30d4e9ed43 = 11:  30d4e9ed43 builtin/fast-import: plug trivial memory leaks\n12:  9591fb7b5e ! 12:  070813a740 builtin/fast-export: fix leaking diff options\n    @@ Commit message\n     \n         Before calling `handle_commit()` in a loop, we set `diffopt.no_free`\n         such that its contents aren't getting freed inside of `handle_commit()`.\n    -    We never unset that flag though, which means that it'll ultimately leak\n    -    when calling `release_revisions()`.\n    +    We never unset that flag though, which means that the structure's\n    +    allocated resources will ultimately leak.\n     \n    -    Fix this by unsetting the flag after the loop.\n    +    Fix this by unsetting the flag after the loop such that we release its\n    +    resources via `release_revisions()`.\n     \n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n13:  254bbb7f6f = 13:  b4096e971f builtin/fast-export: plug leaking tag names\n14:  334c4ed71a = 14:  bdfdf53313 merge-ort: unconditionally release attributes index\n15:  9f08a859fb = 15:  f6c1055805 sequencer: release todo list on error paths\n16:  5d4934b1a9 = 16:  9db41181a6 unpack-trees: clear index when not propagating it\n17:  e1b6a24fbe = 17:  85f6ffd610 diff: fix leak when parsing invalid ignore regex option\n18:  c048b54a2c ! 18:  e00aa1ef06 builtin/format-patch: fix various trivial memory leaks\n    @@ Commit message\n     \n      ## builtin/log.c ##\n     @@ builtin/log.c: static struct commit *get_base_commit(const struct format_config *cfg,\n    + \t\t\t\tif (die_on_failure) {\n    + \t\t\t\t\tdie(_(\"failed to find exact merge base\"));\n    + \t\t\t\t} else {\n    ++\t\t\t\t\tfree_commit_list(merge_base);\n    + \t\t\t\t\tfree(rev);\n    + \t\t\t\t\treturn NULL;\n    + \t\t\t\t}\n      \t\t\t}\n      \n      \t\t\trev[i] = merge_base->item;\n19:  39b2921e3e = 19:  cc04751134 userdiff: fix leaking memory for configured diff drivers\n20:  50dea1c98a = 20:  0e2d3e523f builtin/log: fix leak when showing converted blob contents\n21:  d5cb4ad580 = 21:  9faffa7a62 diff: free state populated via options\n22:  31e38ba4e1 ! 22:  ee252e752c builtin/diff: free symmetric diff members\n    @@ builtin/diff.c: static void symdiff_prepare(struct rev_info *rev, struct symdiff\n      \n     +static void symdiff_release(struct symdiff *sdiff)\n     +{\n    -+\tif (!sdiff)\n    -+\t\treturn;\n     +\tbitmap_free(sdiff->skip);\n     +}\n     +\n      int cmd_diff(int argc, const char **argv, const char *prefix)\n      {\n      \tint i;\n    -@@ builtin/diff.c: int cmd_diff(int argc, const char **argv, const char *prefix)\n    - \tstruct object_array_entry *blob[2];\n    - \tint nongit = 0, no_index = 0;\n    - \tint result;\n    --\tstruct symdiff sdiff;\n    -+\tstruct symdiff sdiff = {0};\n    - \n    - \t/*\n    - \t * We could get N tree-ish in the rev.pending_objects list.\n     @@ builtin/diff.c: int cmd_diff(int argc, const char **argv, const char *prefix)\n      \t\trefresh_index_quietly();\n      \trelease_revisions(&rev);\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500845","messageId":"02f6da020f083b3cedcdff192595438ecf82bf56.1723614263.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723614263.git.ps@pks.im","subject":"[PATCH v4 01/22] remote: plug memory leak when aliasing URLs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-14T06:51:52Z","receivedAt":"2024-08-14T06:51:56Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When we have a `url.*.insteadOf` configuration, then we end up aliasing\nURLs when populating remotes. One place where this happens is in\n`alias_all_urls()`, where we loop through all remotes and then alias\neach of their URLs. The actual aliasing logic is then contained in\n`alias_url()`, which returns an allocated string that contains the new\nURL. This URL replaces the old URL that we have in the strvec that\ncontains all remote URLs.\n\nWe replace the remote URLs via `strvec_replace()`, which does not hand\nover ownership of the new string to the vector. Still, we didn't free\nthe aliased URL and thus have a memory leak here. Fix it by freeing the\naliased string.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n remote.c                 | 2 ++\n t/t0210-trace2-normal.sh | 2 +-\n 2 files changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/remote.c b/remote.c\nindex f43cf5e7a4..3b898edd23 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -499,6 +499,7 @@ static void alias_all_urls(struct remote_state *remote_state)\n \t\t\tif (alias)\n \t\t\t\tstrvec_replace(&remote_state->remotes[i]->pushurl,\n \t\t\t\t\t       j, alias);\n+\t\t\tfree(alias);\n \t\t}\n \t\tadd_pushurl_aliases = remote_state->remotes[i]->pushurl.nr == 0;\n \t\tfor (j = 0; j < remote_state->remotes[i]->url.nr; j++) {\n@@ -512,6 +513,7 @@ static void alias_all_urls(struct remote_state *remote_state)\n \t\t\tif (alias)\n \t\t\t\tstrvec_replace(&remote_state->remotes[i]->url,\n \t\t\t\t\t       j, alias);\n+\t\t\tfree(alias);\n \t\t}\n \t}\n }\ndiff --git a/t/t0210-trace2-normal.sh b/t/t0210-trace2-normal.sh\nindex c312657a12..b9adc94aab 100755\n--- a/t/t0210-trace2-normal.sh\n+++ b/t/t0210-trace2-normal.sh\n@@ -2,7 +2,7 @@\n \n test_description='test trace2 facility (normal target)'\n \n-TEST_PASSES_SANITIZE_LEAK=false\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # Turn off any inherited trace2 settings for this test.\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500846","messageId":"f36d895948a2b26148062a3de2997fb6d23301d3.1723614263.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723614263.git.ps@pks.im","subject":"[PATCH v4 02/22] git: fix leaking system paths","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-14T06:51:55Z","receivedAt":"2024-08-14T06:51:59Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Git has some flags to make it output system paths as they have been\ncompiled into Git. This is done by calling `system_path()`, which\nreturns an allocated string. This string isn't ever free'd though,\ncreating a memory leak.\n\nPlug those leaks. While they are surfaced by t0211, there are more\nmemory leaks looming exposed by that test suite and it thus does not yet\npass with the memory leak checker enabled.\n\nHelped-by: Taylor Blau <me@ttaylorr.com>\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n git.c | 13 ++++++++++---\n 1 file changed, 10 insertions(+), 3 deletions(-)\n\ndiff --git a/git.c b/git.c\nindex e35af9b0e5..9a618a2740 100644\n--- a/git.c\n+++ b/git.c\n@@ -143,6 +143,13 @@ void setup_auto_pager(const char *cmd, int def)\n \tcommit_pager_choice();\n }\n \n+static void print_system_path(const char *path)\n+{\n+\tchar *s_path = system_path(path);\n+\tputs(s_path);\n+\tfree(s_path);\n+}\n+\n static int handle_options(const char ***argv, int *argc, int *envchanged)\n {\n \tconst char **orig_argv = *argv;\n@@ -173,15 +180,15 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\t\texit(0);\n \t\t\t}\n \t\t} else if (!strcmp(cmd, \"--html-path\")) {\n-\t\t\tputs(system_path(GIT_HTML_PATH));\n+\t\t\tprint_system_path(GIT_HTML_PATH);\n \t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"--man-path\")) {\n-\t\t\tputs(system_path(GIT_MAN_PATH));\n+\t\t\tprint_system_path(GIT_MAN_PATH);\n \t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"--info-path\")) {\n-\t\t\tputs(system_path(GIT_INFO_PATH));\n+\t\t\tprint_system_path(GIT_INFO_PATH);\n \t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"-p\") || !strcmp(cmd, \"--paginate\")) {\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500847","messageId":"0415ac986dc8c04917502a1bb0b03afcca47ec67.1723614263.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723614263.git.ps@pks.im","subject":"[PATCH v4 03/22] object-file: fix memory leak when reading corrupted headers","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-14T06:51:58Z","receivedAt":"2024-08-14T06:52:01Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When reading corrupt object headers in `read_loose_object()`, we bail\nout immediately. This causes a memory leak though because we would have\nalready initialized the zstream in `unpack_loose_header()`, and it is\nthe callers responsibility to finish the zstream even on error. While\nthis feels weird, other callsites do it correctly already.\n\nFix this leak by ending the zstream even on errors. We may want to\nrevisit this interface in the future such that the callee handles this\nfor us already when there was an error.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c   | 1 +\n t/t1450-fsck.sh | 1 +\n 2 files changed, 2 insertions(+)\n\ndiff --git a/object-file.c b/object-file.c\nindex 065103be3e..7c65c435cd 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -2954,6 +2954,7 @@ int read_loose_object(const char *path,\n \tif (unpack_loose_header(&stream, map, mapsize, hdr, sizeof(hdr),\n \t\t\t\tNULL) != ULHR_OK) {\n \t\terror(_(\"unable to unpack header of %s\"), path);\n+\t\tgit_inflate_end(&stream);\n \t\tgoto out;\n \t}\n \ndiff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh\nindex 8a456b1142..280cbf3e03 100755\n--- a/t/t1450-fsck.sh\n+++ b/t/t1450-fsck.sh\n@@ -6,6 +6,7 @@ test_description='git fsck random collection of tests\n * (main) A\n '\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success setup '\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500848","messageId":"e5130e50a9f296720960b2a8d7be14fc01a0fd30.1723614263.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723614263.git.ps@pks.im","subject":"[PATCH v4 04/22] object-name: fix leaking symlink paths in object context","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-14T06:52:00Z","receivedAt":"2024-08-14T06:52:04Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The object context may be populated with symlink contents when reading a\nsymlink, but the associated strbuf doesn't ever get released when\nreleasing the object context, causing a memory leak. Plug it.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-name.c       | 1 +\n t/t1006-cat-file.sh | 1 +\n 2 files changed, 2 insertions(+)\n\ndiff --git a/object-name.c b/object-name.c\nindex 240a93e7ce..e39fa50e47 100644\n--- a/object-name.c\n+++ b/object-name.c\n@@ -1765,6 +1765,7 @@ int strbuf_check_branch_ref(struct strbuf *sb, const char *name)\n void object_context_release(struct object_context *ctx)\n {\n \tfree(ctx->path);\n+\tstrbuf_release(&ctx->symlink_path);\n }\n \n /*\ndiff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\nindex ff9bf213aa..d36cd7c086 100755\n--- a/t/t1006-cat-file.sh\n+++ b/t/t1006-cat-file.sh\n@@ -2,6 +2,7 @@\n \n test_description='git cat-file'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_cmdmode_usage () {\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500849","messageId":"276c828ad18c422c7cca0503e336c8aee8aeb149.1723614263.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723614263.git.ps@pks.im","subject":"[PATCH v4 05/22] bulk-checkin: fix leaking state TODO","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-14T06:52:03Z","receivedAt":"2024-08-14T06:52:06Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When flushing a bulk-checking to disk we also reset the `struct\nbulk_checkin_packfile` state. But while we free some of its members,\nothers aren't being free'd, leading to memory leaks:\n\n  - The temporary packfile name is not getting freed.\n\n  - The `struct hashfile` only gets freed in case we end up calling\n    `finalize_hashfile()`. There are code paths though where that is not\n    the case, namely when nothing has been written. For this, we need to\n    make `free_hashfile()` public.\n\nFix those leaks.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n bulk-checkin.c   |  2 ++\n csum-file.c      |  2 +-\n csum-file.h      | 10 ++++++++++\n t/t1050-large.sh |  1 +\n 4 files changed, 14 insertions(+), 1 deletion(-)\n\ndiff --git a/bulk-checkin.c b/bulk-checkin.c\nindex da8673199b..9089c214fa 100644\n--- a/bulk-checkin.c\n+++ b/bulk-checkin.c\n@@ -61,6 +61,7 @@ static void flush_bulk_checkin_packfile(struct bulk_checkin_packfile *state)\n \n \tif (state->nr_written == 0) {\n \t\tclose(state->f->fd);\n+\t\tfree_hashfile(state->f);\n \t\tunlink(state->pack_tmp_name);\n \t\tgoto clear_exit;\n \t} else if (state->nr_written == 1) {\n@@ -83,6 +84,7 @@ static void flush_bulk_checkin_packfile(struct bulk_checkin_packfile *state)\n \t\tfree(state->written[i]);\n \n clear_exit:\n+\tfree(state->pack_tmp_name);\n \tfree(state->written);\n \tmemset(state, 0, sizeof(*state));\n \ndiff --git a/csum-file.c b/csum-file.c\nindex 8abbf01325..7e0ece1305 100644\n--- a/csum-file.c\n+++ b/csum-file.c\n@@ -56,7 +56,7 @@ void hashflush(struct hashfile *f)\n \t}\n }\n \n-static void free_hashfile(struct hashfile *f)\n+void free_hashfile(struct hashfile *f)\n {\n \tfree(f->buffer);\n \tfree(f->check_buffer);\ndiff --git a/csum-file.h b/csum-file.h\nindex 566e05cbd2..ca553eba17 100644\n--- a/csum-file.h\n+++ b/csum-file.h\n@@ -46,6 +46,16 @@ int hashfile_truncate(struct hashfile *, struct hashfile_checkpoint *);\n struct hashfile *hashfd(int fd, const char *name);\n struct hashfile *hashfd_check(const char *name);\n struct hashfile *hashfd_throughput(int fd, const char *name, struct progress *tp);\n+\n+/*\n+ * Free the hashfile without flushing its contents to disk. This only\n+ * needs to be called when not calling `finalize_hashfile()`.\n+ */\n+void free_hashfile(struct hashfile *f);\n+\n+/*\n+ * Finalize the hashfile by flushing data to disk and free'ing it.\n+ */\n int finalize_hashfile(struct hashfile *, unsigned char *, enum fsync_component, unsigned int);\n void hashwrite(struct hashfile *, const void *, unsigned int);\n void hashflush(struct hashfile *f);\ndiff --git a/t/t1050-large.sh b/t/t1050-large.sh\nindex c71932b024..ed638f6644 100755\n--- a/t/t1050-large.sh\n+++ b/t/t1050-large.sh\n@@ -3,6 +3,7 @@\n \n test_description='adding and checking out large blobs'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'core.bigFileThreshold must be non-negative' '\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500850","messageId":"ed0608e7059c925e50daf5dc454ced781a09a948.1723614263.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723614263.git.ps@pks.im","subject":"[PATCH v4 06/22] read-cache: fix leaking hashfile when writing index fails","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-14T06:52:06Z","receivedAt":"2024-08-14T06:52:13Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `do_write_index()`, we use a `struct hashfile` to write the index\nwith a trailer hash. In case the write fails though, we never clean up\nthe allocated `hashfile` state and thus leak memory.\n\nRefactor the code to have a common exit path where we can free this and\nother allocated memory. While at it, refactor our use of `strbuf`s such\nthat we reuse the same buffer to avoid some unneeded allocations.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n read-cache.c                       | 97 ++++++++++++++++++------------\n t/t1601-index-bogus.sh             |  2 +\n t/t2107-update-index-basic.sh      |  1 +\n t/t7008-filter-branch-null-sha1.sh |  1 +\n 4 files changed, 62 insertions(+), 39 deletions(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex 48bf24f87c..36821fe5b5 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -2840,8 +2840,9 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \tint csum_fsync_flag;\n \tint ieot_entries = 1;\n \tstruct index_entry_offset_table *ieot = NULL;\n-\tint nr, nr_threads;\n \tstruct repository *r = istate->repo;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tint nr, nr_threads, ret;\n \n \tf = hashfd(tempfile->fd, tempfile->filename.buf);\n \n@@ -2962,8 +2963,8 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \tstrbuf_release(&previous_name_buf);\n \n \tif (err) {\n-\t\tfree(ieot);\n-\t\treturn err;\n+\t\tret = err;\n+\t\tgoto out;\n \t}\n \n \toffset = hashfile_total(f);\n@@ -2985,20 +2986,20 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \t * index.\n \t */\n \tif (ieot) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\twrite_ieot_extension(&sb, ieot);\n \t\terr = write_index_ext_header(f, eoie_c, CACHE_EXT_INDEXENTRYOFFSETTABLE, sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tfree(ieot);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \n \tif (write_extensions & WRITE_SPLIT_INDEX_EXTENSION &&\n \t    istate->split_index) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\tif (istate->sparse_index)\n \t\t\tdie(_(\"cannot write split index for a sparse index\"));\n@@ -3007,59 +3008,66 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \t\t\twrite_index_ext_header(f, eoie_c, CACHE_EXT_LINK,\n \t\t\t\t\t       sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \tif (write_extensions & WRITE_CACHE_TREE_EXTENSION &&\n \t    !drop_cache_tree && istate->cache_tree) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\tcache_tree_write(&sb, istate->cache_tree);\n \t\terr = write_index_ext_header(f, eoie_c, CACHE_EXT_TREE, sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \tif (write_extensions & WRITE_RESOLVE_UNDO_EXTENSION &&\n \t    istate->resolve_undo) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\tresolve_undo_write(&sb, istate->resolve_undo);\n \t\terr = write_index_ext_header(f, eoie_c, CACHE_EXT_RESOLVE_UNDO,\n \t\t\t\t\t     sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \tif (write_extensions & WRITE_UNTRACKED_CACHE_EXTENSION &&\n \t    istate->untracked) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\twrite_untracked_extension(&sb, istate->untracked);\n \t\terr = write_index_ext_header(f, eoie_c, CACHE_EXT_UNTRACKED,\n \t\t\t\t\t     sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \tif (write_extensions & WRITE_FSMONITOR_EXTENSION &&\n \t    istate->fsmonitor_last_update) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\twrite_fsmonitor_extension(&sb, istate);\n \t\terr = write_index_ext_header(f, eoie_c, CACHE_EXT_FSMONITOR, sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \tif (istate->sparse_index) {\n-\t\tif (write_index_ext_header(f, eoie_c, CACHE_EXT_SPARSE_DIRECTORIES, 0) < 0)\n-\t\t\treturn -1;\n+\t\tif (write_index_ext_header(f, eoie_c, CACHE_EXT_SPARSE_DIRECTORIES, 0) < 0) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \n \t/*\n@@ -3069,14 +3077,15 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \t * when loading the shared index.\n \t */\n \tif (eoie_c) {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_reset(&sb);\n \n \t\twrite_eoie_extension(&sb, eoie_c, offset);\n \t\terr = write_index_ext_header(f, NULL, CACHE_EXT_ENDOFINDEXENTRIES, sb.len) < 0;\n \t\thashwrite(f, sb.buf, sb.len);\n-\t\tstrbuf_release(&sb);\n-\t\tif (err)\n-\t\t\treturn -1;\n+\t\tif (err) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \n \tcsum_fsync_flag = 0;\n@@ -3085,13 +3094,16 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \n \tfinalize_hashfile(f, istate->oid.hash, FSYNC_COMPONENT_INDEX,\n \t\t\t  CSUM_HASH_IN_STREAM | csum_fsync_flag);\n+\tf = NULL;\n \n \tif (close_tempfile_gently(tempfile)) {\n-\t\terror(_(\"could not close '%s'\"), get_tempfile_path(tempfile));\n-\t\treturn -1;\n+\t\tret = error(_(\"could not close '%s'\"), get_tempfile_path(tempfile));\n+\t\tgoto out;\n+\t}\n+\tif (stat(get_tempfile_path(tempfile), &st)) {\n+\t\tret = -1;\n+\t\tgoto out;\n \t}\n-\tif (stat(get_tempfile_path(tempfile), &st))\n-\t\treturn -1;\n \tistate->timestamp.sec = (unsigned int)st.st_mtime;\n \tistate->timestamp.nsec = ST_MTIME_NSEC(st);\n \ttrace_performance_since(start, \"write index, changed mask = %x\", istate->cache_changed);\n@@ -3105,7 +3117,14 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \ttrace2_data_intmax(\"index\", the_repository, \"write/cache_nr\",\n \t\t\t   istate->cache_nr);\n \n-\treturn 0;\n+\tret = 0;\n+\n+out:\n+\tif (f)\n+\t\tfree_hashfile(f);\n+\tstrbuf_release(&sb);\n+\tfree(ieot);\n+\treturn ret;\n }\n \n void set_alternate_index_output(const char *name)\ndiff --git a/t/t1601-index-bogus.sh b/t/t1601-index-bogus.sh\nindex 4171f1e141..5dcc101882 100755\n--- a/t/t1601-index-bogus.sh\n+++ b/t/t1601-index-bogus.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='test handling of bogus index entries'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'create tree with null sha1' '\ndiff --git a/t/t2107-update-index-basic.sh b/t/t2107-update-index-basic.sh\nindex cc72ead79f..f0eab13f96 100755\n--- a/t/t2107-update-index-basic.sh\n+++ b/t/t2107-update-index-basic.sh\n@@ -5,6 +5,7 @@ test_description='basic update-index tests\n Tests for command-line parsing and basic operation.\n '\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'update-index --nonsense fails' '\ndiff --git a/t/t7008-filter-branch-null-sha1.sh b/t/t7008-filter-branch-null-sha1.sh\nindex 93fbc92b8d..0ce8fd2c89 100755\n--- a/t/t7008-filter-branch-null-sha1.sh\n+++ b/t/t7008-filter-branch-null-sha1.sh\n@@ -2,6 +2,7 @@\n \n test_description='filter-branch removal of trees with null sha1'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup: base commits' '\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500851","messageId":"b7a7f88c7d8abe6f0460df20d37001ec26b6d8e9.1723614263.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723614263.git.ps@pks.im","subject":"[PATCH v4 07/22] submodule-config: fix leaking name entry when traversing submodules","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-14T06:52:12Z","receivedAt":"2024-08-14T06:52:16Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We traverse through submodules in the tree via `tree_entry()`, passing\nto it a `struct name_entry` that it is supposed to populate with the\ntree entry's contents. We unnecessarily allocate this variable instead\nof passing a variable that is allocated on the stack, and the ultimately\ndon't even free that variable. This is unnecessary and leaks memory.\n\nConvert the variable to instead be allocated on the stack to plug the\nmemory leak.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n submodule-config.c | 18 ++++++++----------\n 1 file changed, 8 insertions(+), 10 deletions(-)\n\ndiff --git a/submodule-config.c b/submodule-config.c\nindex 9b0bb0b9f4..c8f2bb2bdd 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -899,27 +899,25 @@ static void traverse_tree_submodules(struct repository *r,\n {\n \tstruct tree_desc tree;\n \tstruct submodule_tree_entry *st_entry;\n-\tstruct name_entry *name_entry;\n+\tstruct name_entry name_entry;\n \tchar *tree_path = NULL;\n \n-\tname_entry = xmalloc(sizeof(*name_entry));\n-\n \tfill_tree_descriptor(r, &tree, treeish_name);\n-\twhile (tree_entry(&tree, name_entry)) {\n+\twhile (tree_entry(&tree, &name_entry)) {\n \t\tif (prefix)\n \t\t\ttree_path =\n-\t\t\t\tmkpathdup(\"%s/%s\", prefix, name_entry->path);\n+\t\t\t\tmkpathdup(\"%s/%s\", prefix, name_entry.path);\n \t\telse\n-\t\t\ttree_path = xstrdup(name_entry->path);\n+\t\t\ttree_path = xstrdup(name_entry.path);\n \n-\t\tif (S_ISGITLINK(name_entry->mode) &&\n+\t\tif (S_ISGITLINK(name_entry.mode) &&\n \t\t    is_tree_submodule_active(r, root_tree, tree_path)) {\n \t\t\tALLOC_GROW(out->entries, out->entry_nr + 1,\n \t\t\t\t   out->entry_alloc);\n \t\t\tst_entry = &out->entries[out->entry_nr++];\n \n \t\t\tst_entry->name_entry = xmalloc(sizeof(*st_entry->name_entry));\n-\t\t\t*st_entry->name_entry = *name_entry;\n+\t\t\t*st_entry->name_entry = name_entry;\n \t\t\tst_entry->submodule =\n \t\t\t\tsubmodule_from_path(r, root_tree, tree_path);\n \t\t\tst_entry->repo = xmalloc(sizeof(*st_entry->repo));\n@@ -927,9 +925,9 @@ static void traverse_tree_submodules(struct repository *r,\n \t\t\t\t\t\troot_tree))\n \t\t\t\tFREE_AND_NULL(st_entry->repo);\n \n-\t\t} else if (S_ISDIR(name_entry->mode))\n+\t\t} else if (S_ISDIR(name_entry.mode))\n \t\t\ttraverse_tree_submodules(r, root_tree, tree_path,\n-\t\t\t\t\t\t &name_entry->oid, out);\n+\t\t\t\t\t\t &name_entry.oid, out);\n \t\tfree(tree_path);\n \t}\n }\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500852","messageId":"9054a459a1419e333ae8f950b9c9c2bf894c55f5.1723614263.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723614263.git.ps@pks.im","subject":"[PATCH v4 08/22] config: fix leaking comment character config","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-14T06:52:14Z","receivedAt":"2024-08-14T06:52:18Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When the comment line character has been specified multiple times in the\nconfiguration, then `git_default_core_config()` will cause a memory leak\nbecause it unconditionally copies the string into `comment_line_str`\nwithout free'ing the previous value. In fact, it can't easily free the\nvalue in the first place because it may contain a string constant.\n\nRefactor the code such that we track allocated comment character strings\nvia a separate non-constant variable `comment_line_str_to_free`. Adapt\nsites that set `comment_line_str` to set both and free the old value\nthat was stored in `comment_line_str_to_free`.\n\nThis memory leak is being hit in t3404. As there are still other memory\nleaks in that file we cannot yet mark it as passing with leak checking\nenabled.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/commit.c | 7 +++++--\n config.c         | 3 ++-\n environment.c    | 1 +\n environment.h    | 1 +\n 4 files changed, 9 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 66427ba82d..b2033c4887 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -684,7 +684,9 @@ static void adjust_comment_line_char(const struct strbuf *sb)\n \tconst char *p;\n \n \tif (!memchr(sb->buf, candidates[0], sb->len)) {\n-\t\tcomment_line_str = xstrfmt(\"%c\", candidates[0]);\n+\t\tfree(comment_line_str_to_free);\n+\t\tcomment_line_str = comment_line_str_to_free =\n+\t\t\txstrfmt(\"%c\", candidates[0]);\n \t\treturn;\n \t}\n \n@@ -705,7 +707,8 @@ static void adjust_comment_line_char(const struct strbuf *sb)\n \tif (!*p)\n \t\tdie(_(\"unable to select a comment character that is not used\\n\"\n \t\t      \"in the current commit message\"));\n-\tcomment_line_str = xstrfmt(\"%c\", *p);\n+\tfree(comment_line_str_to_free);\n+\tcomment_line_str = comment_line_str_to_free = xstrfmt(\"%c\", *p);\n }\n \n static void prepare_amend_commit(struct commit *commit, struct strbuf *sb,\ndiff --git a/config.c b/config.c\nindex 6421894614..205660a8fb 100644\n--- a/config.c\n+++ b/config.c\n@@ -1596,7 +1596,8 @@ static int git_default_core_config(const char *var, const char *value,\n \t\telse if (value[0]) {\n \t\t\tif (strchr(value, '\\n'))\n \t\t\t\treturn error(_(\"%s cannot contain newline\"), var);\n-\t\t\tcomment_line_str = xstrdup(value);\n+\t\t\tcomment_line_str = value;\n+\t\t\tFREE_AND_NULL(comment_line_str_to_free);\n \t\t\tauto_comment_line_char = 0;\n \t\t} else\n \t\t\treturn error(_(\"%s must have at least one character\"), var);\ndiff --git a/environment.c b/environment.c\nindex 5cea2c9f54..1d6c48b52d 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -114,6 +114,7 @@ int protect_ntfs = PROTECT_NTFS_DEFAULT;\n  * that is subject to stripspace.\n  */\n const char *comment_line_str = \"#\";\n+char *comment_line_str_to_free;\n int auto_comment_line_char;\n \n /* Parallel index stat data preload? */\ndiff --git a/environment.h b/environment.h\nindex e9f01d4d11..0148738ed6 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -9,6 +9,7 @@ struct strvec;\n  * that is subject to stripspace.\n  */\n extern const char *comment_line_str;\n+extern char *comment_line_str_to_free;\n extern int auto_comment_line_char;\n \n /*\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500853","messageId":"1d3957a5ebb527d672a66f6f6226b6668924a2fa.1723614263.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723614263.git.ps@pks.im","subject":"[PATCH v4 09/22] builtin/rebase: fix leaking `commit.gpgsign` value","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-14T06:52:17Z","receivedAt":"2024-08-14T06:52:21Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `get_replay_opts()`, we override the `gpg_sign` field that already\ngot populated by `sequencer_init_config()` in case the user has\n\"commit.gpgsign\" set in their config. This creates a memory leak because\nwe overwrite the previously assigned value, which may have already\npointed to an allocated string.\n\nLet's plug the memory leak by freeing the value before we overwrite it.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/rebase.c              | 1 +\n sequencer.c                   | 1 +\n t/t3404-rebase-interactive.sh | 1 +\n t/t3435-rebase-gpg-sign.sh    | 1 +\n t/t7030-verify-tag.sh         | 1 +\n 5 files changed, 5 insertions(+)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex e3a8e74cfc..2f01d5d3a6 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -186,6 +186,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n \treplay.committer_date_is_author_date =\n \t\t\t\t\topts->committer_date_is_author_date;\n \treplay.ignore_date = opts->ignore_date;\n+\tfree(replay.gpg_sign);\n \treplay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);\n \treplay.reflog_action = xstrdup(opts->reflog_action);\n \tif (opts->strategy)\ndiff --git a/sequencer.c b/sequencer.c\nindex 0291920f0b..cade9b0ca8 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -303,6 +303,7 @@ static int git_sequencer_config(const char *k, const char *v,\n \t}\n \n \tif (!strcmp(k, \"commit.gpgsign\")) {\n+\t\tfree(opts->gpg_sign);\n \t\topts->gpg_sign = git_config_bool(k, v) ? xstrdup(\"\") : NULL;\n \t\treturn 0;\n \t}\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex f92baad138..f171af3061 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -26,6 +26,7 @@ Initial setup:\n  touch file \"conflict\".\n '\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n . \"$TEST_DIRECTORY\"/lib-rebase.sh\ndiff --git a/t/t3435-rebase-gpg-sign.sh b/t/t3435-rebase-gpg-sign.sh\nindex 6aa2aeb628..6e329fea7c 100755\n--- a/t/t3435-rebase-gpg-sign.sh\n+++ b/t/t3435-rebase-gpg-sign.sh\n@@ -8,6 +8,7 @@ test_description='test rebase --[no-]gpg-sign'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY/lib-rebase.sh\"\n . \"$TEST_DIRECTORY/lib-gpg.sh\"\ndiff --git a/t/t7030-verify-tag.sh b/t/t7030-verify-tag.sh\nindex 6f526c37c2..effa826744 100755\n--- a/t/t7030-verify-tag.sh\n+++ b/t/t7030-verify-tag.sh\n@@ -4,6 +4,7 @@ test_description='signed tag tests'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY/lib-gpg.sh\"\n \n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500854","messageId":"0af1bab5a1432eb636dc0fa7b538fa39661cb34d.1723614263.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723614263.git.ps@pks.im","subject":"[PATCH v4 10/22] builtin/notes: fix leaking `struct notes_tree` when merging notes","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-14T06:52:20Z","receivedAt":"2024-08-14T06:52:23Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We allocate a `struct notes_tree` in `merge_commit()` which we then\ninitialize via `init_notes()`. It's not really necessary to allocate the\nstructure though given that we never pass ownership to the caller.\nFurthermore, the allocation leads to a memory leak because despite its\nname, `free_notes()` doesn't free the `notes_tree` but only clears it.\n\nFix this issue by converting the code to use an on-stack variable.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/notes.c                       | 9 ++++-----\n t/t3310-notes-merge-manual-resolve.sh | 1 +\n t/t3311-notes-merge-fanout.sh         | 1 +\n 3 files changed, 6 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex d9c356e354..81cbaeec6b 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -807,7 +807,7 @@ static int merge_commit(struct notes_merge_options *o)\n {\n \tstruct strbuf msg = STRBUF_INIT;\n \tstruct object_id oid, parent_oid;\n-\tstruct notes_tree *t;\n+\tstruct notes_tree t = {0};\n \tstruct commit *partial;\n \tstruct pretty_print_context pretty_ctx;\n \tvoid *local_ref_to_free;\n@@ -830,8 +830,7 @@ static int merge_commit(struct notes_merge_options *o)\n \telse\n \t\toidclr(&parent_oid, the_repository->hash_algo);\n \n-\tCALLOC_ARRAY(t, 1);\n-\tinit_notes(t, \"NOTES_MERGE_PARTIAL\", combine_notes_overwrite, 0);\n+\tinit_notes(&t, \"NOTES_MERGE_PARTIAL\", combine_notes_overwrite, 0);\n \n \to->local_ref = local_ref_to_free =\n \t\trefs_resolve_refdup(get_main_ref_store(the_repository),\n@@ -839,7 +838,7 @@ static int merge_commit(struct notes_merge_options *o)\n \tif (!o->local_ref)\n \t\tdie(_(\"failed to resolve NOTES_MERGE_REF\"));\n \n-\tif (notes_merge_commit(o, t, partial, &oid))\n+\tif (notes_merge_commit(o, &t, partial, &oid))\n \t\tdie(_(\"failed to finalize notes merge\"));\n \n \t/* Reuse existing commit message in reflog message */\n@@ -853,7 +852,7 @@ static int merge_commit(struct notes_merge_options *o)\n \t\t\tis_null_oid(&parent_oid) ? NULL : &parent_oid,\n \t\t\t0, UPDATE_REFS_DIE_ON_ERR);\n \n-\tfree_notes(t);\n+\tfree_notes(&t);\n \tstrbuf_release(&msg);\n \tret = merge_abort(o);\n \tfree(local_ref_to_free);\ndiff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh\nindex 597df5ebc0..04866b89be 100755\n--- a/t/t3310-notes-merge-manual-resolve.sh\n+++ b/t/t3310-notes-merge-manual-resolve.sh\n@@ -5,6 +5,7 @@\n \n test_description='Test notes merging with manual conflict resolution'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # Set up a notes merge scenario with different kinds of conflicts\ndiff --git a/t/t3311-notes-merge-fanout.sh b/t/t3311-notes-merge-fanout.sh\nindex 5b675417e9..ce4144db0f 100755\n--- a/t/t3311-notes-merge-fanout.sh\n+++ b/t/t3311-notes-merge-fanout.sh\n@@ -5,6 +5,7 @@\n \n test_description='Test notes merging at various fanout levels'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n verify_notes () {\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500855","messageId":"30d4e9ed437b70a46d9b625cdb9bc4230aca298f.1723614263.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723614263.git.ps@pks.im","subject":"[PATCH v4 11/22] builtin/fast-import: plug trivial memory leaks","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-14T06:52:23Z","receivedAt":"2024-08-14T06:52:26Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Plug some trivial memory leaks in git-fast-import(1).\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fast-import.c        | 8 ++++++--\n t/t9300-fast-import.sh       | 1 +\n t/t9304-fast-import-marks.sh | 2 ++\n 3 files changed, 9 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex d21c4053a7..6dfeb01665 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -206,8 +206,8 @@ static unsigned int object_entry_alloc = 5000;\n static struct object_entry_pool *blocks;\n static struct hashmap object_table;\n static struct mark_set *marks;\n-static const char *export_marks_file;\n-static const char *import_marks_file;\n+static char *export_marks_file;\n+static char *import_marks_file;\n static int import_marks_file_from_stream;\n static int import_marks_file_ignore_missing;\n static int import_marks_file_done;\n@@ -3274,6 +3274,7 @@ static void option_import_marks(const char *marks,\n \t\t\tread_marks();\n \t}\n \n+\tfree(import_marks_file);\n \timport_marks_file = make_fast_import_path(marks);\n \timport_marks_file_from_stream = from_stream;\n \timport_marks_file_ignore_missing = ignore_missing;\n@@ -3316,6 +3317,7 @@ static void option_active_branches(const char *branches)\n \n static void option_export_marks(const char *marks)\n {\n+\tfree(export_marks_file);\n \texport_marks_file = make_fast_import_path(marks);\n }\n \n@@ -3357,6 +3359,8 @@ static void option_rewrite_submodules(const char *arg, struct string_list *list)\n \tfree(f);\n \n \tstring_list_insert(list, s)->util = ms;\n+\n+\tfree(s);\n }\n \n static int parse_one_option(const char *option)\ndiff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh\nindex 1e68426852..3b3c371740 100755\n--- a/t/t9300-fast-import.sh\n+++ b/t/t9300-fast-import.sh\n@@ -7,6 +7,7 @@ test_description='test git fast-import utility'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-diff.sh ;# test-lib chdir's into trash\n \ndiff --git a/t/t9304-fast-import-marks.sh b/t/t9304-fast-import-marks.sh\nindex 410a871c52..1f776a80f3 100755\n--- a/t/t9304-fast-import-marks.sh\n+++ b/t/t9304-fast-import-marks.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='test exotic situations with marks'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup dump of basic history' '\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500856","messageId":"070813a740566bebf50e771ec7d0fa4699c87a4b.1723614263.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723614263.git.ps@pks.im","subject":"[PATCH v4 12/22] builtin/fast-export: fix leaking diff options","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-14T06:52:26Z","receivedAt":"2024-08-14T06:52:32Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Before calling `handle_commit()` in a loop, we set `diffopt.no_free`\nsuch that its contents aren't getting freed inside of `handle_commit()`.\nWe never unset that flag though, which means that the structure's\nallocated resources will ultimately leak.\n\nFix this by unsetting the flag after the loop such that we release its\nresources via `release_revisions()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fast-export.c | 2 ++\n 1 file changed, 2 insertions(+)\n\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex 4b6e8c6832..fe92d2436c 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -1278,9 +1278,11 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)\n \trevs.diffopt.format_callback = show_filemodify;\n \trevs.diffopt.format_callback_data = &paths_of_changed_objects;\n \trevs.diffopt.flags.recursive = 1;\n+\n \trevs.diffopt.no_free = 1;\n \twhile ((commit = get_revision(&revs)))\n \t\thandle_commit(commit, &revs, &paths_of_changed_objects);\n+\trevs.diffopt.no_free = 0;\n \n \thandle_tags_and_duplicates(&extra_refs);\n \thandle_tags_and_duplicates(&tag_refs);\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500857","messageId":"b4096e971fe2fb3c1a6d5f763e08dc62eb6f3d38.1723614263.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723614263.git.ps@pks.im","subject":"[PATCH v4 13/22] builtin/fast-export: plug leaking tag names","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-14T06:52:31Z","receivedAt":"2024-08-14T06:52:34Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When resolving revisions in `get_tags_and_duplicates()`, we only\npartially manage the lifetime of `full_name`. In fact, managing its\nlifetime properly is almost impossible because we put direct pointers to\nthat variable into multiple lists without duplicating the string. The\nconsequence is that these strings will ultimately leak.\n\nRefactor the code to make the lists we put those names into duplicate\nthe memory. This allows us to properly free the string as required and\nthus plugs the memory leak.\n\nWhile this requires us to allocate more data overall, it shouldn't be\nall that bad given that the number of allocations corresponds with the\nnumber of command line parameters, which typically aren't all that many.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fast-export.c            | 17 ++++++++++++-----\n t/t9351-fast-export-anonymize.sh |  1 +\n 2 files changed, 13 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex fe92d2436c..f253b79322 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -42,8 +42,8 @@ static int full_tree;\n static int reference_excluded_commits;\n static int show_original_ids;\n static int mark_tags;\n-static struct string_list extra_refs = STRING_LIST_INIT_NODUP;\n-static struct string_list tag_refs = STRING_LIST_INIT_NODUP;\n+static struct string_list extra_refs = STRING_LIST_INIT_DUP;\n+static struct string_list tag_refs = STRING_LIST_INIT_DUP;\n static struct refspec refspecs = REFSPEC_INIT_FETCH;\n static int anonymize;\n static struct hashmap anonymized_seeds;\n@@ -901,7 +901,7 @@ static void handle_tag(const char *name, struct tag *tag)\n \tfree(buf);\n }\n \n-static struct commit *get_commit(struct rev_cmdline_entry *e, char *full_name)\n+static struct commit *get_commit(struct rev_cmdline_entry *e, const char *full_name)\n {\n \tswitch (e->item->type) {\n \tcase OBJ_COMMIT:\n@@ -932,14 +932,16 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)\n \t\tstruct rev_cmdline_entry *e = info->rev + i;\n \t\tstruct object_id oid;\n \t\tstruct commit *commit;\n-\t\tchar *full_name;\n+\t\tchar *full_name = NULL;\n \n \t\tif (e->flags & UNINTERESTING)\n \t\t\tcontinue;\n \n \t\tif (repo_dwim_ref(the_repository, e->name, strlen(e->name),\n-\t\t\t\t  &oid, &full_name, 0) != 1)\n+\t\t\t\t  &oid, &full_name, 0) != 1) {\n+\t\t\tfree(full_name);\n \t\t\tcontinue;\n+\t\t}\n \n \t\tif (refspecs.nr) {\n \t\t\tchar *private;\n@@ -955,6 +957,7 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)\n \t\t\twarning(\"%s: Unexpected object of type %s, skipping.\",\n \t\t\t\te->name,\n \t\t\t\ttype_name(e->item->type));\n+\t\t\tfree(full_name);\n \t\t\tcontinue;\n \t\t}\n \n@@ -963,10 +966,12 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)\n \t\t\tbreak;\n \t\tcase OBJ_BLOB:\n \t\t\texport_blob(&commit->object.oid);\n+\t\t\tfree(full_name);\n \t\t\tcontinue;\n \t\tdefault: /* OBJ_TAG (nested tags) is already handled */\n \t\t\twarning(\"Tag points to object of unexpected type %s, skipping.\",\n \t\t\t\ttype_name(commit->object.type));\n+\t\t\tfree(full_name);\n \t\t\tcontinue;\n \t\t}\n \n@@ -979,6 +984,8 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)\n \n \t\tif (!*revision_sources_at(&revision_sources, commit))\n \t\t\t*revision_sources_at(&revision_sources, commit) = full_name;\n+\t\telse\n+\t\t\tfree(full_name);\n \t}\n \n \tstring_list_sort(&extra_refs);\ndiff --git a/t/t9351-fast-export-anonymize.sh b/t/t9351-fast-export-anonymize.sh\nindex 156a647484..c0d9d7be75 100755\n--- a/t/t9351-fast-export-anonymize.sh\n+++ b/t/t9351-fast-export-anonymize.sh\n@@ -4,6 +4,7 @@ test_description='basic tests for fast-export --anonymize'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup simple repo' '\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500858","messageId":"bdfdf533134ff979f3236740b96101b83fd78bcc.1723614263.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723614263.git.ps@pks.im","subject":"[PATCH v4 14/22] merge-ort: unconditionally release attributes index","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-14T06:52:33Z","receivedAt":"2024-08-14T06:52:37Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We conditionally release the index used for reading gitattributes in\nmerge-ort based on whether or the index has been populated. This check\nuses `cache_nr` as a condition. This isn't sufficient though, as the\nvariable may be zero even when some other parts of the index have been\npopulated. This leads to memory leaks when sparse checkouts are in use,\nas we may not end up releasing the sparse checkout patterns.\n\nFix this issue by unconditionally releasing the index.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n merge-ort.c                       | 3 +--\n t/t3507-cherry-pick-conflict.sh   | 1 +\n t/t6421-merge-partial-clone.sh    | 1 +\n t/t6428-merge-conflicts-sparse.sh | 1 +\n t/t7817-grep-sparse-checkout.sh   | 1 +\n 5 files changed, 5 insertions(+), 2 deletions(-)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex e9d01ac7f7..3752c7e595 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -689,8 +689,7 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,\n \t */\n \tstrmap_clear_func(&opti->conflicted, 0);\n \n-\tif (opti->attr_index.cache_nr) /* true iff opt->renormalize */\n-\t\tdiscard_index(&opti->attr_index);\n+\tdiscard_index(&opti->attr_index);\n \n \t/* Free memory used by various renames maps */\n \tfor (i = MERGE_SIDE1; i <= MERGE_SIDE2; ++i) {\ndiff --git a/t/t3507-cherry-pick-conflict.sh b/t/t3507-cherry-pick-conflict.sh\nindex f3947b400a..10e9c91dbb 100755\n--- a/t/t3507-cherry-pick-conflict.sh\n+++ b/t/t3507-cherry-pick-conflict.sh\n@@ -13,6 +13,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n TEST_CREATE_REPO_NO_TEMPLATE=1\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n pristine_detach () {\ndiff --git a/t/t6421-merge-partial-clone.sh b/t/t6421-merge-partial-clone.sh\nindex 711b709e75..020375c805 100755\n--- a/t/t6421-merge-partial-clone.sh\n+++ b/t/t6421-merge-partial-clone.sh\n@@ -26,6 +26,7 @@ test_description=\"limiting blob downloads when merging with partial clones\"\n #                     underscore notation is to differentiate different\n #                     files that might be renamed into each other's paths.)\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-merge.sh\n \ndiff --git a/t/t6428-merge-conflicts-sparse.sh b/t/t6428-merge-conflicts-sparse.sh\nindex 9919c3fa7c..8a79bc2e92 100755\n--- a/t/t6428-merge-conflicts-sparse.sh\n+++ b/t/t6428-merge-conflicts-sparse.sh\n@@ -22,6 +22,7 @@ test_description=\"merge cases\"\n #                     underscore notation is to differentiate different\n #                     files that might be renamed into each other's paths.)\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-merge.sh\n \ndiff --git a/t/t7817-grep-sparse-checkout.sh b/t/t7817-grep-sparse-checkout.sh\nindex eb59564565..0ba7817fb7 100755\n--- a/t/t7817-grep-sparse-checkout.sh\n+++ b/t/t7817-grep-sparse-checkout.sh\n@@ -33,6 +33,7 @@ should leave the following structure in the working tree:\n But note that sub2 should have the SKIP_WORKTREE bit set.\n '\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup' '\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500859","messageId":"f6c1055805d944337752acb70cc613d8d4684d3f.1723614263.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723614263.git.ps@pks.im","subject":"[PATCH v4 15/22] sequencer: release todo list on error paths","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-14T06:52:36Z","receivedAt":"2024-08-14T06:52:40Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We're not releasing the `todo_list` in `sequencer_pick_revisions()` when\nhitting an error path. Restructure the function to have a common exit\npath such that we can easily clean up the list and thus plug this memory\nleak.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n sequencer.c                     | 66 +++++++++++++++++++++++----------\n t/t3510-cherry-pick-sequence.sh |  1 +\n 2 files changed, 48 insertions(+), 19 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex cade9b0ca8..ea559c31f1 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5490,8 +5490,10 @@ int sequencer_pick_revisions(struct repository *r,\n \tint i, res;\n \n \tassert(opts->revs);\n-\tif (read_and_refresh_cache(r, opts))\n-\t\treturn -1;\n+\tif (read_and_refresh_cache(r, opts)) {\n+\t\tres = -1;\n+\t\tgoto out;\n+\t}\n \n \tfor (i = 0; i < opts->revs->pending.nr; i++) {\n \t\tstruct object_id oid;\n@@ -5506,11 +5508,14 @@ int sequencer_pick_revisions(struct repository *r,\n \t\t\t\tenum object_type type = oid_object_info(r,\n \t\t\t\t\t\t\t\t\t&oid,\n \t\t\t\t\t\t\t\t\tNULL);\n-\t\t\t\treturn error(_(\"%s: can't cherry-pick a %s\"),\n-\t\t\t\t\tname, type_name(type));\n+\t\t\t\tres = error(_(\"%s: can't cherry-pick a %s\"),\n+\t\t\t\t\t    name, type_name(type));\n+\t\t\t\tgoto out;\n \t\t\t}\n-\t\t} else\n-\t\t\treturn error(_(\"%s: bad revision\"), name);\n+\t\t} else {\n+\t\t\tres = error(_(\"%s: bad revision\"), name);\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \n \t/*\n@@ -5525,14 +5530,23 @@ int sequencer_pick_revisions(struct repository *r,\n \t    opts->revs->no_walk &&\n \t    !opts->revs->cmdline.rev->flags) {\n \t\tstruct commit *cmit;\n-\t\tif (prepare_revision_walk(opts->revs))\n-\t\t\treturn error(_(\"revision walk setup failed\"));\n+\n+\t\tif (prepare_revision_walk(opts->revs)) {\n+\t\t\tres = error(_(\"revision walk setup failed\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n \t\tcmit = get_revision(opts->revs);\n-\t\tif (!cmit)\n-\t\t\treturn error(_(\"empty commit set passed\"));\n+\t\tif (!cmit) {\n+\t\t\tres = error(_(\"empty commit set passed\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n \t\tif (get_revision(opts->revs))\n \t\t\tBUG(\"unexpected extra commit from walk\");\n-\t\treturn single_pick(r, cmit, opts);\n+\n+\t\tres = single_pick(r, cmit, opts);\n+\t\tgoto out;\n \t}\n \n \t/*\n@@ -5542,16 +5556,30 @@ int sequencer_pick_revisions(struct repository *r,\n \t */\n \n \tif (walk_revs_populate_todo(&todo_list, opts) ||\n-\t\t\tcreate_seq_dir(r) < 0)\n-\t\treturn -1;\n-\tif (repo_get_oid(r, \"HEAD\", &oid) && (opts->action == REPLAY_REVERT))\n-\t\treturn error(_(\"can't revert as initial commit\"));\n-\tif (save_head(oid_to_hex(&oid)))\n-\t\treturn -1;\n-\tif (save_opts(opts))\n-\t\treturn -1;\n+\t\t\tcreate_seq_dir(r) < 0) {\n+\t\tres = -1;\n+\t\tgoto out;\n+\t}\n+\n+\tif (repo_get_oid(r, \"HEAD\", &oid) && (opts->action == REPLAY_REVERT)) {\n+\t\tres = error(_(\"can't revert as initial commit\"));\n+\t\tgoto out;\n+\t}\n+\n+\tif (save_head(oid_to_hex(&oid))) {\n+\t\tres = -1;\n+\t\tgoto out;\n+\t}\n+\n+\tif (save_opts(opts)) {\n+\t\tres = -1;\n+\t\tgoto out;\n+\t}\n+\n \tupdate_abort_safety_file();\n \tres = pick_commits(r, &todo_list, opts);\n+\n+out:\n \ttodo_list_release(&todo_list);\n \treturn res;\n }\ndiff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh\nindex 7eb52b12ed..93c725bac3 100755\n--- a/t/t3510-cherry-pick-sequence.sh\n+++ b/t/t3510-cherry-pick-sequence.sh\n@@ -12,6 +12,7 @@ test_description='Test cherry-pick continuation features\n \n '\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # Repeat first match 10 times\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500860","messageId":"9db41181a64f2bddc0d33528dcf32292d21ed145.1723614263.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723614263.git.ps@pks.im","subject":"[PATCH v4 16/22] unpack-trees: clear index when not propagating it","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-14T06:52:39Z","receivedAt":"2024-08-14T06:52:42Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When provided a pointer to a destination index, then `unpack_trees()`\nwill end up copying its `o->internal.result` index into the provided\npointer. In those cases it is thus not necessary to free the index, as\nwe have transferred ownership of it.\n\nThere are cases though where we do not end up transferring ownership of\nthe memory, but `clear_unpack_trees_porcelain()` will never discard the\nindex in that case and thus cause a memory leak. And right now it cannot\ndo so in the first place because we have no indicator of whether we did\nor didn't transfer ownership of the index.\n\nAdapt the code to zero out the index in case we transfer its ownership.\nLike this, we can now unconditionally discard the index when being asked\nto clear the `unpack_trees_options`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n t/t3705-add-sparse-checkout.sh | 1 +\n unpack-trees.c                 | 2 ++\n 2 files changed, 3 insertions(+)\n\ndiff --git a/t/t3705-add-sparse-checkout.sh b/t/t3705-add-sparse-checkout.sh\nindex 2bade9e804..6ae45a788d 100755\n--- a/t/t3705-add-sparse-checkout.sh\n+++ b/t/t3705-add-sparse-checkout.sh\n@@ -2,6 +2,7 @@\n \n test_description='git add in sparse checked out working trees'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n SPARSE_ENTRY_BLOB=\"\"\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 7dc884fafd..9a55cb6204 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -210,6 +210,7 @@ void clear_unpack_trees_porcelain(struct unpack_trees_options *opts)\n {\n \tstrvec_clear(&opts->internal.msgs_to_free);\n \tmemset(opts->internal.msgs, 0, sizeof(opts->internal.msgs));\n+\tdiscard_index(&opts->internal.result);\n }\n \n static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce,\n@@ -2082,6 +2083,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options\n \t\to->internal.result.updated_workdir = 1;\n \t\tdiscard_index(o->dst_index);\n \t\t*o->dst_index = o->internal.result;\n+\t\tmemset(&o->internal.result, 0, sizeof(o->internal.result));\n \t} else {\n \t\tdiscard_index(&o->internal.result);\n \t}\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500861","messageId":"85f6ffd61099fd6194c8fa8491abfe01a9195298.1723614263.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723614263.git.ps@pks.im","subject":"[PATCH v4 17/22] diff: fix leak when parsing invalid ignore regex option","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-14T06:52:42Z","receivedAt":"2024-08-14T06:52:45Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When parsing invalid ignore regexes passed via the `-I` option we don't\nfree already-allocated memory, leading to a memory leak. Fix this.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n diff.c                  | 6 +++++-\n t/t4013-diff-various.sh | 1 +\n 2 files changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/diff.c b/diff.c\nindex ebb7538e04..9251c47b72 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -5464,9 +5464,13 @@ static int diff_opt_ignore_regex(const struct option *opt,\n \tregex_t *regex;\n \n \tBUG_ON_OPT_NEG(unset);\n+\n \tregex = xmalloc(sizeof(*regex));\n-\tif (regcomp(regex, arg, REG_EXTENDED | REG_NEWLINE))\n+\tif (regcomp(regex, arg, REG_EXTENDED | REG_NEWLINE)) {\n+\t\tfree(regex);\n \t\treturn error(_(\"invalid regex given to -I: '%s'\"), arg);\n+\t}\n+\n \tALLOC_GROW(options->ignore_regex, options->ignore_regex_nr + 1,\n \t\t   options->ignore_regex_alloc);\n \toptions->ignore_regex[options->ignore_regex_nr++] = regex;\ndiff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh\nindex 3855d68dbc..87d248d034 100755\n--- a/t/t4013-diff-various.sh\n+++ b/t/t4013-diff-various.sh\n@@ -8,6 +8,7 @@ test_description='Various diff formatting options'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-diff.sh\n \n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500862","messageId":"e00aa1ef0677175b4d276607f7b28de0e0491fc7.1723614263.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723614263.git.ps@pks.im","subject":"[PATCH v4 18/22] builtin/format-patch: fix various trivial memory leaks","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-14T06:52:44Z","receivedAt":"2024-08-14T06:52:50Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"There are various memory leaks hit by git-format-patch(1). Basically all\nof them are trivial, except that un-setting `diffopt.no_free` requires\nus to unset the `diffopt.file` because we manually close it already.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/log.c           | 13 ++++++++++---\n t/t4014-format-patch.sh |  1 +\n 2 files changed, 11 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex a73a767606..f5cb00c643 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1827,12 +1827,14 @@ static struct commit *get_base_commit(const struct format_config *cfg,\n \t\t\t\tif (die_on_failure) {\n \t\t\t\t\tdie(_(\"failed to find exact merge base\"));\n \t\t\t\t} else {\n+\t\t\t\t\tfree_commit_list(merge_base);\n \t\t\t\t\tfree(rev);\n \t\t\t\t\treturn NULL;\n \t\t\t\t}\n \t\t\t}\n \n \t\t\trev[i] = merge_base->item;\n+\t\t\tfree_commit_list(merge_base);\n \t\t}\n \n \t\tif (rev_nr % 2)\n@@ -2023,6 +2025,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \tconst char *rfc = NULL;\n \tint creation_factor = -1;\n \tconst char *signature = git_version_string;\n+\tchar *signature_to_free = NULL;\n \tchar *signature_file_arg = NULL;\n \tstruct keep_callback_data keep_callback_data = {\n \t\t.cfg = &cfg,\n@@ -2443,7 +2446,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \n \t\tif (strbuf_read_file(&buf, signature_file, 128) < 0)\n \t\t\tdie_errno(_(\"unable to read signature file '%s'\"), signature_file);\n-\t\tsignature = strbuf_detach(&buf, NULL);\n+\t\tsignature = signature_to_free = strbuf_detach(&buf, NULL);\n \t} else if (cfg.signature) {\n \t\tsignature = cfg.signature;\n \t}\n@@ -2548,12 +2551,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \t\t\telse\n \t\t\t\tprint_signature(signature, rev.diffopt.file);\n \t\t}\n-\t\tif (output_directory)\n+\t\tif (output_directory) {\n \t\t\tfclose(rev.diffopt.file);\n+\t\t\trev.diffopt.file = NULL;\n+\t\t}\n \t}\n \tstop_progress(&progress);\n \tfree(list);\n-\tfree(branch_name);\n \tif (ignore_if_in_upstream)\n \t\tfree_patch_ids(&ids);\n \n@@ -2565,11 +2569,14 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \tstrbuf_release(&rdiff_title);\n \tfree(description_file);\n \tfree(signature_file_arg);\n+\tfree(signature_to_free);\n+\tfree(branch_name);\n \tfree(to_free);\n \tfree(rev.message_id);\n \tif (rev.ref_message_ids)\n \t\tstring_list_clear(rev.ref_message_ids, 0);\n \tfree(rev.ref_message_ids);\n+\trev.diffopt.no_free = 0;\n \trelease_revisions(&rev);\n \tformat_config_release(&cfg);\n \treturn 0;\ndiff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh\nindex 884f83fb8a..1c46e963e4 100755\n--- a/t/t4014-format-patch.sh\n+++ b/t/t4014-format-patch.sh\n@@ -8,6 +8,7 @@ test_description='various format-patch tests'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-terminal.sh\n \n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500863","messageId":"cc047511349039951da63ebbfd4fda6e42a2beec.1723614263.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723614263.git.ps@pks.im","subject":"[PATCH v4 19/22] userdiff: fix leaking memory for configured diff drivers","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-14T06:52:50Z","receivedAt":"2024-08-14T06:52:53Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The userdiff structures may be initialized either statically on the\nstack or dynamically via configuration keys. In the latter case we end\nup leaking memory because we didn't have any infrastructure to discern\nthose strings which have been allocated statically and those which have\nbeen allocated dynamically.\n\nRefactor the code such that we have two pointers for each of these\nstrings: one that holds the value as accessed by other subsystems, and\none that points to the same string in case it has been allocated. Like\nthis, we can safely free the second pointer and thus plug those memory\nleaks.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n range-diff.c                     |  6 +++--\n t/t4018-diff-funcname.sh         |  1 +\n t/t4042-diff-textconv-caching.sh |  2 ++\n t/t4048-diff-combined-binary.sh  |  1 +\n t/t4209-log-pickaxe.sh           |  2 ++\n userdiff.c                       | 38 ++++++++++++++++++++++++--------\n userdiff.h                       |  4 ++++\n 7 files changed, 43 insertions(+), 11 deletions(-)\n\ndiff --git a/range-diff.c b/range-diff.c\nindex 5f01605550..bbb0952264 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -450,8 +450,10 @@ static void output_pair_header(struct diff_options *diffopt,\n }\n \n static struct userdiff_driver section_headers = {\n-\t.funcname = { \"^ ## (.*) ##$\\n\"\n-\t\t      \"^.?@@ (.*)$\", REG_EXTENDED }\n+\t.funcname = {\n+\t\t.pattern = \"^ ## (.*) ##$\\n^.?@@ (.*)$\",\n+\t\t.cflags = REG_EXTENDED,\n+\t},\n };\n \n static struct diff_filespec *get_filespec(const char *name, const char *p)\ndiff --git a/t/t4018-diff-funcname.sh b/t/t4018-diff-funcname.sh\nindex e026fac1f4..8128c30e7f 100755\n--- a/t/t4018-diff-funcname.sh\n+++ b/t/t4018-diff-funcname.sh\n@@ -5,6 +5,7 @@\n \n test_description='Test custom diff function name patterns'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup' '\ndiff --git a/t/t4042-diff-textconv-caching.sh b/t/t4042-diff-textconv-caching.sh\nindex 8ebfa3c1be..a179205394 100755\n--- a/t/t4042-diff-textconv-caching.sh\n+++ b/t/t4042-diff-textconv-caching.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='test textconv caching'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n cat >helper <<'EOF'\ndiff --git a/t/t4048-diff-combined-binary.sh b/t/t4048-diff-combined-binary.sh\nindex 0260cf64f5..f399484bce 100755\n--- a/t/t4048-diff-combined-binary.sh\n+++ b/t/t4048-diff-combined-binary.sh\n@@ -4,6 +4,7 @@ test_description='combined and merge diff handle binary files and textconv'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup binary merge conflict' '\ndiff --git a/t/t4209-log-pickaxe.sh b/t/t4209-log-pickaxe.sh\nindex 64e1623733..b42fdc54fc 100755\n--- a/t/t4209-log-pickaxe.sh\n+++ b/t/t4209-log-pickaxe.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='log --grep/--author/--regexp-ignore-case/-S/-G'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_log () {\ndiff --git a/userdiff.c b/userdiff.c\nindex c4ebb9ff73..989629149f 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -399,8 +399,11 @@ static struct userdiff_driver *userdiff_find_by_namelen(const char *name, size_t\n static int parse_funcname(struct userdiff_funcname *f, const char *k,\n \t\tconst char *v, int cflags)\n {\n-\tif (git_config_string((char **) &f->pattern, k, v) < 0)\n+\tf->pattern = NULL;\n+\tFREE_AND_NULL(f->pattern_owned);\n+\tif (git_config_string(&f->pattern_owned, k, v) < 0)\n \t\treturn -1;\n+\tf->pattern = f->pattern_owned;\n \tf->cflags = cflags;\n \treturn 0;\n }\n@@ -444,20 +447,37 @@ int userdiff_config(const char *k, const char *v)\n \t\treturn parse_funcname(&drv->funcname, k, v, REG_EXTENDED);\n \tif (!strcmp(type, \"binary\"))\n \t\treturn parse_tristate(&drv->binary, k, v);\n-\tif (!strcmp(type, \"command\"))\n-\t\treturn git_config_string((char **) &drv->external.cmd, k, v);\n+\tif (!strcmp(type, \"command\")) {\n+\t\tFREE_AND_NULL(drv->external.cmd);\n+\t\treturn git_config_string(&drv->external.cmd, k, v);\n+\t}\n \tif (!strcmp(type, \"trustexitcode\")) {\n \t\tdrv->external.trust_exit_code = git_config_bool(k, v);\n \t\treturn 0;\n \t}\n-\tif (!strcmp(type, \"textconv\"))\n-\t\treturn git_config_string((char **) &drv->textconv, k, v);\n+\tif (!strcmp(type, \"textconv\")) {\n+\t\tint ret;\n+\t\tFREE_AND_NULL(drv->textconv_owned);\n+\t\tret = git_config_string(&drv->textconv_owned, k, v);\n+\t\tdrv->textconv = drv->textconv_owned;\n+\t\treturn ret;\n+\t}\n \tif (!strcmp(type, \"cachetextconv\"))\n \t\treturn parse_bool(&drv->textconv_want_cache, k, v);\n-\tif (!strcmp(type, \"wordregex\"))\n-\t\treturn git_config_string((char **) &drv->word_regex, k, v);\n-\tif (!strcmp(type, \"algorithm\"))\n-\t\treturn git_config_string((char **) &drv->algorithm, k, v);\n+\tif (!strcmp(type, \"wordregex\")) {\n+\t\tint ret;\n+\t\tFREE_AND_NULL(drv->word_regex_owned);\n+\t\tret = git_config_string(&drv->word_regex_owned, k, v);\n+\t\tdrv->word_regex = drv->word_regex_owned;\n+\t\treturn ret;\n+\t}\n+\tif (!strcmp(type, \"algorithm\")) {\n+\t\tint ret;\n+\t\tFREE_AND_NULL(drv->algorithm_owned);\n+\t\tret = git_config_string(&drv->algorithm_owned, k, v);\n+\t\tdrv->algorithm = drv->algorithm_owned;\n+\t\treturn ret;\n+\t}\n \n \treturn 0;\n }\ndiff --git a/userdiff.h b/userdiff.h\nindex 7565930337..827361b0bc 100644\n--- a/userdiff.h\n+++ b/userdiff.h\n@@ -8,6 +8,7 @@ struct repository;\n \n struct userdiff_funcname {\n \tconst char *pattern;\n+\tchar *pattern_owned;\n \tint cflags;\n };\n \n@@ -20,11 +21,14 @@ struct userdiff_driver {\n \tconst char *name;\n \tstruct external_diff external;\n \tconst char *algorithm;\n+\tchar *algorithm_owned;\n \tint binary;\n \tstruct userdiff_funcname funcname;\n \tconst char *word_regex;\n+\tchar *word_regex_owned;\n \tconst char *word_regex_multi_byte;\n \tconst char *textconv;\n+\tchar *textconv_owned;\n \tstruct notes_cache *textconv_cache;\n \tint textconv_want_cache;\n };\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500864","messageId":"0e2d3e523f7debf121bf4c1e62e485577b77e9de.1723614263.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723614263.git.ps@pks.im","subject":"[PATCH v4 20/22] builtin/log: fix leak when showing converted blob contents","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-14T06:52:53Z","receivedAt":"2024-08-14T06:52:57Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `show_blob_object()`, we proactively call `textconv_object()`. In\ncase we have a textconv driver for this blob we will end up showing the\nconverted contents, otherwise we'll show the un-converted contents of it\ninstead.\n\nWhen the object has been converted we never free the buffer containing\nthe converted contents. Fix this to plug this memory leak.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/log.c            | 1 +\n t/t4030-diff-textconv.sh | 2 ++\n 2 files changed, 3 insertions(+)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex f5cb00c643..36769bab3b 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -707,6 +707,7 @@ static int show_blob_object(const struct object_id *oid, struct rev_info *rev, c\n \n \twrite_or_die(1, buf, size);\n \tobject_context_release(&obj_context);\n+\tfree(buf);\n \treturn 0;\n }\n \ndiff --git a/t/t4030-diff-textconv.sh b/t/t4030-diff-textconv.sh\nindex a39a626664..29f6d610c2 100755\n--- a/t/t4030-diff-textconv.sh\n+++ b/t/t4030-diff-textconv.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='diff.*.textconv tests'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n find_diff() {\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500865","messageId":"9faffa7a62c05a83e4669a01c74da7853a1d7379.1723614263.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723614263.git.ps@pks.im","subject":"[PATCH v4 21/22] diff: free state populated via options","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-14T06:52:55Z","receivedAt":"2024-08-14T06:52:58Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `objfind` and `anchors` members of `struct diff_options` are\npopulated via option parsing, but are never freed in `diff_free()`. Fix\nthis to plug those memory leaks.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n diff.c                   | 10 ++++++++++\n t/t4064-diff-oidfind.sh  |  2 ++\n t/t4065-diff-anchored.sh |  1 +\n t/t4069-remerge-diff.sh  |  1 +\n 4 files changed, 14 insertions(+)\n\ndiff --git a/diff.c b/diff.c\nindex 9251c47b72..4035a9374d 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -6717,6 +6717,16 @@ void diff_free(struct diff_options *options)\n \tif (options->no_free)\n \t\treturn;\n \n+\tif (options->objfind) {\n+\t\toidset_clear(options->objfind);\n+\t\tFREE_AND_NULL(options->objfind);\n+\t}\n+\n+\tfor (size_t i = 0; i < options->anchors_nr; i++)\n+\t\tfree(options->anchors[i]);\n+\tFREE_AND_NULL(options->anchors);\n+\toptions->anchors_nr = options->anchors_alloc = 0;\n+\n \tdiff_free_file(options);\n \tdiff_free_ignore_regex(options);\n \tclear_pathspec(&options->pathspec);\ndiff --git a/t/t4064-diff-oidfind.sh b/t/t4064-diff-oidfind.sh\nindex 6d8c8986fc..846f285f77 100755\n--- a/t/t4064-diff-oidfind.sh\n+++ b/t/t4064-diff-oidfind.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='test finding specific blobs in the revision walking'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup ' '\ndiff --git a/t/t4065-diff-anchored.sh b/t/t4065-diff-anchored.sh\nindex b3f510f040..647537c12e 100755\n--- a/t/t4065-diff-anchored.sh\n+++ b/t/t4065-diff-anchored.sh\n@@ -2,6 +2,7 @@\n \n test_description='anchored diff algorithm'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success '--anchored' '\ndiff --git a/t/t4069-remerge-diff.sh b/t/t4069-remerge-diff.sh\nindex 07323ebafe..888714bbd3 100755\n--- a/t/t4069-remerge-diff.sh\n+++ b/t/t4069-remerge-diff.sh\n@@ -2,6 +2,7 @@\n \n test_description='remerge-diff handling'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # This test is ort-specific\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500866","messageId":"ee252e752c7bee0dea6767c5a53dd6656b4c35db.1723614263.git.ps@pks.im","threadId":"61910","inReplyTo":"cover.1723614263.git.ps@pks.im","subject":"[PATCH v4 22/22] builtin/diff: free symmetric diff members","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-14T06:52:58Z","receivedAt":"2024-08-14T06:53:02Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We populate a `struct symdiff` in case the user has requested a\nsymmetric diff. Part of this is to populate a `skip` bitmap that\nindicates which commits shall be ignored in the diff. But while this\nbitmap is dynamically allocated, we never free it.\n\nFix this by introducing and calling a new `symdiff_release()` function\nthat does this for us.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/diff.c                       | 6 ++++++\n t/t4068-diff-symmetric-merge-base.sh | 1 +\n t/t4108-apply-threeway.sh            | 1 +\n 3 files changed, 8 insertions(+)\n\ndiff --git a/builtin/diff.c b/builtin/diff.c\nindex 9b6cdabe15..6eac445579 100644\n--- a/builtin/diff.c\n+++ b/builtin/diff.c\n@@ -388,6 +388,11 @@ static void symdiff_prepare(struct rev_info *rev, struct symdiff *sym)\n \tsym->skip = map;\n }\n \n+static void symdiff_release(struct symdiff *sdiff)\n+{\n+\tbitmap_free(sdiff->skip);\n+}\n+\n int cmd_diff(int argc, const char **argv, const char *prefix)\n {\n \tint i;\n@@ -619,6 +624,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)\n \t\trefresh_index_quietly();\n \trelease_revisions(&rev);\n \tobject_array_clear(&ent);\n+\tsymdiff_release(&sdiff);\n \tUNLEAK(blob);\n \treturn result;\n }\ndiff --git a/t/t4068-diff-symmetric-merge-base.sh b/t/t4068-diff-symmetric-merge-base.sh\nindex eff63c16b0..4d6565e728 100755\n--- a/t/t4068-diff-symmetric-merge-base.sh\n+++ b/t/t4068-diff-symmetric-merge-base.sh\n@@ -5,6 +5,7 @@ test_description='behavior of diff with symmetric-diff setups and --merge-base'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # build these situations:\ndiff --git a/t/t4108-apply-threeway.sh b/t/t4108-apply-threeway.sh\nindex c558282bc0..3211e1e65f 100755\n--- a/t/t4108-apply-threeway.sh\n+++ b/t/t4108-apply-threeway.sh\n@@ -5,6 +5,7 @@ test_description='git apply --3way'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n print_sanitized_conflicted_diff () {\n-- \n2.46.0.46.g406f326d27.dirty\n\n"},{"id":"500915","messageId":"xmqq1q2r2kfa.fsf@gitster.g","threadId":"61910","inReplyTo":"Zrw6M7wvPFfzv3fO@tanuki","subject":"Re: [PATCH v3 22/22] builtin/diff: free symmetric diff members","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-08-14T15:28:25Z","receivedAt":"2024-08-14T15:28:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Good point. It does make sense for `_free()` functions to handle NULL\n> pointers, but doesn't quite for `_release()` ones.\n\nI agree that foo_free() should accept NULL and silently become a\nno-op.  I do not care deeply whether foo_release() did the same, or\nnot, as long as all *_release()s behave the same way.  Maybe it is\nmore convenient if they ignored NULL, as I have a hunch that feeding\na NULL pointer to foo_release() is unlikely to be a bug.\n\nSince we documented our aspiration to use these (and foo_clear())\nconsistently, we may #leftoverbits want to also document the calling\nconvention as well.\n\n>> And symdiff_prepare() at least clears its .skip member to NULL, so\n>> this pre-initialization is probably not needed.  If we are preparing\n>> ourselves for future changes of the flow in this function (e.g.\n>> goto's that jump to the clean-up label from which symdiff_release()\n>> is always called, even when we did not call symdiff_prepare() on\n>> this thing), this is probably not sufficient to convey that\n>> intention (instead I'd use an explicit \".skip = NULL\" to say \"we\n>> might not even call _prepare() but this one is prepared to be passed\n>> to _release() even in such a case\").\n>> \n>> Given that there is no such goto exists, and that _prepare() always\n>> sets up the .skip member appropriately, I wonder if we are much\n>> better off leaving sdiff uninitialized at the declaration site here.\n>> If we add such a goto that bypasses _prepare() in the future, the\n>> compiler will notice that we are passing an uninitialized sdiff to\n>> _release(), no?\n>\n> You'd hope it does, but it certainly depends on your compiler flags.\n> Various hardening flags for example implicitly initialize variables, and\n> I have a feeling that this also causes them to not emit any warnings\n> anymore. At least I only spot such warnings in CI.\n\nYeah, that is a sad fact in the real world X-<.  To be defensive, I\nthink an explicit \"{ .skip = NULL }\" or \"{ 0 }\" would not be too bad\nand may even serve as a good reminder for developers who may want to\njump over the call to _prepare() in the future.\n\nThe explicit \".skip = NULL\" says \"we know it is safe to call\n_release() with a struct that hasn't gone through _prepare(), as\nlong as its .skip member is cleared\", but the story \"{ 0 }\" tells us\nis not much more than \"we clear just like everybody else\", and that\nis why I suggested the former (iow, I know both mean the same thing\nto the C compiler---I just care more about what it tells the human\nreaders).\n\nThanks.\n\n"}]}