Subject: [PATCH v2 00/22] Memory leak fixes (pt.4)

From: Patrick Steinhardt <[email-masked]>
Date: Thu, 8 Aug 2024 15:04:28 +0200

Hi,

this is the second version of my fourth batch of patches that fix
various memory leaks.

Changes compared to v1:

  - Adapt the memory leak fix for command characters to instead use a
    `comment_line_str_allocated` variable.

  - Clarify some commit messages.

  - Drop the TODO comment about `rebase.gpgsign`. Turns out that this is
    working as intended, as explained by Phillip.

Thanks!

Patrick

Patrick Steinhardt (22):
  remote: plug memory leak when aliasing URLs
  git: fix leaking system paths
  object-file: fix memory leak when reading corrupted headers
  object-name: fix leaking symlink paths in object context
  bulk-checkin: fix leaking state TODO
  read-cache: fix leaking hashfile when writing index fails
  submodule-config: fix leaking name enrty when traversing submodules
  config: fix leaking comment character config
  builtin/rebase: fix leaking `commit.gpgsign` value
  builtin/notes: fix leaking `struct notes_tree` when merging notes
  builtin/fast-import: plug trivial memory leaks
  builtin/fast-export: fix leaking diff options
  builtin/fast-export: plug leaking tag names
  merge-ort: unconditionally release attributes index
  sequencer: release todo list on error paths
  unpack-trees: clear index when not propagating it
  diff: fix leak when parsing invalid ignore regex option
  builtin/format-patch: fix various trivial memory leaks
  userdiff: fix leaking memory for configured diff drivers
  builtin/log: fix leak when showing converted blob contents
  diff: free state populated via options
  builtin/diff: free symmetric diff members

 builtin/commit.c                      |  7 +-
 builtin/diff.c                        | 10 ++-
 builtin/fast-export.c                 | 19 ++++--
 builtin/fast-import.c                 |  8 ++-
 builtin/log.c                         | 13 +++-
 builtin/notes.c                       |  9 ++-
 builtin/rebase.c                      |  1 +
 bulk-checkin.c                        |  2 +
 config.c                              |  4 +-
 csum-file.c                           |  2 +-
 csum-file.h                           | 10 +++
 diff.c                                | 16 ++++-
 environment.c                         |  1 +
 environment.h                         |  1 +
 git.c                                 | 12 +++-
 merge-ort.c                           |  3 +-
 object-file.c                         |  1 +
 object-name.c                         |  1 +
 range-diff.c                          |  6 +-
 read-cache.c                          | 97 ++++++++++++++++-----------
 remote.c                              |  2 +
 sequencer.c                           | 67 ++++++++++++------
 submodule-config.c                    | 18 +++--
 t/t0210-trace2-normal.sh              |  2 +-
 t/t1006-cat-file.sh                   |  1 +
 t/t1050-large.sh                      |  1 +
 t/t1450-fsck.sh                       |  1 +
 t/t1601-index-bogus.sh                |  2 +
 t/t2107-update-index-basic.sh         |  1 +
 t/t3310-notes-merge-manual-resolve.sh |  1 +
 t/t3311-notes-merge-fanout.sh         |  1 +
 t/t3404-rebase-interactive.sh         |  1 +
 t/t3435-rebase-gpg-sign.sh            |  1 +
 t/t3507-cherry-pick-conflict.sh       |  1 +
 t/t3510-cherry-pick-sequence.sh       |  1 +
 t/t3705-add-sparse-checkout.sh        |  1 +
 t/t4013-diff-various.sh               |  1 +
 t/t4014-format-patch.sh               |  1 +
 t/t4018-diff-funcname.sh              |  1 +
 t/t4030-diff-textconv.sh              |  2 +
 t/t4042-diff-textconv-caching.sh      |  2 +
 t/t4048-diff-combined-binary.sh       |  1 +
 t/t4064-diff-oidfind.sh               |  2 +
 t/t4065-diff-anchored.sh              |  1 +
 t/t4068-diff-symmetric-merge-base.sh  |  1 +
 t/t4069-remerge-diff.sh               |  1 +
 t/t4108-apply-threeway.sh             |  1 +
 t/t4209-log-pickaxe.sh                |  2 +
 t/t6421-merge-partial-clone.sh        |  1 +
 t/t6428-merge-conflicts-sparse.sh     |  1 +
 t/t7008-filter-branch-null-sha1.sh    |  1 +
 t/t7030-verify-tag.sh                 |  1 +
 t/t7817-grep-sparse-checkout.sh       |  1 +
 t/t9300-fast-import.sh                |  1 +
 t/t9304-fast-import-marks.sh          |  2 +
 t/t9351-fast-export-anonymize.sh      |  1 +
 unpack-trees.c                        |  2 +
 userdiff.c                            | 38 ++++++++---
 userdiff.h                            |  4 ++
 59 files changed, 288 insertions(+), 106 deletions(-)

Range-diff against v1:
 1:  6e2fcd85c7 =  1:  2afa51f9ff remote: plug memory leak when aliasing URLs
 2:  9574995a24 =  2:  324140e4fd git: fix leaking system paths
 3:  f7e67d02d2 =  3:  43a38a2281 object-file: fix memory leak when reading corrupted headers
 4:  a9caaaed55 =  4:  9d3dc145e8 object-name: fix leaking symlink paths in object context
 5:  794af66103 =  5:  454139e7a4 bulk-checkin: fix leaking state TODO
 6:  2810cada0a =  6:  f8b7195796 read-cache: fix leaking hashfile when writing index fails
 7:  03f699cf39 =  7:  762fb5aa73 submodule-config: fix leaking name enrty when traversing submodules
 8:  a34c90a552 !  8:  8fbd72a100 config: fix leaking comment character config
    @@ Commit message
         without free'ing the previous value. In fact, it can't easily free the
         value in the first place because it may contain a string constant.
     
    -    Refactor the code so that we initialize the value with another array.
    -    This allows us to free the value in case the string is not pointing to
    -    that constant array anymore.
    +    Refactor the code such that we track allocated comment character strings
    +    via a separate non-constant variable `comment_line_str_allocated`. Adapt
    +    sites that set `comment_line_str` to set both and free the old value
    +    that was stored in `comment_line_str_allocated`.
     
         This memory leak is being hit in t3404. As there are still other memory
         leaks in that file we cannot yet mark it as passing with leak checking
    @@ Commit message
     
         Signed-off-by: Patrick Steinhardt <[email-masked]>
     
    + ## builtin/commit.c ##
    +@@ builtin/commit.c: static void adjust_comment_line_char(const struct strbuf *sb)
    + 	const char *p;
    + 
    + 	if (!memchr(sb->buf, candidates[0], sb->len)) {
    +-		comment_line_str = xstrfmt("%c", candidates[0]);
    ++		free(comment_line_str_allocated);
    ++		comment_line_str = comment_line_str_allocated =
    ++			xstrfmt("%c", candidates[0]);
    + 		return;
    + 	}
    + 
    +@@ builtin/commit.c: static void adjust_comment_line_char(const struct strbuf *sb)
    + 	if (!*p)
    + 		die(_("unable to select a comment character that is not used\n"
    + 		      "in the current commit message"));
    +-	comment_line_str = xstrfmt("%c", *p);
    ++	free(comment_line_str_allocated);
    ++	comment_line_str = comment_line_str_allocated = xstrfmt("%c", *p);
    + }
    + 
    + static void prepare_amend_commit(struct commit *commit, struct strbuf *sb,
    +
      ## config.c ##
     @@ config.c: static int git_default_core_config(const char *var, const char *value,
      		else if (value[0]) {
      			if (strchr(value, '\n'))
      				return error(_("%s cannot contain newline"), var);
    -+			if (comment_line_str != comment_line_str_default)
    -+				free((char *) comment_line_str);
    - 			comment_line_str = xstrdup(value);
    +-			comment_line_str = xstrdup(value);
    ++			free(comment_line_str_allocated);
    ++			comment_line_str = comment_line_str_allocated =
    ++				xstrdup(value);
      			auto_comment_line_char = 0;
      		} else
    + 			return error(_("%s must have at least one character"), var);
     
      ## environment.c ##
     @@ environment.c: int protect_ntfs = PROTECT_NTFS_DEFAULT;
    -  * The character that begins a commented line in user-editable file
       * that is subject to stripspace.
       */
    --const char *comment_line_str = "#";
    -+const char comment_line_str_default[] = "#";
    -+const char *comment_line_str = comment_line_str_default;
    + const char *comment_line_str = "#";
    ++char *comment_line_str_allocated;
      int auto_comment_line_char;
      
      /* Parallel index stat data preload? */
     
      ## environment.h ##
     @@ environment.h: struct strvec;
    -  * The character that begins a commented line in user-editable file
       * that is subject to stripspace.
       */
    -+extern const char comment_line_str_default[];
      extern const char *comment_line_str;
    ++extern char *comment_line_str_allocated;
      extern int auto_comment_line_char;
      
    + /*
 9:  05290fc1f1 !  9:  e497b76e9c builtin/rebase: fix leaking `commit.gpgsign` value
    @@ Metadata
      ## Commit message ##
         builtin/rebase: fix leaking `commit.gpgsign` value
     
    -    In `get_replay_opts()`, we unconditionally override the `gpg_sign` field
    -    that already got populated by `sequencer_init_config()` in case the user
    -    has "commit.gpgsign" set in their config. It is kind of dubious whether
    -    this is the correct thing to do or a bug. What is clear though is that
    -    this creates a memory leak.
    +    In `get_replay_opts()`, we override the `gpg_sign` field that already
    +    got populated by `sequencer_init_config()` in case the user has
    +    "commit.gpgsign" set in their config. This creates a memory leak because
    +    we overwrite the previously assigned value, which may have already
    +    pointed to an allocated string.
     
    -    Let's mark this assignment with a TODO comment to figure out whether
    -    this needs to be fixed or not. Meanwhile though, let's plug the memory
    -    leak.
    +    Let's plug the memory leak by freeing the value before we overwrite it.
     
         Signed-off-by: Patrick Steinhardt <[email-masked]>
     
    @@ builtin/rebase.c: static struct replay_opts get_replay_opts(const struct rebase_
      	replay.committer_date_is_author_date =
      					opts->committer_date_is_author_date;
      	replay.ignore_date = opts->ignore_date;
    -+
    -+	/*
    -+	 * TODO: Is it really intentional that we unconditionally override
    -+	 * `replay.gpg_sign` even if it has already been initialized via the
    -+	 * configuration?
    -+	 */
     +	free(replay.gpg_sign);
      	replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);
    -+
      	replay.reflog_action = xstrdup(opts->reflog_action);
      	if (opts->strategy)
    - 		replay.strategy = xstrdup_or_null(opts->strategy);
     
      ## sequencer.c ##
     @@ sequencer.c: static int git_sequencer_config(const char *k, const char *v,
10:  4f5d490074 = 10:  c886b666f7 builtin/notes: fix leaking `struct notes_tree` when merging notes
11:  798b911f77 = 11:  d1c757157b builtin/fast-import: plug trivial memory leaks
12:  660732d29d = 12:  fa2d5c5d6b builtin/fast-export: fix leaking diff options
13:  64366155de = 13:  d9dd860d2a builtin/fast-export: plug leaking tag names
14:  b12015b3c3 = 14:  8f6860485e merge-ort: unconditionally release attributes index
15:  df4c21b49f ! 15:  ea6a350f31 sequencer: release todo list on error paths
    @@ sequencer.c: int sequencer_pick_revisions(struct repository *r,
      									&oid,
      									NULL);
     -				return error(_("%s: can't cherry-pick a %s"),
    +-					name, type_name(type));
     +				res = error(_("%s: can't cherry-pick a %s"),
    - 					name, type_name(type));
    ++					    name, type_name(type));
     +				goto out;
      			}
     -		} else
16:  1f8553fd43 = 16:  2755023742 unpack-trees: clear index when not propagating it
17:  c6db8df324 = 17:  edf6f148cd diff: fix leak when parsing invalid ignore regex option
18:  bf818a8a79 = 18:  343e3bd4df builtin/format-patch: fix various trivial memory leaks
19:  ef780aa360 = 19:  be2c5b0bca userdiff: fix leaking memory for configured diff drivers
20:  f3882986a3 = 20:  7888203833 builtin/log: fix leak when showing converted blob contents
21:  a49bb2e0cc = 21:  245fc30afb diff: free state populated via options
22:  fb52599404 = 22:  343ddcd17b builtin/diff: free symmetric diff members
-- 
2.46.0.46.g406f326d27.dirty