threads / patch / 61561

patch, 19 partsCompile with `-Wwrite-strings`

Subject: [PATCH 00/19] Compile with `-Wwrite-strings`

## tl;dr

205 messages between May 29, 2024 and Jun 8, 2024. Diffs are folded; open one to read it.

replies: 204people: 5as markdown or json

Patrick Steinhardt· May 29, 2024, 12:44 UTC · lore
Hi,

there were some recent discussions about compiler warnings and how to stay on top of breaking changes in compilers in general [1] and about string constants in particular [2]. This made me look into what kind of warnings we should reasonably enable, which led me to the following list of warnings that may be sensible:

  - `-Wformat-nonliteral` to warn about non-constant strings being
    passed as format string.
  - `-Wwrite-strings` to warn about string constants being assigned to a
    non-constant variable.
  - `-Wredundant-decls` to warn about redundant declarations.
  - `-Wconversion` to warn about implicit integer casts when they may
    alter the value.

This patch series adapts our code to compile with `-Wwrite-strings`. This option will change the type of string constants from `char []` to `const char []` such that it is now invalid to assign it to non-const variables without a cast. The intent is to avoid undefined behaviour when accedintally writing to such strings and to avoid free'ing such a variable.

There are quite some cases where we mishandle this. Oftentimes we just didn't bother to free any memory at all, which made it a non-issue in the first place. Other times we had some special logic that prevents writing or freeing such strings. But in most cases it was an accident waiting to happen.

Even though the changes are quite invasive, I think that this is a step into the right direction. Many of the constructs feel quite fragile, and most of those get fixed in this series. Some others I just paper over, for example when assigning to structures with global lifetime where we know that they are never released at all.

I also have a patch series cooking for `-Wredundant-decls`. But while that warning detects some redundant declarations indeed, it creates a problem with `extern char **environ`. There is no header for it and programs are asked to declare it by themselves. But of course, some libc implementations disagree and declare it. I haven't found a nice way to work around this issue, but may send the patches that drop the redundant declarations nonetheless.

The other two warnings I haven't yet looked into.

I ran some test jobs on both GitHub [3] and GitLab [4] to verify that the result is sane.

Thanks!
Patrick

[1]: <xmqqbk5bim2n.fsf@gitster.g> [2]: <20240525043347.GA1895047@coredump.intra.peff.net> [3]: https://github.com/git/git/pull/1730 [4]: https://gitlab.com/gitlab-org/git/-/pipelines/1310156791

Patrick Steinhardt (19):
  global: improve const correctness when assigning string constants
  global: assign non-const strings as required
  global: convert intentionally-leaking config strings to consts
  compat/win32: fix const-correctness with string constants
  reftable: improve const correctness when assigning string constants
  refspec: remove global tag refspec structure
  http: do not assign string constant to non-const field
  line-log: always allocate the output prefix
  object-file: make `buf` parameter of `index_mem()` a constant
  parse-options: cast long name for OPTION_ALIAS
  send-pack: always allocate receive status
  remote-curl: avoid assigning string constant to non-const variable
  revision: always store allocated strings in output encoding
  mailmap: always store allocated strings in mailmap blob
  imap-send: drop global `imap_server_conf` variable
  imap-send: fix leaking memory in `imap_server_conf`
  builtin/rebase: adapt code to not assign string constants to non-const
  builtin/merge: always store allocated strings in `pull_twohead`
  config.mak.dev: enable `-Wwrite-strings` warning
 builtin/bisect.c             |   3 +-
 builtin/blame.c              |   2 +-
 builtin/bugreport.c          |   2 +-
 builtin/check-ignore.c       |   4 +-
 builtin/clone.c              |  14 ++--
 builtin/commit.c             |   6 +-
 builtin/diagnose.c           |   2 +-
 builtin/fetch.c              |  11 ++-
 builtin/log.c                |   2 +-
 builtin/mailsplit.c          |   4 +-
 builtin/merge.c              |  18 +++--
 builtin/pull.c               |  52 +++++++-------
 builtin/rebase.c             |  16 +++--
 builtin/receive-pack.c       |   4 +-
 builtin/remote.c             |   3 +-
 builtin/revert.c             |   2 +-
 builtin/send-pack.c          |   2 +
 compat/basename.c            |  15 +++-
 compat/mingw.c               |  25 +++----
 compat/regex/regcomp.c       |   2 +-
 compat/winansi.c             |   2 +-
 config.mak.dev               |   1 +
 diff.c                       |   7 +-
 diffcore-rename.c            |   6 +-
 entry.c                      |   7 +-
 fmt-merge-msg.c              |   2 +-
 fsck.c                       |   2 +-
 fsck.h                       |   2 +-
 gpg-interface.c              |   6 +-
 http-backend.c               |   2 +-
 http.c                       |   5 +-
 ident.c                      |   9 ++-
 imap-send.c                  | 133 ++++++++++++++++++++---------------
 line-log.c                   |  21 +++---
 mailmap.c                    |   2 +-
 merge-ll.c                   |  11 ++-
 object-file.c                |  17 ++---
 parse-options.h              |   2 +-
 pretty.c                     |   7 +-
 refs.c                       |   2 +-
 refs.h                       |   2 +-
 refs/reftable-backend.c      |   5 +-
 refspec.c                    |  13 ----
 refspec.h                    |   1 -
 reftable/basics_test.c       |   4 +-
 reftable/block_test.c        |   2 +-
 reftable/merged_test.c       |  45 ++++++------
 reftable/readwrite_test.c    |  47 +++++++------
 reftable/record.c            |   6 +-
 reftable/stack_test.c        |  65 ++++++++---------
 remote-curl.c                |  58 ++++++++-------
 revision.c                   |   3 +-
 run-command.c                |   2 +-
 send-pack.c                  |   2 +-
 t/helper/test-hashmap.c      |   3 +-
 t/helper/test-json-writer.c  |  10 +--
 t/helper/test-regex.c        |   4 +-
 t/helper/test-rot13-filter.c |   5 +-
 t/t3900-i18n-commit.sh       |   1 +
 t/t3901-i18n-patch.sh        |   1 +
 t/unit-tests/t-strbuf.c      |  10 +--
 trailer.c                    |   2 +-
 userdiff.c                   |  10 +--
 userdiff.h                   |  12 ++--
 wt-status.c                  |   2 +-
 65 files changed, 410 insertions(+), 340 deletions(-)
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 29, 2024, 12:44 UTC · re: Patrick Steinhardt · lore

[PATCH 01/19] global: improve const correctness when assigning string constants

We're about to enable `-Wwrite-strings`, which changes the type of string constants to `const char[]`. Fix various sites where we assign such constants to non-const variables.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/bisect.c             |  3 ++-
 builtin/blame.c              |  2 +-
 builtin/bugreport.c          |  2 +-
 builtin/check-ignore.c       |  4 +--
 builtin/clone.c              |  6 ++---
 builtin/commit.c             |  6 ++---
 builtin/diagnose.c           |  2 +-
 builtin/log.c                |  2 +-
 builtin/mailsplit.c          |  4 +--
 builtin/pull.c               | 52 ++++++++++++++++++------------------
 builtin/receive-pack.c       |  4 +--
 builtin/revert.c             |  2 +-
 compat/regex/regcomp.c       |  2 +-
 diff.c                       |  4 +--
 diffcore-rename.c            |  6 ++---
 fmt-merge-msg.c              |  2 +-
 fsck.c                       |  2 +-
 fsck.h                       |  2 +-
 gpg-interface.c              |  2 +-
 http-backend.c               |  2 +-
 imap-send.c                  |  6 ++---
 pretty.c                     |  2 +-
 refs.c                       |  2 +-
 refs.h                       |  2 +-
 reftable/record.c            |  6 ++---
 run-command.c                |  2 +-
 t/helper/test-hashmap.c      |  3 ++-
 t/helper/test-json-writer.c  | 10 +++----
 t/helper/test-regex.c        |  4 +--
 t/helper/test-rot13-filter.c |  5 ++--
 t/unit-tests/t-strbuf.c      | 10 ++++---
 trailer.c                    |  2 +-
 wt-status.c                  |  2 +-
 33 files changed, 86 insertions(+), 81 deletions(-)
Show changes to 33 files +86 −81

builtin/bisect.c, builtin/blame.c, builtin/bugreport.c, builtin/check-ignore.c, builtin/clone.c, builtin/commit.c, builtin/diagnose.c, builtin/log.c, builtin/mailsplit.c, builtin/pull.c, builtin/receive-pack.c, builtin/revert.c, compat/regex/regcomp.c, diff.c, diffcore-rename.c, fmt-merge-msg.c, fsck.c, fsck.h, gpg-interface.c, http-backend.c, imap-send.c, pretty.c, refs.c, refs.h, reftable/record.c, run-command.c, t/helper/test-hashmap.c, t/helper/test-json-writer.c, t/helper/test-regex.c, t/helper/test-rot13-filter.c, t/unit-tests/t-strbuf.c, trailer.c, wt-status.c

diff --git a/builtin/bisect.c b/builtin/bisect.c
index a58432b9d9..dabce9b542 100644
--- a/builtin/bisect.c
+++ b/builtin/bisect.c
@@ -262,7 +262,8 @@ static int bisect_reset(const char *commit)
 	return bisect_clean_state();
 }
 
-static void log_commit(FILE *fp, char *fmt, const char *state,
+static void log_commit(FILE *fp,
+		       const char *fmt, const char *state,
 		       struct commit *commit)
 {
 	struct pretty_print_context pp = {0};
diff --git a/builtin/blame.c b/builtin/blame.c
index 838cd476be..98c7629b6a 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -134,7 +134,7 @@ static void get_ac_line(const char *inbuf, const char *what,
 {
 	struct ident_split ident;
 	size_t len, maillen, namelen;
-	char *tmp, *endp;
+	const char *tmp, *endp;
 	const char *namebuf, *mailbuf;
 
 	tmp = strstr(inbuf, what);
diff --git a/builtin/bugreport.c b/builtin/bugreport.c
index 25f860a0d9..b3cc77af53 100644
--- a/builtin/bugreport.c
+++ b/builtin/bugreport.c
@@ -107,7 +107,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix)
 	struct tm tm;
 	enum diagnose_mode diagnose = DIAGNOSE_NONE;
 	char *option_output = NULL;
-	char *option_suffix = "%Y-%m-%d-%H%M";
+	const char *option_suffix = "%Y-%m-%d-%H%M";
 	const char *user_relative_path = NULL;
 	char *prefixed_filename;
 	size_t output_path_len;
diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c
index 6c43430ec4..2bda6a1d46 100644
--- a/builtin/check-ignore.c
+++ b/builtin/check-ignore.c
@@ -35,8 +35,8 @@ static const struct option check_ignore_options[] = {
 
 static void output_pattern(const char *path, struct path_pattern *pattern)
 {
-	char *bang  = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE)  ? "!" : "";
-	char *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? "/" : "";
+	const char *bang  = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE)  ? "!" : "";
+	const char *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? "/" : "";
 	if (!nul_term_line) {
 		if (!verbose) {
 			write_name_quoted(path, stdout, '\n');
diff --git a/builtin/clone.c b/builtin/clone.c
index 23993b905b..92ab7d7165 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -71,7 +71,7 @@ static char *option_branch = NULL;
 static struct string_list option_not = STRING_LIST_INIT_NODUP;
 static const char *real_git_dir;
 static const char *ref_format;
-static char *option_upload_pack = "git-upload-pack";
+static const char *option_upload_pack = "git-upload-pack";
 static int option_verbosity;
 static int option_progress = -1;
 static int option_sparse_checkout;
@@ -177,8 +177,8 @@ static struct option builtin_clone_options[] = {
 
 static const char *get_repo_path_1(struct strbuf *path, int *is_bundle)
 {
-	static char *suffix[] = { "/.git", "", ".git/.git", ".git" };
-	static char *bundle_suffix[] = { ".bundle", "" };
+	static const char *suffix[] = { "/.git", "", ".git/.git", ".git" };
+	static const char *bundle_suffix[] = { ".bundle", "" };
 	size_t baselen = path->len;
 	struct stat st;
 	int i;
diff --git a/builtin/commit.c b/builtin/commit.c
index f53e7e86ff..75c741173e 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,7 +113,7 @@ static char *template_file;
  * the commit message and/or authorship.
  */
 static const char *author_message, *author_message_buffer;
-static char *edit_message, *use_message;
+static const char *edit_message, *use_message;
 static char *fixup_message, *fixup_commit, *squash_message;
 static const char *fixup_prefix;
 static int all, also, interactive, patch_interactive, only, amend, signoff;
@@ -121,8 +121,8 @@ static int edit_flag = -1; /* unspecified */
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
-static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
-static char *sign_commit, *pathspec_from_file;
+static const char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
+static const char *sign_commit, *pathspec_from_file;
 static struct strvec trailer_args = STRVEC_INIT;
 
 /*
diff --git a/builtin/diagnose.c b/builtin/diagnose.c
index 4f22eb2b55..4857a4395b 100644
--- a/builtin/diagnose.c
+++ b/builtin/diagnose.c
@@ -18,7 +18,7 @@ int cmd_diagnose(int argc, const char **argv, const char *prefix)
 	struct tm tm;
 	enum diagnose_mode mode = DIAGNOSE_STATS;
 	char *option_output = NULL;
-	char *option_suffix = "%Y-%m-%d-%H%M";
+	const char *option_suffix = "%Y-%m-%d-%H%M";
 	char *prefixed_filename;
 
 	const struct option diagnose_options[] = {
diff --git a/builtin/log.c b/builtin/log.c
index 78a247d8a9..b8846a9458 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1283,7 +1283,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids)
 	o2->flags = flags2;
 }
 
-static void gen_message_id(struct rev_info *info, char *base)
+static void gen_message_id(struct rev_info *info, const char *base)
 {
 	struct strbuf buf = STRBUF_INIT;
 	strbuf_addf(&buf, "%s.%"PRItime".git.%s", base,
diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c
index 3af9ddb8ae..fe6dbc5d05 100644
--- a/builtin/mailsplit.c
+++ b/builtin/mailsplit.c
@@ -113,8 +113,8 @@ static int populate_maildir_list(struct string_list *list, const char *path)
 	DIR *dir;
 	struct dirent *dent;
 	char *name = NULL;
-	char *subs[] = { "cur", "new", NULL };
-	char **sub;
+	const char *subs[] = { "cur", "new", NULL };
+	const char **sub;
 	int ret = -1;
 
 	for (sub = subs; *sub; ++sub) {
diff --git a/builtin/pull.c b/builtin/pull.c
index d622202bce..2d0429f14f 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -71,48 +71,48 @@ static const char * const pull_usage[] = {
 
 /* Shared options */
 static int opt_verbosity;
-static char *opt_progress;
+static const char *opt_progress;
 static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
 static int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT;
 
 /* Options passed to git-merge or git-rebase */
 static enum rebase_type opt_rebase = -1;
-static char *opt_diffstat;
-static char *opt_log;
-static char *opt_signoff;
-static char *opt_squash;
-static char *opt_commit;
-static char *opt_edit;
-static char *cleanup_arg;
-static char *opt_ff;
-static char *opt_verify_signatures;
-static char *opt_verify;
+static const char *opt_diffstat;
+static const char *opt_log;
+static const char *opt_signoff;
+static const char *opt_squash;
+static const char *opt_commit;
+static const char *opt_edit;
+static const char *cleanup_arg;
+static const char *opt_ff;
+static const char *opt_verify_signatures;
+static const char *opt_verify;
 static int opt_autostash = -1;
 static int config_autostash;
 static int check_trust_level = 1;
 static struct strvec opt_strategies = STRVEC_INIT;
 static struct strvec opt_strategy_opts = STRVEC_INIT;
-static char *opt_gpg_sign;
+static const char *opt_gpg_sign;
 static int opt_allow_unrelated_histories;
 
 /* Options passed to git-fetch */
-static char *opt_all;
-static char *opt_append;
-static char *opt_upload_pack;
+static const char *opt_all;
+static const char *opt_append;
+static const char *opt_upload_pack;
 static int opt_force;
-static char *opt_tags;
-static char *opt_prune;
-static char *max_children;
+static const char *opt_tags;
+static const char *opt_prune;
+static const char *max_children;
 static int opt_dry_run;
-static char *opt_keep;
-static char *opt_depth;
-static char *opt_unshallow;
-static char *opt_update_shallow;
-static char *opt_refmap;
-static char *opt_ipv4;
-static char *opt_ipv6;
+static const char *opt_keep;
+static const char *opt_depth;
+static const char *opt_unshallow;
+static const char *opt_update_shallow;
+static const char *opt_refmap;
+static const char *opt_ipv4;
+static const char *opt_ipv6;
 static int opt_show_forced_updates = -1;
-static char *set_upstream;
+static const char *set_upstream;
 static struct strvec opt_fetch = STRVEC_INIT;
 
 static struct option pull_options[] = {
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 01c1f04ece..c8d12ee0a7 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1249,7 +1249,7 @@ static int run_proc_receive_hook(struct command *commands,
 	return code;
 }
 
-static char *refuse_unconfigured_deny_msg =
+static const char *refuse_unconfigured_deny_msg =
 	N_("By default, updating the current branch in a non-bare repository\n"
 	   "is denied, because it will make the index and work tree inconsistent\n"
 	   "with what you pushed, and will require 'git reset --hard' to match\n"
@@ -1269,7 +1269,7 @@ static void refuse_unconfigured_deny(void)
 	rp_error("%s", _(refuse_unconfigured_deny_msg));
 }
 
-static char *refuse_unconfigured_deny_delete_current_msg =
+static const char *refuse_unconfigured_deny_delete_current_msg =
 	N_("By default, deleting the current branch is denied, because the next\n"
 	   "'git clone' won't result in any file checked out, causing confusion.\n"
 	   "\n"
diff --git a/builtin/revert.c b/builtin/revert.c
index 53935d2c68..7bf2b4e11d 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -179,7 +179,7 @@ static int run_sequencer(int argc, const char **argv, const char *prefix,
 
 	/* Check for incompatible command line arguments */
 	if (cmd) {
-		char *this_operation;
+		const char *this_operation;
 		if (cmd == 'q')
 			this_operation = "--quit";
 		else if (cmd == 'c')
diff --git a/compat/regex/regcomp.c b/compat/regex/regcomp.c
index 2bc0f1187a..6c5d455e92 100644
--- a/compat/regex/regcomp.c
+++ b/compat/regex/regcomp.c
@@ -848,7 +848,7 @@ init_dfa (re_dfa_t *dfa, size_t pat_len)
 {
   unsigned int table_size;
 #ifndef _LIBC
-  char *codeset_name;
+  const char *codeset_name;
 #endif
 
   memset (dfa, '\0', sizeof (re_dfa_t));
diff --git a/diff.c b/diff.c
index e70301df76..ffd867ef6c 100644
--- a/diff.c
+++ b/diff.c
@@ -3764,7 +3764,7 @@ static void builtin_diff(const char *name_a,
 	return;
 }
 
-static char *get_compact_summary(const struct diff_filepair *p, int is_renamed)
+static const char *get_compact_summary(const struct diff_filepair *p, int is_renamed)
 {
 	if (!is_renamed) {
 		if (p->status == DIFF_STATUS_ADDED) {
@@ -4076,7 +4076,7 @@ static int reuse_worktree_file(struct index_state *istate,
 static int diff_populate_gitlink(struct diff_filespec *s, int size_only)
 {
 	struct strbuf buf = STRBUF_INIT;
-	char *dirty = "";
+	const char *dirty = "";
 
 	/* Are we looking at the work tree? */
 	if (s->dirty_submodule)
diff --git a/diffcore-rename.c b/diffcore-rename.c
index 5a6e2bcac7..0e1adb87df 100644
--- a/diffcore-rename.c
+++ b/diffcore-rename.c
@@ -406,7 +406,7 @@ static const char *get_highest_rename_path(struct strintmap *counts)
 	return highest_destination_dir;
 }
 
-static char *UNKNOWN_DIR = "/";  /* placeholder -- short, illegal directory */
+static const char *UNKNOWN_DIR = "/";  /* placeholder -- short, illegal directory */
 
 static int dir_rename_already_determinable(struct strintmap *counts)
 {
@@ -429,8 +429,8 @@ static int dir_rename_already_determinable(struct strintmap *counts)
 }
 
 static void increment_count(struct dir_rename_info *info,
-			    char *old_dir,
-			    char *new_dir)
+			    const char *old_dir,
+			    const char *new_dir)
 {
 	struct strintmap *counts;
 	struct strmap_entry *e;
diff --git a/fmt-merge-msg.c b/fmt-merge-msg.c
index 7d144b803a..5af63ab5ab 100644
--- a/fmt-merge-msg.c
+++ b/fmt-merge-msg.c
@@ -447,7 +447,7 @@ static void fmt_merge_msg_title(struct strbuf *out,
 				const char *current_branch)
 {
 	int i = 0;
-	char *sep = "";
+	const char *sep = "";
 
 	strbuf_addstr(out, "Merge ");
 	for (i = 0; i < srcs.nr; i++) {
diff --git a/fsck.c b/fsck.c
index 7dff41413e..61cd48aa25 100644
--- a/fsck.c
+++ b/fsck.c
@@ -1231,7 +1231,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size,
 }
 
 int fsck_buffer(const struct object_id *oid, enum object_type type,
-		void *data, unsigned long size,
+		const void *data, unsigned long size,
 		struct fsck_options *options)
 {
 	if (type == OBJ_BLOB)
diff --git a/fsck.h b/fsck.h
index 17fa2dda5d..4f0c4e6479 100644
--- a/fsck.h
+++ b/fsck.h
@@ -202,7 +202,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size,
  * struct.
  */
 int fsck_buffer(const struct object_id *oid, enum object_type,
-		void *data, unsigned long size,
+		const void *data, unsigned long size,
 		struct fsck_options *options);
 
 /*
diff --git a/gpg-interface.c b/gpg-interface.c
index 5193223714..71a9382a61 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -727,7 +727,7 @@ static int git_gpg_config(const char *var, const char *value,
 			  void *cb UNUSED)
 {
 	struct gpg_format *fmt = NULL;
-	char *fmtname = NULL;
+	const char *fmtname = NULL;
 	char *trust;
 	int ret;
 
diff --git a/http-backend.c b/http-backend.c
index 5b65287ac9..5b4dca65ed 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -753,7 +753,7 @@ static int bad_request(struct strbuf *hdr, const struct service_cmd *c)
 
 int cmd_main(int argc UNUSED, const char **argv UNUSED)
 {
-	char *method = getenv("REQUEST_METHOD");
+	const char *method = getenv("REQUEST_METHOD");
 	const char *proto_header;
 	char *dir;
 	struct service_cmd *cmd = NULL;
diff --git a/imap-send.c b/imap-send.c
index a5d1510180..8b723b34a5 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -1215,9 +1215,9 @@ static int imap_store_msg(struct imap_store *ctx, struct strbuf *msg)
 static void wrap_in_html(struct strbuf *msg)
 {
 	struct strbuf buf = STRBUF_INIT;
-	static char *content_type = "Content-Type: text/html;\n";
-	static char *pre_open = "<pre>\n";
-	static char *pre_close = "</pre>\n";
+	static const char *content_type = "Content-Type: text/html;\n";
+	static const char *pre_open = "<pre>\n";
+	static const char *pre_close = "</pre>\n";
 	const char *body = strstr(msg->buf, "\n\n");
 
 	if (!body)
diff --git a/pretty.c b/pretty.c
index 22a81506b7..ec05db5655 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1325,7 +1325,7 @@ int format_set_trailers_options(struct process_trailer_options *opts,
 static size_t parse_describe_args(const char *start, struct strvec *args)
 {
 	struct {
-		char *name;
+		const char *name;
 		enum {
 			DESCRIBE_ARG_BOOL,
 			DESCRIBE_ARG_INTEGER,
diff --git a/refs.c b/refs.c
index 8260c27cde..292e8d947e 100644
--- a/refs.c
+++ b/refs.c
@@ -159,7 +159,7 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref)
 {
 	struct ref_namespace_info *info = &ref_namespace[namespace];
 	if (info->ref_updated)
-		free(info->ref);
+		free((char *)info->ref);
 	info->ref = ref;
 	info->ref_updated = 1;
 }
diff --git a/refs.h b/refs.h
index 34568ee1fb..923f751d18 100644
--- a/refs.h
+++ b/refs.h
@@ -975,7 +975,7 @@ struct ref_store *get_worktree_ref_store(const struct worktree *wt);
  */
 
 struct ref_namespace_info {
-	char *ref;
+	const char *ref;
 	enum decoration_type decoration;
 
 	/*
diff --git a/reftable/record.c b/reftable/record.c
index 5506f3e913..a2cba5ef74 100644
--- a/reftable/record.c
+++ b/reftable/record.c
@@ -116,7 +116,7 @@ static int decode_string(struct strbuf *dest, struct string_view in)
 	return start_len - in.len;
 }
 
-static int encode_string(char *str, struct string_view s)
+static int encode_string(const char *str, struct string_view s)
 {
 	struct string_view start = s;
 	int l = strlen(str);
@@ -969,9 +969,9 @@ static int reftable_log_record_decode(void *rec, struct strbuf key,
 	return REFTABLE_FORMAT_ERROR;
 }
 
-static int null_streq(char *a, char *b)
+static int null_streq(const char *a, const char *b)
 {
-	char *empty = "";
+	const char *empty = "";
 	if (!a)
 		a = empty;
 
diff --git a/run-command.c b/run-command.c
index 1b821042b4..7600531fb6 100644
--- a/run-command.c
+++ b/run-command.c
@@ -663,7 +663,7 @@ int start_command(struct child_process *cmd)
 	int need_in, need_out, need_err;
 	int fdin[2], fdout[2], fderr[2];
 	int failed_errno;
-	char *str;
+	const char *str;
 
 	/*
 	 * In case of errors we must keep the promise to close FDs
diff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c
index 0eb0b3d49c..2912899558 100644
--- a/t/helper/test-hashmap.c
+++ b/t/helper/test-hashmap.c
@@ -36,7 +36,8 @@ static int test_entry_cmp(const void *cmp_data,
 }
 
 static struct test_entry *alloc_test_entry(unsigned int hash,
-					   char *key, char *value)
+					   const char *key,
+					   const char *value)
 {
 	size_t klen = strlen(key);
 	size_t vlen = strlen(value);
diff --git a/t/helper/test-json-writer.c b/t/helper/test-json-writer.c
index afe393f597..ed52eb76bf 100644
--- a/t/helper/test-json-writer.c
+++ b/t/helper/test-json-writer.c
@@ -174,7 +174,7 @@ static void make_arr4(int pretty)
 	jw_end(&arr4);
 }
 
-static char *expect_nest1 =
+static const char *expect_nest1 =
 	"{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true},\"arr1\":[\"abc\",42,true]}";
 
 static struct json_writer nest1 = JSON_WRITER_INIT;
@@ -195,10 +195,10 @@ static void make_nest1(int pretty)
 	jw_release(&arr1);
 }
 
-static char *expect_inline1 =
+static const char *expect_inline1 =
 	"{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true},\"arr1\":[\"abc\",42,true]}";
 
-static char *pretty_inline1 =
+static const char *pretty_inline1 =
 	("{\n"
 	 "  \"obj1\": {\n"
 	 "    \"a\": \"abc\",\n"
@@ -236,10 +236,10 @@ static void make_inline1(int pretty)
 	jw_end(&inline1);
 }
 
-static char *expect_inline2 =
+static const char *expect_inline2 =
 	"[[1,2],[3,4],{\"a\":\"abc\"}]";
 
-static char *pretty_inline2 =
+static const char *pretty_inline2 =
 	("[\n"
 	 "  [\n"
 	 "    1,\n"
diff --git a/t/helper/test-regex.c b/t/helper/test-regex.c
index 80042eafc2..366bd70976 100644
--- a/t/helper/test-regex.c
+++ b/t/helper/test-regex.c
@@ -20,8 +20,8 @@ static struct reg_flag reg_flags[] = {
 
 static int test_regex_bug(void)
 {
-	char *pat = "[^={} \t]+";
-	char *str = "={}\nfred";
+	const char *pat = "[^={} \t]+";
+	const char *str = "={}\nfred";
 	regex_t r;
 	regmatch_t m[1];
 
diff --git a/t/helper/test-rot13-filter.c b/t/helper/test-rot13-filter.c
index f8d564c622..7e1d9e0ee4 100644
--- a/t/helper/test-rot13-filter.c
+++ b/t/helper/test-rot13-filter.c
@@ -136,7 +136,7 @@ static void free_delay_entries(void)
 	strmap_clear(&delay, 0);
 }
 
-static void add_delay_entry(char *pathname, int count, int requested)
+static void add_delay_entry(const char *pathname, int count, int requested)
 {
 	struct delay_entry *entry = xcalloc(1, sizeof(*entry));
 	entry->count = count;
@@ -189,7 +189,8 @@ static void reply_list_available_blobs_cmd(void)
 static void command_loop(void)
 {
 	for (;;) {
-		char *buf, *output;
+		char *buf;
+		const char *output;
 		char *pathname;
 		struct delay_entry *entry;
 		struct strbuf input = STRBUF_INIT;
diff --git a/t/unit-tests/t-strbuf.c b/t/unit-tests/t-strbuf.c
index de434a4441..6027dafef7 100644
--- a/t/unit-tests/t-strbuf.c
+++ b/t/unit-tests/t-strbuf.c
@@ -2,7 +2,8 @@
 #include "strbuf.h"
 
 /* wrapper that supplies tests with an empty, initialized strbuf */
-static void setup(void (*f)(struct strbuf*, void*), void *data)
+static void setup(void (*f)(struct strbuf*, const void*),
+		  const void *data)
 {
 	struct strbuf buf = STRBUF_INIT;
 
@@ -13,7 +14,8 @@ static void setup(void (*f)(struct strbuf*, void*), void *data)
 }
 
 /* wrapper that supplies tests with a populated, initialized strbuf */
-static void setup_populated(void (*f)(struct strbuf*, void*), char *init_str, void *data)
+static void setup_populated(void (*f)(struct strbuf*, const void*),
+			    const char *init_str, const void *data)
 {
 	struct strbuf buf = STRBUF_INIT;
 
@@ -64,7 +66,7 @@ static void t_dynamic_init(void)
 	strbuf_release(&buf);
 }
 
-static void t_addch(struct strbuf *buf, void *data)
+static void t_addch(struct strbuf *buf, const void *data)
 {
 	const char *p_ch = data;
 	const char ch = *p_ch;
@@ -83,7 +85,7 @@ static void t_addch(struct strbuf *buf, void *data)
 	check_char(buf->buf[buf->len], ==, '\0');
 }
 
-static void t_addstr(struct strbuf *buf, void *data)
+static void t_addstr(struct strbuf *buf, const void *data)
 {
 	const char *text = data;
 	size_t len = strlen(text);
diff --git a/trailer.c b/trailer.c
index 2bcb9ba8f7..72e5136c73 100644
--- a/trailer.c
+++ b/trailer.c
@@ -63,7 +63,7 @@ struct arg_item {
 
 static LIST_HEAD(conf_head);
 
-static char *separators = ":";
+static const char *separators = ":";
 
 static int configured;
 
diff --git a/wt-status.c b/wt-status.c
index ff4be071ca..7912545e4e 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -2408,7 +2408,7 @@ static void wt_porcelain_v2_print_unmerged_entry(
 		int mode;
 		struct object_id oid;
 	} stages[3];
-	char *key;
+	const char *key;
 	char submodule_token[5];
 	char unmerged_prefix = 'u';
 	char eol_char = s->null_termination ? '\0' : '\n';
-- 
2.45.1.313.g3a57aa566a.dirty
Junio C Hamano· May 29, 2024, 16:58 UTC · re: Patrick Steinhardt · lore

Re: [PATCH 01/19] global: improve const correctness when assigning string constants

Patrick Steinhardt <ps@pks.im> writes:
Show 6 quoted lines
> We're about to enable `-Wwrite-strings`, which changes the type of
> string constants to `const char[]`. Fix various sites where we assign
> such constants to non-const variables.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
This is a noisy change, and it is kind of surprising that ...
Show 14 quoted lines
> diff --git a/builtin/bisect.c b/builtin/bisect.c
> index a58432b9d9..dabce9b542 100644
> --- a/builtin/bisect.c
> +++ b/builtin/bisect.c
> @@ -262,7 +262,8 @@ static int bisect_reset(const char *commit)
>  	return bisect_clean_state();
>  }
>  
> -static void log_commit(FILE *fp, char *fmt, const char *state,
> +static void log_commit(FILE *fp,
> +		       const char *fmt, const char *state,
>  		       struct commit *commit)
>  {
>  	struct pretty_print_context pp = {0};

... a change like this does not require any other change to the code inside the function (e.g., by making further cascading changes to what the function calls).

But applying this step alone and building would not give us any constness warning/error from the compiler, so the result looks good to the compiler, I guess?

Thanks.
Patrick Steinhardt· May 30, 2024, 11:29 UTC · re: Junio C Hamano · lore

Re: [PATCH 01/19] global: improve const correctness when assigning string constants

On Wed, May 29, 2024 at 09:58:56AM -0700, Junio C Hamano wrote:
Show 33 quoted lines
> Patrick Steinhardt <ps@pks.im> writes:
> 
> > We're about to enable `-Wwrite-strings`, which changes the type of
> > string constants to `const char[]`. Fix various sites where we assign
> > such constants to non-const variables.
> >
> > Signed-off-by: Patrick Steinhardt <ps@pks.im>
> > ---
> 
> This is a noisy change, and it is kind of surprising that ...
> 
> > diff --git a/builtin/bisect.c b/builtin/bisect.c
> > index a58432b9d9..dabce9b542 100644
> > --- a/builtin/bisect.c
> > +++ b/builtin/bisect.c
> > @@ -262,7 +262,8 @@ static int bisect_reset(const char *commit)
> >  	return bisect_clean_state();
> >  }
> >  
> > -static void log_commit(FILE *fp, char *fmt, const char *state,
> > +static void log_commit(FILE *fp,
> > +		       const char *fmt, const char *state,
> >  		       struct commit *commit)
> >  {
> >  	struct pretty_print_context pp = {0};
> 
> ... a change like this does not require any other change to the
> code inside the function (e.g., by making further cascading changes
> to what the function calls).
> 
> But applying this step alone and building would not give us any
> constness warning/error from the compiler, so the result looks good
> to the compiler, I guess?
Yup, any intermediate commit builds with DEVELOPER=1.
Patrick
Patrick Steinhardt· May 29, 2024, 12:44 UTC · re: Patrick Steinhardt · lore

[PATCH 02/19] global: assign non-const strings as required

There are several cases where we initialize non-const fields with string constants. This is invalid and will cause warnings once we enable the `-Wwrite-strings` warning. Adapt those cases to instead use string arrays.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/remote.c        | 3 ++-
 diff.c                  | 3 ++-
 entry.c                 | 7 ++++---
 ident.c                 | 9 +++++++--
 line-log.c              | 3 ++-
 object-file.c           | 3 ++-
 pretty.c                | 5 +++--
 refs/reftable-backend.c | 5 +++--
 8 files changed, 25 insertions(+), 13 deletions(-)
Show changes to 8 files +25 −13

builtin/remote.c, diff.c, entry.c, ident.c, line-log.c, object-file.c, pretty.c, refs/reftable-backend.c

diff --git a/builtin/remote.c b/builtin/remote.c
index d52b1c0e10..0324a5d48d 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -494,11 +494,12 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat
 	struct ref *ref, *matches;
 	struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;
 	struct refspec_item refspec;
+	char refspec_str[] = "refs/heads/*";
 
 	memset(&refspec, 0, sizeof(refspec));
 	refspec.force = 0;
 	refspec.pattern = 1;
-	refspec.src = refspec.dst = "refs/heads/*";
+	refspec.src = refspec.dst = refspec_str;
 	get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
 	matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
 				    fetch_map, 1);
diff --git a/diff.c b/diff.c
index ffd867ef6c..1439a5a01d 100644
--- a/diff.c
+++ b/diff.c
@@ -7231,11 +7231,12 @@ size_t fill_textconv(struct repository *r,
 		     struct diff_filespec *df,
 		     char **outbuf)
 {
+	static char empty_str[] = "";
 	size_t size;
 
 	if (!driver) {
 		if (!DIFF_FILE_VALID(df)) {
-			*outbuf = "";
+			*outbuf = empty_str;
 			return 0;
 		}
 		if (diff_populate_filespec(r, df, NULL))
diff --git a/entry.c b/entry.c
index b8c257f6f9..2fc06ac90c 100644
--- a/entry.c
+++ b/entry.c
@@ -175,6 +175,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 	struct string_list_item *filter, *path;
 	struct progress *progress = NULL;
 	struct delayed_checkout *dco = state->delayed_checkout;
+	char empty_str[] = "";
 
 	if (!state->delayed_checkout)
 		return errs;
@@ -189,7 +190,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 			if (!async_query_available_blobs(filter->string, &available_paths)) {
 				/* Filter reported an error */
 				errs = 1;
-				filter->string = "";
+				filter->string = empty_str;
 				continue;
 			}
 			if (available_paths.nr <= 0) {
@@ -199,7 +200,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 				 * filter from the list (see
 				 * "string_list_remove_empty_items" call below).
 				 */
-				filter->string = "";
+				filter->string = empty_str;
 				continue;
 			}
 
@@ -225,7 +226,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 					 * Do not ask the filter for available blobs,
 					 * again, as the filter is likely buggy.
 					 */
-					filter->string = "";
+					filter->string = empty_str;
 					continue;
 				}
 				ce = index_file_exists(state->istate, path->string,
diff --git a/ident.c b/ident.c
index cc7afdbf81..df7aa42802 100644
--- a/ident.c
+++ b/ident.c
@@ -46,9 +46,14 @@ static struct passwd *xgetpwuid_self(int *is_bogus)
 	pw = getpwuid(getuid());
 	if (!pw) {
 		static struct passwd fallback;
-		fallback.pw_name = "unknown";
+		static char fallback_name[] = "unknown";
 #ifndef NO_GECOS_IN_PWENT
-		fallback.pw_gecos = "Unknown";
+		static char fallback_gcos[] = "Unknown";
+#endif
+
+		fallback.pw_name = fallback_name;
+#ifndef NO_GECOS_IN_PWENT
+		fallback.pw_gecos = fallback_gcos;
 #endif
 		pw = &fallback;
 		if (is_bogus)
diff --git a/line-log.c b/line-log.c
index 8ff6ccb772..d9bf2c8120 100644
--- a/line-log.c
+++ b/line-log.c
@@ -1032,6 +1032,7 @@ static int process_diff_filepair(struct rev_info *rev,
 	struct range_set tmp;
 	struct diff_ranges diff;
 	mmfile_t file_parent, file_target;
+	char empty_str[] = "";
 
 	assert(pair->two->path);
 	while (rg) {
@@ -1056,7 +1057,7 @@ static int process_diff_filepair(struct rev_info *rev,
 		file_parent.ptr = pair->one->data;
 		file_parent.size = pair->one->size;
 	} else {
-		file_parent.ptr = "";
+		file_parent.ptr = empty_str;
 		file_parent.size = 0;
 	}
 
diff --git a/object-file.c b/object-file.c
index 610b1f465c..c9e374e57e 100644
--- a/object-file.c
+++ b/object-file.c
@@ -282,12 +282,13 @@ static struct cached_object {
 } *cached_objects;
 static int cached_object_nr, cached_object_alloc;
 
+static char empty_tree_buf[] = "";
 static struct cached_object empty_tree = {
 	.oid = {
 		.hash = EMPTY_TREE_SHA1_BIN_LITERAL,
 	},
 	.type = OBJ_TREE,
-	.buf = "",
+	.buf = empty_tree_buf,
 };
 
 static struct cached_object *find_cached_object(const struct object_id *oid)
diff --git a/pretty.c b/pretty.c
index ec05db5655..1a0030b32a 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1583,9 +1583,10 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
 		return 1;
 	case 'D':
 		{
+			char empty_str[] = "";
 			const struct decoration_options opts = {
-				.prefix = "",
-				.suffix = ""
+				.prefix = empty_str,
+				.suffix = empty_str,
 			};
 
 			format_decorations(sb, commit, c->auto_color, &opts);
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 1af86bbdec..1908e74dea 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1285,6 +1285,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 	struct strbuf errbuf = STRBUF_INIT;
 	size_t logs_nr = 0, logs_alloc = 0, i;
 	const char *committer_info;
+	char head[] = "HEAD";
 	int ret;
 
 	committer_info = git_committer_info(0);
@@ -1387,7 +1388,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 		if (append_head_reflog) {
 			ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 			logs[logs_nr] = logs[logs_nr - 1];
-			logs[logs_nr].refname = "HEAD";
+			logs[logs_nr].refname = head;
 			logs_nr++;
 		}
 	}
@@ -1463,7 +1464,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 	string_list_clear(&skip, 0);
 	strbuf_release(&errbuf);
 	for (i = 0; i < logs_nr; i++) {
-		if (!strcmp(logs[i].refname, "HEAD"))
+		if (logs[i].refname == head)
 			continue;
 		logs[i].refname = NULL;
 		reftable_log_record_release(&logs[i]);
-- 
2.45.1.313.g3a57aa566a.dirty
Junio C Hamano· May 29, 2024, 17:25 UTC · re: Patrick Steinhardt · lore

Re: [PATCH 02/19] global: assign non-const strings as required

Patrick Steinhardt <ps@pks.im> writes:
Show 10 quoted lines
> +	char refspec_str[] = "refs/heads/*";
>  
>  	memset(&refspec, 0, sizeof(refspec));
>  	refspec.force = 0;
>  	refspec.pattern = 1;
> -	refspec.src = refspec.dst = "refs/heads/*";
> +	refspec.src = refspec.dst = refspec_str;
>  	get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
>  	matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
>  				    fetch_map, 1);

I have no objections to changes along this line, but this makes me wonder if we somehow have ways to ensure that after everything is done, refspec_str[], empty_str[], and the like in other hunks remain to have their initial contents. Stepping back a bit, without applying this series, if we told the compiler to store these constant strings in read-only segment, any attempt to write into refspec.src or refspec.dst string would have been caught at runtime as an error. With the patch, that would no longer work. The piece of memory in refspec_str[] is a fair game to be overwritten by anybody.

Which makes me wonder why these refspec.src and refspec.dst members are not "const char *" pointers in the first place? Obviously we do not expect "refs/heads/*" to be overwritten after storing the pointer to it in these members and making the get_fetch_map() call.

Patrick Steinhardt· May 30, 2024, 11:29 UTC · re: Junio C Hamano · lore

Re: [PATCH 02/19] global: assign non-const strings as required

On Wed, May 29, 2024 at 10:25:47AM -0700, Junio C Hamano wrote:
Show 23 quoted lines
> Patrick Steinhardt <ps@pks.im> writes:
> 
> > +	char refspec_str[] = "refs/heads/*";
> >  
> >  	memset(&refspec, 0, sizeof(refspec));
> >  	refspec.force = 0;
> >  	refspec.pattern = 1;
> > -	refspec.src = refspec.dst = "refs/heads/*";
> > +	refspec.src = refspec.dst = refspec_str;
> >  	get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
> >  	matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
> >  				    fetch_map, 1);
> 
> I have no objections to changes along this line, but this makes me
> wonder if we somehow have ways to ensure that after everything is
> done, refspec_str[], empty_str[], and the like in other hunks remain
> to have their initial contents.  Stepping back a bit, without
> applying this series, if we told the compiler to store these
> constant strings in read-only segment, any attempt to write into
> refspec.src or refspec.dst string would have been caught at runtime
> as an error.  With the patch, that would no longer work.  The piece
> of memory in refspec_str[] is a fair game to be overwritten by
> anybody.

True. Ideally, we wouldn't have to do these acrobatics here in the first place. That would likely require quite a lot more work and go beyond the scope of this patch series.

> Which makes me wonder why these refspec.src and refspec.dst members
> are not "const char *" pointers in the first place?  Obviously we do
> not expect "refs/heads/*" to be overwritten after storing the pointer
> to it in these members and making the get_fetch_map() call.

Well, we do. Not in `get_fetch_map()`, but in `query_refspecs()`. It does weird stuff where it writes the result into either `src` or `dst` depending on which of these fields is provided by the caller. Which means that one of the fields would typically be a constant, whereas the other one will be allocated.

Really, once you start looking you find all kinds of weird interfaces where we cannot quite make up our mind whether fields are controlled by the caller or by the callee. This patch series certainly surfaces quite some places that left me scratching my head.

Patrick
Junio C Hamano· May 30, 2024, 19:38 UTC · re: Patrick Steinhardt · lore

Re: [PATCH 02/19] global: assign non-const strings as required

Patrick Steinhardt <ps@pks.im> writes:
Show 5 quoted lines
> Well, we do. Not in `get_fetch_map()`, but in `query_refspecs()`. It
> does weird stuff where it writes the result into either `src` or `dst`
> depending on which of these fields is provided by the caller. Which
> means that one of the fields would typically be a constant, whereas the
> other one will be allocated.

Yes, <src, dst> is used as a pair of <key, value> to query one with the other (i.e. "where does this one go?" "where does this one come from?").

But we are not talking about const-ness of the member (iow, once you point a string with the member, you cannot repoint the pointer to another string), but we are talking about const-ness of the string that is pointed by the member (iow, not "char const *src" but "const char *src"), no? If I ask "I have this src, where does it go?" with a refspec element filled with src, the dst member may need to be updated to point at the string that is the answer of the query, but that still can be done with "const char *src, *dst", can't it? That was what I was wondering.

And again you are conflating "allocated" with "read-write" here. It is often convenient if a variable that points at an allocated string is of type "char *" and not "const char *", because you do not cast it when calling free(). But if you want to make a structure member or a variable that holds an allocated string responsible for _owning_ the piece of memory, then you need to consistently have the member point at an allocated piece of memory (or NULL), no? What this patch does, i.e. prepare an on-stack refspec_str[] array that is initialized from a constant string, and have .src member point at it, would not make .src freeable. In other words, .src pointing at an allocated piece of string "some of the time" alone is not a good justification to make it a non-const pointer, I would think.

Patrick Steinhardt· May 31, 2024, 13:00 UTC · re: Junio C Hamano · lore

Re: [PATCH 02/19] global: assign non-const strings as required

On Thu, May 30, 2024 at 12:38:45PM -0700, Junio C Hamano wrote:
Show 21 quoted lines
> Patrick Steinhardt <ps@pks.im> writes:
> 
> > Well, we do. Not in `get_fetch_map()`, but in `query_refspecs()`. It
> > does weird stuff where it writes the result into either `src` or `dst`
> > depending on which of these fields is provided by the caller. Which
> > means that one of the fields would typically be a constant, whereas the
> > other one will be allocated.
> 
> Yes, <src, dst> is used as a pair of <key, value> to query one with
> the other (i.e. "where does this one go?" "where does this one come
> from?").
> 
> But we are not talking about const-ness of the member (iow, once you
> point a string with the member, you cannot repoint the pointer to
> another string), but we are talking about const-ness of the string
> that is pointed by the member (iow, not "char const *src" but "const
> char *src"), no?  If I ask "I have this src, where does it go?" with
> a refspec element filled with src, the dst member may need to be
> updated to point at the string that is the answer of the query, but
> that still can be done with "const char *src, *dst", can't it?  That
> was what I was wondering.

Well, yes, the pointers can be updated. But that doesn't solve the underlying problem that the interface is not well-prepared to track ownership of its inputs and outputs.

The root problem is that `struct refspec_item` is being abused for multiple purposes. One of these purposes it to store parsed refspec items. Another purpose is to actually serve as a query. The requirements for those two purposes regarding the lifetime of associated strings is different:

  - When using it for storage purposes the strings should be owned by
    the structure itself. Consequently, the lifetime should be managed
    by the structure too, namely via `refspec_item_clear()`.
  - When using it for parsing purposes the input string is owned by the
    caller, whereas the output string is owned by the callee and then
    handed over to the caller. Here we cannot rely on the structure
    itself to manage the lifetimes of both strings, at least not in the
    current form.

Now of course, we could amend `struct refspec_item` to grow two additional fields `src_to_free` and `dst_to_free`. But that only doubles down on this misdesigned interface.

The proper solution in my opinion is to detangle those two usecases and split them out into two independent interfaces.

Show 12 quoted lines
> And again you are conflating "allocated" with "read-write" here.  It
> is often convenient if a variable that points at an allocated string
> is of type "char *" and not "const char *", because you do not cast
> it when calling free().  But if you want to make a structure member
> or a variable that holds an allocated string responsible for
> _owning_ the piece of memory, then you need to consistently have the
> member point at an allocated piece of memory (or NULL), no?  What
> this patch does, i.e. prepare an on-stack refspec_str[] array that
> is initialized from a constant string, and have .src member point at
> it, would not make .src freeable.  In other words, .src pointing at
> an allocated piece of string "some of the time" alone is not a good
> justification to make it a non-const pointer, I would think.

That's fair enough. I'm trying to work around a broken interface, but do not solve the underlying issue.

Patrick
Patrick Steinhardt· May 31, 2024, 13:33 UTC · re: Patrick Steinhardt · lore

Re: [PATCH 02/19] global: assign non-const strings as required

On Fri, May 31, 2024 at 03:00:36PM +0200, Patrick Steinhardt wrote:
Show 67 quoted lines
> On Thu, May 30, 2024 at 12:38:45PM -0700, Junio C Hamano wrote:
> > Patrick Steinhardt <ps@pks.im> writes:
> > 
> > > Well, we do. Not in `get_fetch_map()`, but in `query_refspecs()`. It
> > > does weird stuff where it writes the result into either `src` or `dst`
> > > depending on which of these fields is provided by the caller. Which
> > > means that one of the fields would typically be a constant, whereas the
> > > other one will be allocated.
> > 
> > Yes, <src, dst> is used as a pair of <key, value> to query one with
> > the other (i.e. "where does this one go?" "where does this one come
> > from?").
> > 
> > But we are not talking about const-ness of the member (iow, once you
> > point a string with the member, you cannot repoint the pointer to
> > another string), but we are talking about const-ness of the string
> > that is pointed by the member (iow, not "char const *src" but "const
> > char *src"), no?  If I ask "I have this src, where does it go?" with
> > a refspec element filled with src, the dst member may need to be
> > updated to point at the string that is the answer of the query, but
> > that still can be done with "const char *src, *dst", can't it?  That
> > was what I was wondering.
> 
> Well, yes, the pointers can be updated. But that doesn't solve the
> underlying problem that the interface is not well-prepared to track
> ownership of its inputs and outputs.
> 
> The root problem is that `struct refspec_item` is being abused for
> multiple purposes. One of these purposes it to store parsed refspec
> items. Another purpose is to actually serve as a query. The requirements
> for those two purposes regarding the lifetime of associated strings is
> different:
> 
>   - When using it for storage purposes the strings should be owned by
>     the structure itself. Consequently, the lifetime should be managed
>     by the structure too, namely via `refspec_item_clear()`.
> 
>   - When using it for parsing purposes the input string is owned by the
>     caller, whereas the output string is owned by the callee and then
>     handed over to the caller. Here we cannot rely on the structure
>     itself to manage the lifetimes of both strings, at least not in the
>     current form.
> 
> Now of course, we could amend `struct refspec_item` to grow two
> additional fields `src_to_free` and `dst_to_free`. But that only doubles
> down on this misdesigned interface.
> 
> The proper solution in my opinion is to detangle those two usecases and
> split them out into two independent interfaces.
> 
> > And again you are conflating "allocated" with "read-write" here.  It
> > is often convenient if a variable that points at an allocated string
> > is of type "char *" and not "const char *", because you do not cast
> > it when calling free().  But if you want to make a structure member
> > or a variable that holds an allocated string responsible for
> > _owning_ the piece of memory, then you need to consistently have the
> > member point at an allocated piece of memory (or NULL), no?  What
> > this patch does, i.e. prepare an on-stack refspec_str[] array that
> > is initialized from a constant string, and have .src member point at
> > it, would not make .src freeable.  In other words, .src pointing at
> > an allocated piece of string "some of the time" alone is not a good
> > justification to make it a non-const pointer, I would think.
> 
> That's fair enough. I'm trying to work around a broken interface, but do
> not solve the underlying issue.
> 
> Patrick

In any case, I have now invested some more time into the individual sites where I was converting to use on-stack strings. In many of the cases we were indeed able to avoid the issue altogether without too much of a hassle. The end result is more pleasing indeed, I'd say.

Patrick
Junio C Hamano· May 31, 2024, 15:27 UTC · re: Patrick Steinhardt · lore

Re: [PATCH 02/19] global: assign non-const strings as required

Patrick Steinhardt <ps@pks.im> writes:
> In any case, I have now invested some more time into the individual
> sites where I was converting to use on-stack strings. In many of the
> cases we were indeed able to avoid the issue altogether without too much
> of a hassle. The end result is more pleasing indeed, I'd say.

Nice to hear that. Thanks.

Junio C Hamano· May 31, 2024, 15:27 UTC · re: Patrick Steinhardt · lore

Re: [PATCH 02/19] global: assign non-const strings as required

Patrick Steinhardt <ps@pks.im> writes:
Show 12 quoted lines
> On Thu, May 30, 2024 at 12:38:45PM -0700, Junio C Hamano wrote:
>> Patrick Steinhardt <ps@pks.im> writes:
>> 
>> > Well, we do. Not in `get_fetch_map()`, but in `query_refspecs()`. It
>> > does weird stuff where it writes the result into either `src` or `dst`
>> > depending on which of these fields is provided by the caller. Which
>> > means that one of the fields would typically be a constant, whereas the
>> > other one will be allocated.
> ...
> Well, yes, the pointers can be updated. But that doesn't solve the
> underlying problem that the interface is not well-prepared to track
> ownership of its inputs and outputs.

Oh, absolutely. That is exactly why I wondered why .src and .dst need to be "char *" and not "const char *". If the piece of memory these members point at are never modified via these pointers, marking them as "const char *" would help the compilers help us by catching when we try to break the promise.

Sometimes, the "constness of a pointer should help compilers help us avoid modifying a piece of memory we promised not to modify through the pointer" contradicts with the idea of (ab)using the constness to signal ownership, i.e., "const pointers point at something owned by somebody else, but via non-const pointers you always own the memory".

I wonder if we can do something to separate these two concerns apart, using a trick similar to what we often use with an extra variable "to_free". Doing so would bloat the refspec_item, but unlike the references themselves, there won't be thousands of them, so it may not be an issue, perhaps?

Show 15 quoted lines
>> And again you are conflating "allocated" with "read-write" here.  It
>> is often convenient if a variable that points at an allocated string
>> is of type "char *" and not "const char *", because you do not cast
>> it when calling free().  But if you want to make a structure member
>> or a variable that holds an allocated string responsible for
>> _owning_ the piece of memory, then you need to consistently have the
>> member point at an allocated piece of memory (or NULL), no?  What
>> this patch does, i.e. prepare an on-stack refspec_str[] array that
>> is initialized from a constant string, and have .src member point at
>> it, would not make .src freeable.  In other words, .src pointing at
>> an allocated piece of string "some of the time" alone is not a good
>> justification to make it a non-const pointer, I would think.
>
> That's fair enough. I'm trying to work around a broken interface, but do
> not solve the underlying issue.
Jeff King· Jun 5, 2024, 10:46 UTC · re: Junio C Hamano · lore

Re: [PATCH 02/19] global: assign non-const strings as required

On Fri, May 31, 2024 at 08:27:13AM -0700, Junio C Hamano wrote:
Show 5 quoted lines
> I wonder if we can do something to separate these two concerns
> apart, using a trick similar to what we often use with an extra
> variable "to_free".  Doing so would bloat the refspec_item, but
> unlike the references themselves, there won't be thousands of them,
> so it may not be an issue, perhaps?

I had a similar thought while looking at this spot a while ago, so I dug this attempt out of my stash. It's quite ugly, as you need to keep the storage pointer and the const pointer in sync. Especially because there's a lot of clever pointer indirection via match_name_with_pattern().

So I don't think it's the best way to refactor this code, but I present it here for your amusement/disgust. This is on top of what you have queued in ps/no-writable-strings.

---
 branch.c         |  6 +++---
 builtin/fetch.c  |  8 ++++----
 builtin/remote.c |  6 +++---
 checkout.c       | 13 +++++++------
 refspec.c        |  6 ++++--
 refspec.h        |  6 ++++--
 remote.c         | 30 +++++++++++++++++-------------
 transport.c      |  2 +-
 8 files changed, 43 insertions(+), 34 deletions(-)
Show changes to 8 files +43 −34

branch.c, builtin/fetch.c, builtin/remote.c, checkout.c, refspec.c, refspec.h, remote.c, transport.c

diff --git a/branch.c b/branch.c
index df5d24fec6..e1585b10c2 100644
--- a/branch.c
+++ b/branch.c
@@ -38,7 +38,7 @@ static int find_tracked_branch(struct remote *remote, void *priv)
 	if (!remote_find_tracking(remote, &tracking->spec)) {
 		switch (++tracking->matches) {
 		case 1:
-			string_list_append_nodup(tracking->srcs, tracking->spec.src);
+			string_list_append_nodup(tracking->srcs, tracking->spec.src_storage);
 			tracking->remote = remote->name;
 			break;
 		case 2:
@@ -47,7 +47,7 @@ static int find_tracked_branch(struct remote *remote, void *priv)
 			/* fall through */
 		default:
 			string_list_append(&ftb->ambiguous_remotes, remote->name);
-			free(tracking->spec.src);
+			free(tracking->spec.src_storage);
 			string_list_clear(tracking->srcs, 0);
 		break;
 		}
@@ -489,7 +489,7 @@ static int check_tracking_branch(struct remote *remote, void *cb_data)
 	memset(&query, 0, sizeof(struct refspec_item));
 	query.dst = tracking_branch;
 	res = !remote_find_tracking(remote, &query);
-	free(query.src);
+	free(query.src_storage);
 	return res;
 }
 
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 06b60867f5..00a67c99ef 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -443,7 +443,7 @@ static void filter_prefetch_refspec(struct refspec *rs)
 
 	for (i = 0; i < rs->nr; i++) {
 		struct strbuf new_dst = STRBUF_INIT;
-		char *old_dst;
+		const char *old_dst;
 		const char *sub = NULL;
 
 		if (rs->items[i].negative)
@@ -454,8 +454,8 @@ static void filter_prefetch_refspec(struct refspec *rs)
 				 ref_namespace[NAMESPACE_TAGS].ref))) {
 			int j;
 
-			free(rs->items[i].src);
-			free(rs->items[i].dst);
+			free(rs->items[i].src_storage);
+			free(rs->items[i].dst_storage);
 
 			for (j = i + 1; j < rs->nr; j++) {
 				rs->items[j - 1] = rs->items[j];
@@ -481,7 +481,7 @@ static void filter_prefetch_refspec(struct refspec *rs)
 		rs->items[i].dst = strbuf_detach(&new_dst, NULL);
 		rs->items[i].force = 1;
 
-		free(old_dst);
+		free(rs->items[i].dst_storage);
 	}
 }
 
diff --git a/builtin/remote.c b/builtin/remote.c
index b44f580b8c..87b97f5ef1 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -496,8 +496,8 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat
 	struct refspec_item refspec = {
 		.force = 0,
 		.pattern = 1,
-		.src = (char *) "refs/heads/*",
-		.dst = (char *) "refs/heads/*",
+		.src = "refs/heads/*",
+		.dst = "refs/heads/*",
 	};
 
 	get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
@@ -981,7 +981,7 @@ static int append_ref_to_tracked_list(const char *refname,
 		return 0;
 
 	memset(&refspec, 0, sizeof(refspec));
-	refspec.dst = (char *)refname;
+	refspec.dst = refname;
 	if (!remote_find_tracking(states->remote, &refspec))
 		string_list_append(&states->tracked, abbrev_branch(refspec.src));
 
diff --git a/checkout.c b/checkout.c
index cfaea4bd10..327ab5f6e3 100644
--- a/checkout.c
+++ b/checkout.c
@@ -8,7 +8,7 @@
 #include "strbuf.h"
 
 struct tracking_name_data {
-	/* const */ char *src_ref;
+	const char *src_ref;
 	char *dst_ref;
 	struct object_id *dst_oid;
 	int num_matches;
@@ -27,7 +27,7 @@ static int check_tracking_name(struct remote *remote, void *cb_data)
 	query.src = cb->src_ref;
 	if (remote_find_tracking(remote, &query) ||
 	    repo_get_oid(the_repository, query.dst, cb->dst_oid)) {
-		free(query.dst);
+		free(query.dst_storage);
 		return 0;
 	}
 	cb->num_matches++;
@@ -38,10 +38,10 @@ static int check_tracking_name(struct remote *remote, void *cb_data)
 		cb->default_dst_oid = dst;
 	}
 	if (cb->dst_ref) {
-		free(query.dst);
+		free(query.dst_storage);
 		return 0;
 	}
-	cb->dst_ref = query.dst;
+	cb->dst_ref = query.dst_storage;
 	return 0;
 }
 
@@ -50,14 +50,15 @@ char *unique_tracking_name(const char *name, struct object_id *oid,
 {
 	struct tracking_name_data cb_data = TRACKING_NAME_DATA_INIT;
 	const char *default_remote = NULL;
+	char *src_ref;
 	if (!git_config_get_string_tmp("checkout.defaultremote", &default_remote))
 		cb_data.default_remote = default_remote;
-	cb_data.src_ref = xstrfmt("refs/heads/%s", name);
+	cb_data.src_ref = src_ref = xstrfmt("refs/heads/%s", name);
 	cb_data.dst_oid = oid;
 	for_each_remote(check_tracking_name, &cb_data);
 	if (dwim_remotes_matched)
 		*dwim_remotes_matched = cb_data.num_matches;
-	free(cb_data.src_ref);
+	free(src_ref);
 	if (cb_data.num_matches == 1) {
 		free(cb_data.default_dst_ref);
 		free(cb_data.default_dst_oid);
diff --git a/refspec.c b/refspec.c
index 1df5de6c2f..a7473c9628 100644
--- a/refspec.c
+++ b/refspec.c
@@ -163,8 +163,10 @@ void refspec_item_init_or_die(struct refspec_item *item, const char *refspec,
 
 void refspec_item_clear(struct refspec_item *item)
 {
-	FREE_AND_NULL(item->src);
-	FREE_AND_NULL(item->dst);
+	FREE_AND_NULL(item->src_storage);
+	item->src = NULL;
+	FREE_AND_NULL(item->dst_storage);
+	item->dst = NULL;
 	item->force = 0;
 	item->pattern = 0;
 	item->matching = 0;
diff --git a/refspec.h b/refspec.h
index 754be45cee..fbc4352397 100644
--- a/refspec.h
+++ b/refspec.h
@@ -24,8 +24,10 @@ struct refspec_item {
 	unsigned exact_sha1 : 1;
 	unsigned negative : 1;
 
-	char *src;
-	char *dst;
+	const char *src;
+	const char *dst;
+	char *src_storage;
+	char *dst_storage;
 };
 
 #define REFSPEC_FETCH 1
diff --git a/remote.c b/remote.c
index d319f28757..5cdc3a17cc 100644
--- a/remote.c
+++ b/remote.c
@@ -827,7 +827,8 @@ int remote_has_url(struct remote *remote, const char *url)
 }
 
 static int match_name_with_pattern(const char *key, const char *name,
-				   const char *value, char **result)
+				   const char *value, char **result,
+				   const char **result_const)
 {
 	const char *kstar = strchr(key, '*');
 	size_t klen;
@@ -850,6 +851,8 @@ static int match_name_with_pattern(const char *key, const char *name,
 		strbuf_add(&sb, name + klen, namelen - klen - ksuffixlen);
 		strbuf_addstr(&sb, vstar + 1);
 		*result = strbuf_detach(&sb, NULL);
+		if (result_const)
+			*result_const = *result;
 	}
 	return ret;
 }
@@ -858,7 +861,7 @@ static int refspec_match(const struct refspec_item *refspec,
 			 const char *name)
 {
 	if (refspec->pattern)
-		return match_name_with_pattern(refspec->src, name, NULL, NULL);
+		return match_name_with_pattern(refspec->src, name, NULL, NULL, NULL);
 
 	return !strcmp(refspec->src, name);
 }
@@ -927,7 +930,7 @@ static int query_matches_negative_refspec(struct refspec *rs, struct refspec_ite
 			const char *key = refspec->dst ? refspec->dst : refspec->src;
 			const char *value = refspec->src;
 
-			if (match_name_with_pattern(key, needle, value, &expn_name))
+			if (match_name_with_pattern(key, needle, value, &expn_name, NULL))
 				string_list_append_nodup(&reversed, expn_name);
 		} else if (refspec->matching) {
 			/* For the special matching refspec, any query should match */
@@ -967,12 +970,12 @@ static void query_refspecs_multiple(struct refspec *rs,
 		const char *key = find_src ? refspec->dst : refspec->src;
 		const char *value = find_src ? refspec->src : refspec->dst;
 		const char *needle = find_src ? query->dst : query->src;
-		char **result = find_src ? &query->src : &query->dst;
+		char **result = find_src ? &query->src_storage : &query->dst_storage;
 
 		if (!refspec->dst || refspec->negative)
 			continue;
 		if (refspec->pattern) {
-			if (match_name_with_pattern(key, needle, value, result))
+			if (match_name_with_pattern(key, needle, value, result, NULL))
 				string_list_append_nodup(results, *result);
 		} else if (!strcmp(needle, key)) {
 			string_list_append(results, value);
@@ -985,7 +988,8 @@ int query_refspecs(struct refspec *rs, struct refspec_item *query)
 	int i;
 	int find_src = !query->src;
 	const char *needle = find_src ? query->dst : query->src;
-	char **result = find_src ? &query->src : &query->dst;
+	char **result = find_src ? &query->src_storage : &query->dst_storage;
+	const char **result_const = find_src ? &query->src : &query->dst;
 
 	if (find_src && !query->dst)
 		BUG("query_refspecs: need either src or dst");
@@ -1001,12 +1005,12 @@ int query_refspecs(struct refspec *rs, struct refspec_item *query)
 		if (!refspec->dst || refspec->negative)
 			continue;
 		if (refspec->pattern) {
-			if (match_name_with_pattern(key, needle, value, result)) {
+			if (match_name_with_pattern(key, needle, value, result, result_const)) {
 				query->force = refspec->force;
 				return 0;
 			}
 		} else if (!strcmp(needle, key)) {
-			*result = xstrdup(value);
+			*result_const = *result = xstrdup(value);
 			query->force = refspec->force;
 			return 0;
 		}
@@ -1019,12 +1023,12 @@ char *apply_refspecs(struct refspec *rs, const char *name)
 	struct refspec_item query;
 
 	memset(&query, 0, sizeof(struct refspec_item));
-	query.src = (char *)name;
+	query.src = name;
 
 	if (query_refspecs(rs, &query))
 		return NULL;
 
-	return query.dst;
+	return query.dst_storage;
 }
 
 int remote_find_tracking(struct remote *remote, struct refspec_item *refspec)
@@ -1399,9 +1403,9 @@ static char *get_ref_match(const struct refspec *rs, const struct ref *ref,
 			const char *dst_side = item->dst ? item->dst : item->src;
 			int match;
 			if (direction == FROM_SRC)
-				match = match_name_with_pattern(item->src, ref->name, dst_side, &name);
+				match = match_name_with_pattern(item->src, ref->name, dst_side, &name, NULL);
 			else
-				match = match_name_with_pattern(dst_side, ref->name, item->src, &name);
+				match = match_name_with_pattern(dst_side, ref->name, item->src, &name, NULL);
 			if (match) {
 				matching_refs = i;
 				break;
@@ -2020,7 +2024,7 @@ static struct ref *get_expanded_map(const struct ref *remote_refs,
 		if (strchr(ref->name, '^'))
 			continue; /* a dereference item */
 		if (match_name_with_pattern(refspec->src, ref->name,
-					    refspec->dst, &expn_name) &&
+					    refspec->dst, &expn_name, NULL) &&
 		    !ignore_symref_update(expn_name, &scratch)) {
 			struct ref *cpy = copy_ref(ref);
 
diff --git a/transport.c b/transport.c
index 83ddea8fbc..0cedda425c 100644
--- a/transport.c
+++ b/transport.c
@@ -550,7 +550,7 @@ static void update_one_tracking_ref(struct remote *remote, char *refname,
 			refs_update_ref(get_main_ref_store(the_repository),
 					"update by push", rs.dst, new_oid,
 					NULL, 0, 0);
-		free(rs.dst);
+		free(rs.dst_storage);
 	}
 }
 
Junio C Hamano· Jun 5, 2024, 17:13 UTC · re: Jeff King · lore

Re: [PATCH 02/19] global: assign non-const strings as required

Jeff King <peff@peff.net> writes:
Show 12 quoted lines
> On Fri, May 31, 2024 at 08:27:13AM -0700, Junio C Hamano wrote:
>
>> I wonder if we can do something to separate these two concerns
>> apart, using a trick similar to what we often use with an extra
>> variable "to_free".  Doing so would bloat the refspec_item, but
>> unlike the references themselves, there won't be thousands of them,
>> so it may not be an issue, perhaps?
>
> I had a similar thought while looking at this spot a while ago, so I dug
> this attempt out of my stash. It's quite ugly, as you need to keep the
> storage pointer and the const pointer in sync. Especially because
> there's a lot of clever pointer indirection via match_name_with_pattern().

Ah, true. The patch itself does not look _too_ bad, but that may simply be because the original is bad enough ;-)

Jeff King· Jun 8, 2024, 10:59 UTC · re: Junio C Hamano · lore

Re: [PATCH 02/19] global: assign non-const strings as required

On Wed, Jun 05, 2024 at 10:13:28AM -0700, Junio C Hamano wrote:
Show 17 quoted lines
> Jeff King <peff@peff.net> writes:
> 
> > On Fri, May 31, 2024 at 08:27:13AM -0700, Junio C Hamano wrote:
> >
> >> I wonder if we can do something to separate these two concerns
> >> apart, using a trick similar to what we often use with an extra
> >> variable "to_free".  Doing so would bloat the refspec_item, but
> >> unlike the references themselves, there won't be thousands of them,
> >> so it may not be an issue, perhaps?
> >
> > I had a similar thought while looking at this spot a while ago, so I dug
> > this attempt out of my stash. It's quite ugly, as you need to keep the
> > storage pointer and the const pointer in sync. Especially because
> > there's a lot of clever pointer indirection via match_name_with_pattern().
> 
> Ah, true.  The patch itself does not look _too_ bad, but that may
> simply be because the original is bad enough ;-)

A lot of it is not how bad the patch itself looks, but how hard it was to write. Each site which touches "src" needs to consider whether it should be "src_storage", and there was a lot of running the test suite with ASan, followed by head-scratching. I _think_ I got all of the right spots, but who knows. ;)

Anyway, I think Patrick is quite sensible in not pursuing it further for this series. But just for posterity, here's a slightly less invasive version of the patch I showed earlier. I had added an extra arguments to match_name_with_pattern() to auto-assign the "const" version, but it turned out that only one caller cared about it (and had to manually assign the const version in its fallback code path anyway!).

So this version has a little less noise in it.
---
 branch.c         |  6 +++---
 builtin/fetch.c  |  8 ++++----
 builtin/remote.c |  6 +++---
 checkout.c       | 13 +++++++------
 refspec.c        |  6 ++++--
 refspec.h        |  6 ++++--
 remote.c         | 12 +++++++-----
 transport.c      |  2 +-
 8 files changed, 33 insertions(+), 26 deletions(-)
Show changes to 8 files +33 −26

branch.c, builtin/fetch.c, builtin/remote.c, checkout.c, refspec.c, refspec.h, remote.c, transport.c

diff --git a/branch.c b/branch.c
index df5d24fec6..e1585b10c2 100644
--- a/branch.c
+++ b/branch.c
@@ -38,7 +38,7 @@ static int find_tracked_branch(struct remote *remote, void *priv)
 	if (!remote_find_tracking(remote, &tracking->spec)) {
 		switch (++tracking->matches) {
 		case 1:
-			string_list_append_nodup(tracking->srcs, tracking->spec.src);
+			string_list_append_nodup(tracking->srcs, tracking->spec.src_storage);
 			tracking->remote = remote->name;
 			break;
 		case 2:
@@ -47,7 +47,7 @@ static int find_tracked_branch(struct remote *remote, void *priv)
 			/* fall through */
 		default:
 			string_list_append(&ftb->ambiguous_remotes, remote->name);
-			free(tracking->spec.src);
+			free(tracking->spec.src_storage);
 			string_list_clear(tracking->srcs, 0);
 		break;
 		}
@@ -489,7 +489,7 @@ static int check_tracking_branch(struct remote *remote, void *cb_data)
 	memset(&query, 0, sizeof(struct refspec_item));
 	query.dst = tracking_branch;
 	res = !remote_find_tracking(remote, &query);
-	free(query.src);
+	free(query.src_storage);
 	return res;
 }
 
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 06b60867f5..00a67c99ef 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -443,7 +443,7 @@ static void filter_prefetch_refspec(struct refspec *rs)
 
 	for (i = 0; i < rs->nr; i++) {
 		struct strbuf new_dst = STRBUF_INIT;
-		char *old_dst;
+		const char *old_dst;
 		const char *sub = NULL;
 
 		if (rs->items[i].negative)
@@ -454,8 +454,8 @@ static void filter_prefetch_refspec(struct refspec *rs)
 				 ref_namespace[NAMESPACE_TAGS].ref))) {
 			int j;
 
-			free(rs->items[i].src);
-			free(rs->items[i].dst);
+			free(rs->items[i].src_storage);
+			free(rs->items[i].dst_storage);
 
 			for (j = i + 1; j < rs->nr; j++) {
 				rs->items[j - 1] = rs->items[j];
@@ -481,7 +481,7 @@ static void filter_prefetch_refspec(struct refspec *rs)
 		rs->items[i].dst = strbuf_detach(&new_dst, NULL);
 		rs->items[i].force = 1;
 
-		free(old_dst);
+		free(rs->items[i].dst_storage);
 	}
 }
 
diff --git a/builtin/remote.c b/builtin/remote.c
index b44f580b8c..87b97f5ef1 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -496,8 +496,8 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat
 	struct refspec_item refspec = {
 		.force = 0,
 		.pattern = 1,
-		.src = (char *) "refs/heads/*",
-		.dst = (char *) "refs/heads/*",
+		.src = "refs/heads/*",
+		.dst = "refs/heads/*",
 	};
 
 	get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
@@ -981,7 +981,7 @@ static int append_ref_to_tracked_list(const char *refname,
 		return 0;
 
 	memset(&refspec, 0, sizeof(refspec));
-	refspec.dst = (char *)refname;
+	refspec.dst = refname;
 	if (!remote_find_tracking(states->remote, &refspec))
 		string_list_append(&states->tracked, abbrev_branch(refspec.src));
 
diff --git a/checkout.c b/checkout.c
index cfaea4bd10..327ab5f6e3 100644
--- a/checkout.c
+++ b/checkout.c
@@ -8,7 +8,7 @@
 #include "strbuf.h"
 
 struct tracking_name_data {
-	/* const */ char *src_ref;
+	const char *src_ref;
 	char *dst_ref;
 	struct object_id *dst_oid;
 	int num_matches;
@@ -27,7 +27,7 @@ static int check_tracking_name(struct remote *remote, void *cb_data)
 	query.src = cb->src_ref;
 	if (remote_find_tracking(remote, &query) ||
 	    repo_get_oid(the_repository, query.dst, cb->dst_oid)) {
-		free(query.dst);
+		free(query.dst_storage);
 		return 0;
 	}
 	cb->num_matches++;
@@ -38,10 +38,10 @@ static int check_tracking_name(struct remote *remote, void *cb_data)
 		cb->default_dst_oid = dst;
 	}
 	if (cb->dst_ref) {
-		free(query.dst);
+		free(query.dst_storage);
 		return 0;
 	}
-	cb->dst_ref = query.dst;
+	cb->dst_ref = query.dst_storage;
 	return 0;
 }
 
@@ -50,14 +50,15 @@ char *unique_tracking_name(const char *name, struct object_id *oid,
 {
 	struct tracking_name_data cb_data = TRACKING_NAME_DATA_INIT;
 	const char *default_remote = NULL;
+	char *src_ref;
 	if (!git_config_get_string_tmp("checkout.defaultremote", &default_remote))
 		cb_data.default_remote = default_remote;
-	cb_data.src_ref = xstrfmt("refs/heads/%s", name);
+	cb_data.src_ref = src_ref = xstrfmt("refs/heads/%s", name);
 	cb_data.dst_oid = oid;
 	for_each_remote(check_tracking_name, &cb_data);
 	if (dwim_remotes_matched)
 		*dwim_remotes_matched = cb_data.num_matches;
-	free(cb_data.src_ref);
+	free(src_ref);
 	if (cb_data.num_matches == 1) {
 		free(cb_data.default_dst_ref);
 		free(cb_data.default_dst_oid);
diff --git a/refspec.c b/refspec.c
index 1df5de6c2f..a7473c9628 100644
--- a/refspec.c
+++ b/refspec.c
@@ -163,8 +163,10 @@ void refspec_item_init_or_die(struct refspec_item *item, const char *refspec,
 
 void refspec_item_clear(struct refspec_item *item)
 {
-	FREE_AND_NULL(item->src);
-	FREE_AND_NULL(item->dst);
+	FREE_AND_NULL(item->src_storage);
+	item->src = NULL;
+	FREE_AND_NULL(item->dst_storage);
+	item->dst = NULL;
 	item->force = 0;
 	item->pattern = 0;
 	item->matching = 0;
diff --git a/refspec.h b/refspec.h
index 754be45cee..fbc4352397 100644
--- a/refspec.h
+++ b/refspec.h
@@ -24,8 +24,10 @@ struct refspec_item {
 	unsigned exact_sha1 : 1;
 	unsigned negative : 1;
 
-	char *src;
-	char *dst;
+	const char *src;
+	const char *dst;
+	char *src_storage;
+	char *dst_storage;
 };
 
 #define REFSPEC_FETCH 1
diff --git a/remote.c b/remote.c
index d319f28757..d1511ecf83 100644
--- a/remote.c
+++ b/remote.c
@@ -967,7 +967,7 @@ static void query_refspecs_multiple(struct refspec *rs,
 		const char *key = find_src ? refspec->dst : refspec->src;
 		const char *value = find_src ? refspec->src : refspec->dst;
 		const char *needle = find_src ? query->dst : query->src;
-		char **result = find_src ? &query->src : &query->dst;
+		char **result = find_src ? &query->src_storage : &query->dst_storage;
 
 		if (!refspec->dst || refspec->negative)
 			continue;
@@ -985,7 +985,8 @@ int query_refspecs(struct refspec *rs, struct refspec_item *query)
 	int i;
 	int find_src = !query->src;
 	const char *needle = find_src ? query->dst : query->src;
-	char **result = find_src ? &query->src : &query->dst;
+	char **result = find_src ? &query->src_storage : &query->dst_storage;
+	const char **result_const = find_src ? &query->src : &query->dst;
 
 	if (find_src && !query->dst)
 		BUG("query_refspecs: need either src or dst");
@@ -1002,11 +1003,12 @@ int query_refspecs(struct refspec *rs, struct refspec_item *query)
 			continue;
 		if (refspec->pattern) {
 			if (match_name_with_pattern(key, needle, value, result)) {
+				*result_const = *result;
 				query->force = refspec->force;
 				return 0;
 			}
 		} else if (!strcmp(needle, key)) {
-			*result = xstrdup(value);
+			*result_const = *result = xstrdup(value);
 			query->force = refspec->force;
 			return 0;
 		}
@@ -1019,12 +1021,12 @@ char *apply_refspecs(struct refspec *rs, const char *name)
 	struct refspec_item query;
 
 	memset(&query, 0, sizeof(struct refspec_item));
-	query.src = (char *)name;
+	query.src = name;
 
 	if (query_refspecs(rs, &query))
 		return NULL;
 
-	return query.dst;
+	return query.dst_storage;
 }
 
 int remote_find_tracking(struct remote *remote, struct refspec_item *refspec)
diff --git a/transport.c b/transport.c
index 83ddea8fbc..0cedda425c 100644
--- a/transport.c
+++ b/transport.c
@@ -550,7 +550,7 @@ static void update_one_tracking_ref(struct remote *remote, char *refname,
 			refs_update_ref(get_main_ref_store(the_repository),
 					"update by push", rs.dst, new_oid,
 					NULL, 0, 0);
-		free(rs.dst);
+		free(rs.dst_storage);
 	}
 }
 
Patrick Steinhardt· Jun 6, 2024, 10:36 UTC · re: Jeff King · lore

Re: [PATCH 02/19] global: assign non-const strings as required

On Wed, Jun 05, 2024 at 06:46:46AM -0400, Jeff King wrote:
Show 16 quoted lines
> On Fri, May 31, 2024 at 08:27:13AM -0700, Junio C Hamano wrote:
> 
> > I wonder if we can do something to separate these two concerns
> > apart, using a trick similar to what we often use with an extra
> > variable "to_free".  Doing so would bloat the refspec_item, but
> > unlike the references themselves, there won't be thousands of them,
> > so it may not be an issue, perhaps?
> 
> I had a similar thought while looking at this spot a while ago, so I dug
> this attempt out of my stash. It's quite ugly, as you need to keep the
> storage pointer and the const pointer in sync. Especially because
> there's a lot of clever pointer indirection via match_name_with_pattern().
> 
> So I don't think it's the best way to refactor this code, but I present
> it here for your amusement/disgust. This is on top of what you have
> queued in ps/no-writable-strings.

Just for the record: I'll keep such a refactoring out of this patch series as it is already big enough. I do agree though that we should ideally fix this interface in a subsequent iteration.

Patrick
Patrick Steinhardt· May 29, 2024, 12:44 UTC · re: Patrick Steinhardt · lore

[PATCH 03/19] global: convert intentionally-leaking config strings to consts

There are multiple cases where we intentionally leak config strings:
  - `struct gpg_format` is used to track programs that can be used for
    signing commits, either via gpg(1), gpgsm(1) or ssh-keygen(1). The
    user can override the commands via several config variables. As the
    array is populated once, only, and will never be free'd, it is fine
    to treat the program as a quasi-constant.
  - `struct ll_merge_driver` is used to track merge drivers. Same as
    with the GPG format, these drivers are populated once and then
    reused. Its data is never free'd, either.
  - `struct userdiff_funcname` and `struct userdiff_driver` can be
    configured via `diff.<driver>.*` to add additional drivers. Again,
    these have a global lifetime and are never free'd.

All of these are intentionally kept alive and never free'd. Let's mark the respective fields as `const char *` and cast away the constness when assigning those values.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 gpg-interface.c |  4 ++--
 merge-ll.c      | 11 ++++++++---
 userdiff.c      | 10 +++++-----
 userdiff.h      | 12 ++++++------
 4 files changed, 21 insertions(+), 16 deletions(-)
Show changes to 4 files +21 −16

gpg-interface.c, merge-ll.c, userdiff.c, userdiff.h

diff --git a/gpg-interface.c b/gpg-interface.c
index 71a9382a61..5c824aeb25 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -34,7 +34,7 @@ static enum signature_trust_level configured_min_trust_level = TRUST_UNDEFINED;
 
 struct gpg_format {
 	const char *name;
-	char *program;
+	const char *program;
 	const char **verify_args;
 	const char **sigs;
 	int (*verify_signed_buffer)(struct signature_check *sigc,
@@ -783,7 +783,7 @@ static int git_gpg_config(const char *var, const char *value,
 
 	if (fmtname) {
 		fmt = get_format_by_name(fmtname);
-		return git_config_string(&fmt->program, var, value);
+		return git_config_string((char **) &fmt->program, var, value);
 	}
 
 	return 0;
diff --git a/merge-ll.c b/merge-ll.c
index e29b15fa4a..180c19df67 100644
--- a/merge-ll.c
+++ b/merge-ll.c
@@ -27,7 +27,7 @@ typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *,
 
 struct ll_merge_driver {
 	const char *name;
-	char *description;
+	const char *description;
 	ll_merge_fn fn;
 	char *recursive;
 	struct ll_merge_driver *next;
@@ -304,8 +304,13 @@ static int read_merge_config(const char *var, const char *value,
 		ll_user_merge_tail = &(fn->next);
 	}
 
-	if (!strcmp("name", key))
-		return git_config_string(&fn->description, var, value);
+	if (!strcmp("name", key)) {
+		/*
+		 * The description is leaking, but that's okay as we want to
+		 * keep around the merge drivers anyway.
+		 */
+		return git_config_string((char **) &fn->description, var, value);
+	}
 
 	if (!strcmp("driver", key)) {
 		if (!value)
diff --git a/userdiff.c b/userdiff.c
index 82bc76b910..371032a413 100644
--- a/userdiff.c
+++ b/userdiff.c
@@ -399,7 +399,7 @@ static struct userdiff_driver *userdiff_find_by_namelen(const char *name, size_t
 static int parse_funcname(struct userdiff_funcname *f, const char *k,
 		const char *v, int cflags)
 {
-	if (git_config_string(&f->pattern, k, v) < 0)
+	if (git_config_string((char **) &f->pattern, k, v) < 0)
 		return -1;
 	f->cflags = cflags;
 	return 0;
@@ -445,15 +445,15 @@ int userdiff_config(const char *k, const char *v)
 	if (!strcmp(type, "binary"))
 		return parse_tristate(&drv->binary, k, v);
 	if (!strcmp(type, "command"))
-		return git_config_string(&drv->external, k, v);
+		return git_config_string((char **) &drv->external, k, v);
 	if (!strcmp(type, "textconv"))
-		return git_config_string(&drv->textconv, k, v);
+		return git_config_string((char **) &drv->textconv, k, v);
 	if (!strcmp(type, "cachetextconv"))
 		return parse_bool(&drv->textconv_want_cache, k, v);
 	if (!strcmp(type, "wordregex"))
-		return git_config_string(&drv->word_regex, k, v);
+		return git_config_string((char **) &drv->word_regex, k, v);
 	if (!strcmp(type, "algorithm"))
-		return git_config_string(&drv->algorithm, k, v);
+		return git_config_string((char **) &drv->algorithm, k, v);
 
 	return 0;
 }
diff --git a/userdiff.h b/userdiff.h
index cc8e5abfef..d726804c3e 100644
--- a/userdiff.h
+++ b/userdiff.h
@@ -7,19 +7,19 @@ struct index_state;
 struct repository;
 
 struct userdiff_funcname {
-	char *pattern;
+	const char *pattern;
 	int cflags;
 };
 
 struct userdiff_driver {
 	const char *name;
-	char *external;
-	char *algorithm;
+	const char *external;
+	const char *algorithm;
 	int binary;
 	struct userdiff_funcname funcname;
-	char *word_regex;
-	char *word_regex_multi_byte;
-	char *textconv;
+	const char *word_regex;
+	const char *word_regex_multi_byte;
+	const char *textconv;
 	struct notes_cache *textconv_cache;
 	int textconv_want_cache;
 };
-- 
2.45.1.313.g3a57aa566a.dirty
Junio C Hamano· May 29, 2024, 17:28 UTC · re: Patrick Steinhardt · lore

Re: [PATCH 03/19] global: convert intentionally-leaking config strings to consts

Patrick Steinhardt <ps@pks.im> writes:
Show 19 quoted lines
> There are multiple cases where we intentionally leak config strings:
>
>   - `struct gpg_format` is used to track programs that can be used for
>     signing commits, either via gpg(1), gpgsm(1) or ssh-keygen(1). The
>     user can override the commands via several config variables. As the
>     array is populated once, only, and will never be free'd, it is fine
>     to treat the program as a quasi-constant.
>
>   - `struct ll_merge_driver` is used to track merge drivers. Same as
>     with the GPG format, these drivers are populated once and then
>     reused. Its data is never free'd, either.
>
>   - `struct userdiff_funcname` and `struct userdiff_driver` can be
>     configured via `diff.<driver>.*` to add additional drivers. Again,
>     these have a global lifetime and are never free'd.
>
> All of these are intentionally kept alive and never free'd. Let's mark
> the respective fields as `const char *` and cast away the constness when
> assigning those values.

It is not unclear where the linkage between "not freed" and "must be const" comes from. What am I missing?

Thanks.
Patrick Steinhardt· May 30, 2024, 11:30 UTC · re: Junio C Hamano · lore

Re: [PATCH 03/19] global: convert intentionally-leaking config strings to consts

On Wed, May 29, 2024 at 10:28:05AM -0700, Junio C Hamano wrote:
Show 24 quoted lines
> Patrick Steinhardt <ps@pks.im> writes:
> 
> > There are multiple cases where we intentionally leak config strings:
> >
> >   - `struct gpg_format` is used to track programs that can be used for
> >     signing commits, either via gpg(1), gpgsm(1) or ssh-keygen(1). The
> >     user can override the commands via several config variables. As the
> >     array is populated once, only, and will never be free'd, it is fine
> >     to treat the program as a quasi-constant.
> >
> >   - `struct ll_merge_driver` is used to track merge drivers. Same as
> >     with the GPG format, these drivers are populated once and then
> >     reused. Its data is never free'd, either.
> >
> >   - `struct userdiff_funcname` and `struct userdiff_driver` can be
> >     configured via `diff.<driver>.*` to add additional drivers. Again,
> >     these have a global lifetime and are never free'd.
> >
> > All of these are intentionally kept alive and never free'd. Let's mark
> > the respective fields as `const char *` and cast away the constness when
> > assigning those values.
> 
> It is not unclear where the linkage between "not freed" and "must be
> const" comes from.  What am I missing?

It comes from `-Wwrite-strings`, which will mark string constants as `const char *`. This will cause warnings in all of the above cases because the fields are being assigned constants, but those fields are currently `char *`. Will clarify.

Patrick
Junio C Hamano· May 30, 2024, 16:00 UTC · re: Patrick Steinhardt · lore

Re: [PATCH 03/19] global: convert intentionally-leaking config strings to consts

Patrick Steinhardt <ps@pks.im> writes:
Show 30 quoted lines
> On Wed, May 29, 2024 at 10:28:05AM -0700, Junio C Hamano wrote:
>> Patrick Steinhardt <ps@pks.im> writes:
>> 
>> > There are multiple cases where we intentionally leak config strings:
>> >
>> >   - `struct gpg_format` is used to track programs that can be used for
>> >     signing commits, either via gpg(1), gpgsm(1) or ssh-keygen(1). The
>> >     user can override the commands via several config variables. As the
>> >     array is populated once, only, and will never be free'd, it is fine
>> >     to treat the program as a quasi-constant.
>> >
>> >   - `struct ll_merge_driver` is used to track merge drivers. Same as
>> >     with the GPG format, these drivers are populated once and then
>> >     reused. Its data is never free'd, either.
>> >
>> >   - `struct userdiff_funcname` and `struct userdiff_driver` can be
>> >     configured via `diff.<driver>.*` to add additional drivers. Again,
>> >     these have a global lifetime and are never free'd.
>> >
>> > All of these are intentionally kept alive and never free'd. Let's mark
>> > the respective fields as `const char *` and cast away the constness when
>> > assigning those values.
>> 
>> It is not unclear where the linkage between "not freed" and "must be
>> const" comes from.  What am I missing?
>
> It comes from `-Wwrite-strings`, which will mark string constants as
> `const char *`. This will cause warnings in all of the above cases
> because the fields are being assigned constants, but those fields are
> currently `char *`. Will clarify.

In short, these warnings have nothing to do with the pointee by these variables or struct members are eventually freed or not, no?

Patrick Steinhardt· May 29, 2024, 12:44 UTC · re: Patrick Steinhardt · lore

[PATCH 04/19] compat/win32: fix const-correctness with string constants

Adjust various places in our Win32 compatibility layer where we are not assigning string constants to `const char *` variables.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 compat/basename.c | 15 +++++++++++++--
 compat/mingw.c    | 25 +++++++++++++------------
 compat/winansi.c  |  2 +-
 3 files changed, 27 insertions(+), 15 deletions(-)
Show changes to 3 files +27 −15

compat/basename.c, compat/mingw.c, compat/winansi.c

diff --git a/compat/basename.c b/compat/basename.c
index 96bd9533b4..c3c9d65fac 100644
--- a/compat/basename.c
+++ b/compat/basename.c
@@ -1,6 +1,13 @@
 #include "../git-compat-util.h"
 #include "../strbuf.h"
 
+/*
+ * Both basename(3P) and dirname(3P) are mis-specified because they return a
+ * non-constant pointer even though it is specified that they may return a
+ * pointer to internal memory. This variable here is a result of that.
+ */
+static char current_directory[] = ".";
+
 /* Adapted from libiberty's basename.c.  */
 char *gitbasename (char *path)
 {
@@ -10,7 +17,7 @@ char *gitbasename (char *path)
 		skip_dos_drive_prefix(&path);
 
 	if (!path || !*path)
-		return ".";
+		return current_directory;
 
 	for (base = path; *path; path++) {
 		if (!is_dir_sep(*path))
@@ -33,8 +40,12 @@ char *gitdirname(char *path)
 	char *p = path, *slash = NULL, c;
 	int dos_drive_prefix;
 
+	/*
+	 * Same here, dirname(3P) is broken because it returns a non-constant
+	 * pointer that may point to internal memory.
+	 */
 	if (!p)
-		return ".";
+		return current_directory;
 
 	if ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p)
 		goto dot;
diff --git a/compat/mingw.c b/compat/mingw.c
index 6b06ea540f..60f0986f76 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -2257,6 +2257,7 @@ struct passwd *getpwuid(int uid)
 {
 	static unsigned initialized;
 	static char user_name[100];
+	static char unknown[] = "unknown";
 	static struct passwd *p;
 	wchar_t buf[100];
 	DWORD len;
@@ -2279,7 +2280,7 @@ struct passwd *getpwuid(int uid)
 	p->pw_name = user_name;
 	p->pw_gecos = get_extended_user_info(NameDisplay);
 	if (!p->pw_gecos)
-		p->pw_gecos = "unknown";
+		p->pw_gecos = unknown;
 	p->pw_dir = NULL;
 
 	initialized = 1;
@@ -2800,16 +2801,16 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)
 			strbuf_addf(report, "'%s' is on a file system that does "
 				    "not record ownership\n", path);
 		} else if (report) {
-			LPSTR str1, str2, str3, str4, to_free1 = NULL,
-			    to_free3 = NULL, to_local_free2 = NULL,
-			    to_local_free4 = NULL;
+			PCSTR str1, str2, str3, str4;
+			LPSTR to_free1 = NULL, to_free3 = NULL,
+			    to_local_free2 = NULL, to_local_free4 = NULL;
 
-			if (user_sid_to_user_name(sid, &str1))
-				to_free1 = str1;
+			if (user_sid_to_user_name(sid, &to_free1))
+				str1 = to_free1;
 			else
 				str1 = "(inconvertible)";
-			if (ConvertSidToStringSidA(sid, &str2))
-				to_local_free2 = str2;
+			if (ConvertSidToStringSidA(sid, &to_local_free2))
+				str2 = to_local_free2;
 			else
 				str2 = "(inconvertible)";
 
@@ -2822,13 +2823,13 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)
 				str4 = "(invalid)";
 			} else {
 				if (user_sid_to_user_name(current_user_sid,
-							  &str3))
-					to_free3 = str3;
+							  &to_free3))
+					str3 = to_free3;
 				else
 					str3 = "(inconvertible)";
 				if (ConvertSidToStringSidA(current_user_sid,
-							   &str4))
-					to_local_free4 = str4;
+							   &to_local_free4))
+					str4 = to_local_free4;
 				else
 					str4 = "(inconvertible)";
 			}
diff --git a/compat/winansi.c b/compat/winansi.c
index f83610f684..575813bde8 100644
--- a/compat/winansi.c
+++ b/compat/winansi.c
@@ -139,7 +139,7 @@ static void write_console(unsigned char *str, size_t len)
 	/* convert utf-8 to utf-16 */
 	int wlen = xutftowcsn(wbuf, (char*) str, ARRAY_SIZE(wbuf), len);
 	if (wlen < 0) {
-		wchar_t *err = L"[invalid]";
+		const wchar_t *err = L"[invalid]";
 		WriteConsoleW(console, err, wcslen(err), &dummy, NULL);
 		return;
 	}
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 29, 2024, 12:44 UTC · re: Patrick Steinhardt · lore

[PATCH 05/19] reftable: improve const correctness when assigning string constants

There are many cases in the reftable tests where we assign string constants to non-const fields. This is harmless because we know that those fields are only used for reading access, but will break once we enable `-Wwrite-strings`. Add explicit casts to prepare for this.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 reftable/basics_test.c    |  4 +--
 reftable/block_test.c     |  2 +-
 reftable/merged_test.c    | 45 +++++++++++++--------------
 reftable/readwrite_test.c | 47 ++++++++++++++--------------
 reftable/stack_test.c     | 65 ++++++++++++++++++++-------------------
 5 files changed, 82 insertions(+), 81 deletions(-)
Show changes to 5 files +82 −81

reftable/basics_test.c, reftable/block_test.c, reftable/merged_test.c, reftable/readwrite_test.c, reftable/stack_test.c

diff --git a/reftable/basics_test.c b/reftable/basics_test.c
index 997c4d9e01..af9209d535 100644
--- a/reftable/basics_test.c
+++ b/reftable/basics_test.c
@@ -58,8 +58,8 @@ static void test_binsearch(void)
 
 static void test_names_length(void)
 {
-	char *a[] = { "a", "b", NULL };
-	EXPECT(names_length(a) == 2);
+	char *names[] = { (char *)"a", (char *)"b", NULL };
+	EXPECT(names_length(names) == 2);
 }
 
 static void test_parse_names_normal(void)
diff --git a/reftable/block_test.c b/reftable/block_test.c
index 26a9cfbc83..34c48c8091 100644
--- a/reftable/block_test.c
+++ b/reftable/block_test.c
@@ -42,7 +42,7 @@ static void test_block_read_write(void)
 	block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,
 			  header_off, hash_size(GIT_SHA1_FORMAT_ID));
 
-	rec.u.ref.refname = "";
+	rec.u.ref.refname = (char *)"";
 	rec.u.ref.value_type = REFTABLE_REF_DELETION;
 	n = block_writer_add(&bw, &rec);
 	EXPECT(n == REFTABLE_API_ERROR);
diff --git a/reftable/merged_test.c b/reftable/merged_test.c
index 530fc82d1c..6ab8ea2858 100644
--- a/reftable/merged_test.c
+++ b/reftable/merged_test.c
@@ -124,13 +124,13 @@ static void readers_destroy(struct reftable_reader **readers, size_t n)
 static void test_merged_between(void)
 {
 	struct reftable_ref_record r1[] = { {
-		.refname = "b",
+		.refname = (char *)"b",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_VAL1,
 		.value.val1 = { 1, 2, 3, 0 },
 	} };
 	struct reftable_ref_record r2[] = { {
-		.refname = "a",
+		.refname = (char *)"a",
 		.update_index = 2,
 		.value_type = REFTABLE_REF_DELETION,
 	} };
@@ -165,38 +165,38 @@ static void test_merged(void)
 {
 	struct reftable_ref_record r1[] = {
 		{
-			.refname = "a",
+			.refname = (char *)"a",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
 		},
 		{
-			.refname = "b",
+			.refname = (char *)"b",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
 		},
 		{
-			.refname = "c",
+			.refname = (char *)"c",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
 		}
 	};
 	struct reftable_ref_record r2[] = { {
-		.refname = "a",
+		.refname = (char *)"a",
 		.update_index = 2,
 		.value_type = REFTABLE_REF_DELETION,
 	} };
 	struct reftable_ref_record r3[] = {
 		{
-			.refname = "c",
+			.refname = (char *)"c",
 			.update_index = 3,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 2 },
 		},
 		{
-			.refname = "d",
+			.refname = (char *)"d",
 			.update_index = 3,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
@@ -291,46 +291,46 @@ static void test_merged_logs(void)
 {
 	struct reftable_log_record r1[] = {
 		{
-			.refname = "a",
+			.refname = (char *)"a",
 			.update_index = 2,
 			.value_type = REFTABLE_LOG_UPDATE,
 			.value.update = {
 				.old_hash = { 2 },
 				/* deletion */
-				.name = "jane doe",
-				.email = "jane@invalid",
-				.message = "message2",
+				.name = (char *)"jane doe",
+				.email = (char *)"jane@invalid",
+				.message = (char *)"message2",
 			}
 		},
 		{
-			.refname = "a",
+			.refname = (char *)"a",
 			.update_index = 1,
 			.value_type = REFTABLE_LOG_UPDATE,
 			.value.update = {
 				.old_hash = { 1 },
 				.new_hash = { 2 },
-				.name = "jane doe",
-				.email = "jane@invalid",
-				.message = "message1",
+				.name = (char *)"jane doe",
+				.email = (char *)"jane@invalid",
+				.message = (char *)"message1",
 			}
 		},
 	};
 	struct reftable_log_record r2[] = {
 		{
-			.refname = "a",
+			.refname = (char *)"a",
 			.update_index = 3,
 			.value_type = REFTABLE_LOG_UPDATE,
 			.value.update = {
 				.new_hash = { 3 },
-				.name = "jane doe",
-				.email = "jane@invalid",
-				.message = "message3",
+				.name = (char *)"jane doe",
+				.email = (char *)"jane@invalid",
+				.message = (char *)"message3",
 			}
 		},
 	};
 	struct reftable_log_record r3[] = {
 		{
-			.refname = "a",
+			.refname = (char *)"a",
 			.update_index = 2,
 			.value_type = REFTABLE_LOG_DELETION,
 		},
@@ -404,9 +404,8 @@ static void test_default_write_opts(void)
 	struct strbuf buf = STRBUF_INIT;
 	struct reftable_writer *w =
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
-
 	struct reftable_ref_record rec = {
-		.refname = "master",
+		.refname = (char *)"master",
 		.update_index = 1,
 	};
 	int err;
diff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c
index a6dbd214c5..72f66507ba 100644
--- a/reftable/readwrite_test.c
+++ b/reftable/readwrite_test.c
@@ -86,7 +86,7 @@ static void write_table(char ***names, struct strbuf *buf, int N,
 		log.update_index = update_index;
 		log.value_type = REFTABLE_LOG_UPDATE;
 		set_test_hash(log.value.update.new_hash, i);
-		log.value.update.message = "message";
+		log.value.update.message = (char *)"message";
 
 		n = reftable_writer_add_log(w, &log);
 		EXPECT(n == 0);
@@ -117,17 +117,18 @@ static void test_log_buffer_size(void)
 	};
 	int err;
 	int i;
-	struct reftable_log_record
-		log = { .refname = "refs/heads/master",
-			.update_index = 0xa,
-			.value_type = REFTABLE_LOG_UPDATE,
-			.value = { .update = {
-					   .name = "Han-Wen Nienhuys",
-					   .email = "hanwen@google.com",
-					   .tz_offset = 100,
-					   .time = 0x5e430672,
-					   .message = "commit: 9\n",
-				   } } };
+	struct reftable_log_record log = {
+		.refname = (char *)"refs/heads/master",
+		.update_index = 0xa,
+		.value_type = REFTABLE_LOG_UPDATE,
+		.value.update = {
+			.name = (char *)"Han-Wen Nienhuys",
+			.email = (char *)"hanwen@google.com",
+			.tz_offset = 100,
+			.time = 0x5e430672,
+			.message = (char *)"commit: 9\n",
+		},
+	};
 	struct reftable_writer *w =
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
 
@@ -156,15 +157,15 @@ static void test_log_overflow(void)
 	};
 	int err;
 	struct reftable_log_record log = {
-		.refname = "refs/heads/master",
+		.refname = (char *)"refs/heads/master",
 		.update_index = 0xa,
 		.value_type = REFTABLE_LOG_UPDATE,
 		.value = {
 			.update = {
 				.old_hash = { 1 },
 				.new_hash = { 2 },
-				.name = "Han-Wen Nienhuys",
-				.email = "hanwen@google.com",
+				.name = (char *)"Han-Wen Nienhuys",
+				.email = (char *)"hanwen@google.com",
 				.tz_offset = 100,
 				.time = 0x5e430672,
 				.message = msg,
@@ -293,14 +294,14 @@ static void test_log_zlib_corruption(void)
 	char message[100] = { 0 };
 	int err, i, n;
 	struct reftable_log_record log = {
-		.refname = "refname",
+		.refname = (char *)"refname",
 		.value_type = REFTABLE_LOG_UPDATE,
 		.value = {
 			.update = {
 				.new_hash = { 1 },
 				.old_hash = { 2 },
-				.name = "My Name",
-				.email = "myname@invalid",
+				.name = (char *)"My Name",
+				.email = (char *)"myname@invalid",
 				.message = message,
 			},
 		},
@@ -728,7 +729,7 @@ static void test_write_empty_key(void)
 	struct reftable_writer *w =
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
 	struct reftable_ref_record ref = {
-		.refname = "",
+		.refname = (char *)"",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_DELETION,
 	};
@@ -752,18 +753,18 @@ static void test_write_key_order(void)
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
 	struct reftable_ref_record refs[2] = {
 		{
-			.refname = "b",
+			.refname = (char *)"b",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_SYMREF,
 			.value = {
-				.symref = "target",
+				.symref = (char *)"target",
 			},
 		}, {
-			.refname = "a",
+			.refname = (char *)"a",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_SYMREF,
 			.value = {
-				.symref = "target",
+				.symref = (char *)"target",
 			},
 		}
 	};
diff --git a/reftable/stack_test.c b/reftable/stack_test.c
index 7889f818d1..0994f6bcda 100644
--- a/reftable/stack_test.c
+++ b/reftable/stack_test.c
@@ -83,7 +83,7 @@ static void test_read_file(void)
 	char out[1024] = "line1\n\nline2\nline3";
 	int n, err;
 	char **names = NULL;
-	char *want[] = { "line1", "line2", "line3" };
+	const char *want[] = { "line1", "line2", "line3" };
 	int i = 0;
 
 	EXPECT(fd > 0);
@@ -116,13 +116,14 @@ static void test_parse_names(void)
 
 static void test_names_equal(void)
 {
-	char *a[] = { "a", "b", "c", NULL };
-	char *b[] = { "a", "b", "d", NULL };
-	char *c[] = { "a", "b", NULL };
-
-	EXPECT(names_equal(a, a));
-	EXPECT(!names_equal(a, b));
-	EXPECT(!names_equal(a, c));
+	char a[] = "a", b[] = "b", c[] = "c", d[] = "d";
+	char *abc[] = { a, b, c, NULL };
+	char *abd[] = { a, b, d, NULL };
+	char *ab[] = { a, b, NULL };
+
+	EXPECT(names_equal(abc, abc));
+	EXPECT(!names_equal(abc, abd));
+	EXPECT(!names_equal(abc, ab));
 }
 
 static int write_test_ref(struct reftable_writer *wr, void *arg)
@@ -156,10 +157,10 @@ static void test_reftable_stack_add_one(void)
 	struct reftable_stack *st = NULL;
 	int err;
 	struct reftable_ref_record ref = {
-		.refname = "HEAD",
+		.refname = (char *)"HEAD",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *)"master",
 	};
 	struct reftable_ref_record dest = { NULL };
 	struct stat stat_result = { 0 };
@@ -216,16 +217,16 @@ static void test_reftable_stack_uptodate(void)
 
 	int err;
 	struct reftable_ref_record ref1 = {
-		.refname = "HEAD",
+		.refname = (char *)"HEAD",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *)"master",
 	};
 	struct reftable_ref_record ref2 = {
-		.refname = "branch2",
+		.refname = (char *)"branch2",
 		.update_index = 2,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *)"master",
 	};
 
 
@@ -264,10 +265,10 @@ static void test_reftable_stack_transaction_api(void)
 	struct reftable_addition *add = NULL;
 
 	struct reftable_ref_record ref = {
-		.refname = "HEAD",
+		.refname = (char *)"HEAD",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *)"master",
 	};
 	struct reftable_ref_record dest = { NULL };
 
@@ -313,7 +314,7 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)
 		struct reftable_ref_record ref = {
 			.update_index = reftable_stack_next_update_index(st),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = (char *)"master",
 		};
 		char name[100];
 
@@ -356,7 +357,7 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)
 static void test_reftable_stack_auto_compaction_fails_gracefully(void)
 {
 	struct reftable_ref_record ref = {
-		.refname = "refs/heads/master",
+		.refname = (char *)"refs/heads/master",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_VAL1,
 		.value.val1 = {0x01},
@@ -409,16 +410,16 @@ static void test_reftable_stack_update_index_check(void)
 	struct reftable_stack *st = NULL;
 	int err;
 	struct reftable_ref_record ref1 = {
-		.refname = "name1",
+		.refname = (char *)"name1",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *)"master",
 	};
 	struct reftable_ref_record ref2 = {
-		.refname = "name2",
+		.refname = (char *)"name2",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *)"master",
 	};
 
 	err = reftable_new_stack(&st, dir, cfg);
@@ -561,7 +562,7 @@ static void test_reftable_stack_log_normalize(void)
 	struct reftable_stack *st = NULL;
 	char *dir = get_tmp_dir(__LINE__);
 	struct reftable_log_record input = {
-		.refname = "branch",
+		.refname = (char *)"branch",
 		.update_index = 1,
 		.value_type = REFTABLE_LOG_UPDATE,
 		.value = {
@@ -582,11 +583,11 @@ static void test_reftable_stack_log_normalize(void)
 	err = reftable_new_stack(&st, dir, cfg);
 	EXPECT_ERR(err);
 
-	input.value.update.message = "one\ntwo";
+	input.value.update.message = (char *)"one\ntwo";
 	err = reftable_stack_add(st, &write_test_log, &arg);
 	EXPECT(err == REFTABLE_API_ERROR);
 
-	input.value.update.message = "one";
+	input.value.update.message = (char *)"one";
 	err = reftable_stack_add(st, &write_test_log, &arg);
 	EXPECT_ERR(err);
 
@@ -594,7 +595,7 @@ static void test_reftable_stack_log_normalize(void)
 	EXPECT_ERR(err);
 	EXPECT(0 == strcmp(dest.value.update.message, "one\n"));
 
-	input.value.update.message = "two\n";
+	input.value.update.message = (char *)"two\n";
 	arg.update_index = 2;
 	err = reftable_stack_add(st, &write_test_log, &arg);
 	EXPECT_ERR(err);
@@ -697,9 +698,9 @@ static void test_reftable_stack_hash_id(void)
 	int err;
 
 	struct reftable_ref_record ref = {
-		.refname = "master",
+		.refname = (char *)"master",
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "target",
+		.value.symref = (char *)"target",
 		.update_index = 1,
 	};
 	struct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID };
@@ -879,7 +880,7 @@ static void test_reftable_stack_auto_compaction(void)
 			.refname = name,
 			.update_index = reftable_stack_next_update_index(st),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = (char *)"master",
 		};
 		snprintf(name, sizeof(name), "branch%04d", i);
 
@@ -913,7 +914,7 @@ static void test_reftable_stack_add_performs_auto_compaction(void)
 		struct reftable_ref_record ref = {
 			.update_index = reftable_stack_next_update_index(st),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = (char *)"master",
 		};
 
 		/*
@@ -964,7 +965,7 @@ static void test_reftable_stack_compaction_concurrent(void)
 			.refname = name,
 			.update_index = reftable_stack_next_update_index(st1),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = (char *)"master",
 		};
 		snprintf(name, sizeof(name), "branch%04d", i);
 
@@ -1014,7 +1015,7 @@ static void test_reftable_stack_compaction_concurrent_clean(void)
 			.refname = name,
 			.update_index = reftable_stack_next_update_index(st1),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = (char *)"master",
 		};
 		snprintf(name, sizeof(name), "branch%04d", i);
 
-- 
2.45.1.313.g3a57aa566a.dirty
Junio C Hamano· May 29, 2024, 17:43 UTC · re: Patrick Steinhardt · lore

Re: [PATCH 05/19] reftable: improve const correctness when assigning string constants

Patrick Steinhardt <ps@pks.im> writes:
Show 13 quoted lines
> diff --git a/reftable/basics_test.c b/reftable/basics_test.c
> index 997c4d9e01..af9209d535 100644
> --- a/reftable/basics_test.c
> +++ b/reftable/basics_test.c
> @@ -58,8 +58,8 @@ static void test_binsearch(void)
>  
>  static void test_names_length(void)
>  {
> -	char *a[] = { "a", "b", NULL };
> -	EXPECT(names_length(a) == 2);
> +	char *names[] = { (char *)"a", (char *)"b", NULL };
> +	EXPECT(names_length(names) == 2);
>  }

I would have preferred to see this kind of rewrite more than separate and clearly writable variables that are initialied with the constant contents e.g. branches[] = "refs/heads/*", we saw in earlier steps. Wouldn't that approach, combined with making the literal constants stored in read-only segment to trigger runtime failure when a bug causes the "unfortunately non-const" variables to be written, give us a better result?

Patrick Steinhardt· May 30, 2024, 11:30 UTC · re: Junio C Hamano · lore

Re: [PATCH 05/19] reftable: improve const correctness when assigning string constants

On Wed, May 29, 2024 at 10:43:47AM -0700, Junio C Hamano wrote:
Show 23 quoted lines
> Patrick Steinhardt <ps@pks.im> writes:
> 
> > diff --git a/reftable/basics_test.c b/reftable/basics_test.c
> > index 997c4d9e01..af9209d535 100644
> > --- a/reftable/basics_test.c
> > +++ b/reftable/basics_test.c
> > @@ -58,8 +58,8 @@ static void test_binsearch(void)
> >  
> >  static void test_names_length(void)
> >  {
> > -	char *a[] = { "a", "b", NULL };
> > -	EXPECT(names_length(a) == 2);
> > +	char *names[] = { (char *)"a", (char *)"b", NULL };
> > +	EXPECT(names_length(names) == 2);
> >  }
> 
> I would have preferred to see this kind of rewrite more than
> separate and clearly writable variables that are initialied with the
> constant contents e.g. branches[] = "refs/heads/*", we saw in
> earlier steps.  Wouldn't that approach, combined with making the
> literal constants stored in read-only segment to trigger runtime
> failure when a bug causes the "unfortunately non-const" variables
> to be written, give us a better result?

Depends on what we mean by "better", I guess. But yeah, I was torn myself when writing this commit because there are so many string constants in the reftable tests that we assign to non-constant fields. I didn't find the result particularly easy to read when putting each of the constants into a separate variable.

Revisiting this again though I don't think it's all that bad. I'll adapt accordingly.

Patrick
Junio C Hamano· May 30, 2024, 16:07 UTC · re: Patrick Steinhardt · lore

Re: [PATCH 05/19] reftable: improve const correctness when assigning string constants

Patrick Steinhardt <ps@pks.im> writes:
Show 30 quoted lines
> On Wed, May 29, 2024 at 10:43:47AM -0700, Junio C Hamano wrote:
>> Patrick Steinhardt <ps@pks.im> writes:
>> 
>> > diff --git a/reftable/basics_test.c b/reftable/basics_test.c
>> > index 997c4d9e01..af9209d535 100644
>> > --- a/reftable/basics_test.c
>> > +++ b/reftable/basics_test.c
>> > @@ -58,8 +58,8 @@ static void test_binsearch(void)
>> >  
>> >  static void test_names_length(void)
>> >  {
>> > -	char *a[] = { "a", "b", NULL };
>> > -	EXPECT(names_length(a) == 2);
>> > +	char *names[] = { (char *)"a", (char *)"b", NULL };
>> > +	EXPECT(names_length(names) == 2);
>> >  }
>> 
>> I would have preferred to see this kind of rewrite more than
>> separate and clearly writable variables that are initialied with the
>> constant contents e.g. branches[] = "refs/heads/*", we saw in
>> earlier steps.  Wouldn't that approach, combined with making the
>> literal constants stored in read-only segment to trigger runtime
>> failure when a bug causes the "unfortunately non-const" variables
>> to be written, give us a better result?
>
> Depends on what we mean by "better", I guess. But yeah, I was torn
> myself when writing this commit because there are so many string
> constants in the reftable tests that we assign to non-constant fields. I
> didn't find the result particularly easy to read when putting each of
> the constants into a separate variable.
Oh, I do *not* want to see.
	char a_string[] = "a";
	char *names[] = { a_string, ... };

As a way to workaround the -Wwritable-strings warnings, what we see in this patch is much better.

I was referring to a redoing of this series in the other direction. The earlier one that introduced

	char refspec_string[] = "refs/heads/*";
and rewrite assignments of
	non_const_pointer_to_char = "refs/heads/*";
with
	non_const_pointer_to_char = refspec_string;

would have been better if it were done without refspec_string[] array. It is a runtime bug to overwrite the refspec_string[] via the non_const_pointer_to_char, but use of refspec_string[] would make it impossible for us to catch.

Patrick Steinhardt· May 29, 2024, 12:44 UTC · re: Patrick Steinhardt · lore

[PATCH 06/19] refspec: remove global tag refspec structure

We have a global tag refspec structure that is used by both git-clone(1) and git-fetch(1). Initialization fo the structure will break once we enable `-Wwrite-strings`, even though the breakage is harmless. While we could just add casts, the structure isn't really required in the first place as we can simply initialize the structures at the respective callsites.

Refactor the code accordingly.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c |  8 ++++++--
 builtin/fetch.c | 11 ++++++++---
 refspec.c       | 13 -------------
 refspec.h       |  1 -
 4 files changed, 14 insertions(+), 19 deletions(-)
Show changes to 4 files +14 −19

builtin/clone.c, builtin/fetch.c, refspec.c, refspec.h

diff --git a/builtin/clone.c b/builtin/clone.c
index 92ab7d7165..bde1d284a2 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -523,6 +523,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
 	struct ref *head = copy_ref(find_ref_by_name(refs, "HEAD"));
 	struct ref *local_refs = head;
 	struct ref **tail = head ? &head->next : &local_refs;
+	struct refspec_item tag_refspec;
+
+	refspec_item_init(&tag_refspec, TAG_REFSPEC, 0);
 
 	if (option_single_branch) {
 		struct ref *remote_head = NULL;
@@ -545,7 +548,7 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
 					      &tail, 0);
 
 			/* if --branch=tag, pull the requested tag explicitly */
-			get_fetch_map(remote_head, tag_refspec, &tail, 0);
+			get_fetch_map(remote_head, &tag_refspec, &tail, 0);
 		}
 		free_refs(remote_head);
 	} else {
@@ -555,8 +558,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
 	}
 
 	if (!option_mirror && !option_single_branch && !option_no_tags)
-		get_fetch_map(refs, tag_refspec, &tail, 0);
+		get_fetch_map(refs, &tag_refspec, &tail, 0);
 
+	refspec_item_clear(&tag_refspec);
 	return local_refs;
 }
 
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 75255dc600..06b60867f5 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -582,11 +582,16 @@ static struct ref *get_ref_map(struct remote *remote,
 		}
 	}
 
-	if (tags == TAGS_SET)
+	if (tags == TAGS_SET) {
+		struct refspec_item tag_refspec;
+
 		/* also fetch all tags */
-		get_fetch_map(remote_refs, tag_refspec, &tail, 0);
-	else if (tags == TAGS_DEFAULT && *autotags)
+		refspec_item_init(&tag_refspec, TAG_REFSPEC, 0);
+		get_fetch_map(remote_refs, &tag_refspec, &tail, 0);
+		refspec_item_clear(&tag_refspec);
+	} else if (tags == TAGS_DEFAULT && *autotags) {
 		find_non_local_tags(remote_refs, NULL, &ref_map, &tail);
+	}
 
 	/* Now append any refs to be updated opportunistically: */
 	*tail = orefs;
diff --git a/refspec.c b/refspec.c
index d60932f4de..1df5de6c2f 100644
--- a/refspec.c
+++ b/refspec.c
@@ -7,19 +7,6 @@
 #include "refspec.h"
 #include "strbuf.h"
 
-static struct refspec_item s_tag_refspec = {
-	.force = 0,
-	.pattern = 1,
-	.matching = 0,
-	.exact_sha1 = 0,
-	.negative = 0,
-	.src = "refs/tags/*",
-	.dst = "refs/tags/*",
-};
-
-/* See TAG_REFSPEC for the string version */
-const struct refspec_item *tag_refspec = &s_tag_refspec;
-
 /*
  * Parses the provided refspec 'refspec' and populates the refspec_item 'item'.
  * Returns 1 if successful and 0 if the refspec is invalid.
diff --git a/refspec.h b/refspec.h
index 8c0c446993..754be45cee 100644
--- a/refspec.h
+++ b/refspec.h
@@ -2,7 +2,6 @@
 #define REFSPEC_H
 
 #define TAG_REFSPEC "refs/tags/*:refs/tags/*"
-extern const struct refspec_item *tag_refspec;
 
 /**
  * A struct refspec_item holds the parsed interpretation of a refspec.  If it
-- 
2.45.1.313.g3a57aa566a.dirty
Junio C Hamano· May 29, 2024, 17:47 UTC · re: Patrick Steinhardt · lore

Re: [PATCH 06/19] refspec: remove global tag refspec structure

Patrick Steinhardt <ps@pks.im> writes:
Show 11 quoted lines
> +	refspec_item_init(&tag_refspec, TAG_REFSPEC, 0);
> ...
> -static struct refspec_item s_tag_refspec = {
> -	.force = 0,
> -	.pattern = 1,
> -	.matching = 0,
> -	.exact_sha1 = 0,
> -	.negative = 0,
> -	.src = "refs/tags/*",
> -	.dst = "refs/tags/*",
> -};

Regardless of the constness issue, replacing these hardcoded initializer values that is an accident waiting to happen with a call to refspec_item_init() is very much welcomed.

Unless parse_refspec() is an immensely high cost operation, which it isn't.

Patrick Steinhardt· May 29, 2024, 12:44 UTC · re: Patrick Steinhardt · lore

[PATCH 07/19] http: do not assign string constant to non-const field

In `write_accept_language()`, we put all acceptable languages into an array. While all entries in that array are allocated strings, the final entry in that array is a string constant. This is fine because we explicitly skip over the last entry when freeing the array, but will cause warnings once we enable `-Wwrite-strings`.

Adapt the code to also allocate the final entry.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 http.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)
Show changes to http.c +2 −3
diff --git a/http.c b/http.c
index 67cc47d28f..2dea2d03da 100644
--- a/http.c
+++ b/http.c
@@ -1974,7 +1974,7 @@ static void write_accept_language(struct strbuf *buf)
 
 		/* add '*' */
 		REALLOC_ARRAY(language_tags, num_langs + 1);
-		language_tags[num_langs++] = "*"; /* it's OK; this won't be freed */
+		language_tags[num_langs++] = xstrdup("*");
 
 		/* compute decimal_places */
 		for (max_q = 1, decimal_places = 0;
@@ -2004,8 +2004,7 @@ static void write_accept_language(struct strbuf *buf)
 		}
 	}
 
-	/* free language tags -- last one is a static '*' */
-	for (i = 0; i < num_langs - 1; i++)
+	for (i = 0; i < num_langs; i++)
 		free(language_tags[i]);
 	free(language_tags);
 }
-- 
2.45.1.313.g3a57aa566a.dirty
Junio C Hamano· May 29, 2024, 19:39 UTC · re: Patrick Steinhardt · lore

Re: [PATCH 07/19] http: do not assign string constant to non-const field

Patrick Steinhardt <ps@pks.im> writes:
Show 36 quoted lines
> In `write_accept_language()`, we put all acceptable languages into an
> array. While all entries in that array are allocated strings, the final
> entry in that array is a string constant. This is fine because we
> explicitly skip over the last entry when freeing the array, but will
> cause warnings once we enable `-Wwrite-strings`.
>
> Adapt the code to also allocate the final entry.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  http.c | 5 ++---
>  1 file changed, 2 insertions(+), 3 deletions(-)
>
> diff --git a/http.c b/http.c
> index 67cc47d28f..2dea2d03da 100644
> --- a/http.c
> +++ b/http.c
> @@ -1974,7 +1974,7 @@ static void write_accept_language(struct strbuf *buf)
>  
>  		/* add '*' */
>  		REALLOC_ARRAY(language_tags, num_langs + 1);
> -		language_tags[num_langs++] = "*"; /* it's OK; this won't be freed */
> +		language_tags[num_langs++] = xstrdup("*");
>  
>  		/* compute decimal_places */
>  		for (max_q = 1, decimal_places = 0;
> @@ -2004,8 +2004,7 @@ static void write_accept_language(struct strbuf *buf)
>  		}
>  	}
>  
> -	/* free language tags -- last one is a static '*' */
> -	for (i = 0; i < num_langs - 1; i++)
> +	for (i = 0; i < num_langs; i++)
>  		free(language_tags[i]);
>  	free(language_tags);
>  }
Makes sense, especially that this is done only once per process.
Patrick Steinhardt· May 29, 2024, 12:44 UTC · re: Patrick Steinhardt · lore

[PATCH 08/19] line-log: always allocate the output prefix

The returned string by `output_prefix()` is sometimes a string constant and sometimes an allocated string. This has been fine until now because we always leak the allocated strings, and thus we never tried to free the string constant.

Fix the code to always return an allocated string and free the returned value at all callsites.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 line-log.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)
Show changes to line-log.c +11 −7
diff --git a/line-log.c b/line-log.c
index d9bf2c8120..9a298209d0 100644
--- a/line-log.c
+++ b/line-log.c
@@ -899,14 +899,12 @@ static void print_line(const char *prefix, char first,
 
 static char *output_prefix(struct diff_options *opt)
 {
-	char *prefix = "";
-
 	if (opt->output_prefix) {
 		struct strbuf *sb = opt->output_prefix(opt, opt->output_prefix_data);
-		prefix = sb->buf;
+		return sb->buf;
+	} else {
+		return xstrdup("");
 	}
-
-	return prefix;
 }
 
 static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *range)
@@ -927,7 +925,7 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang
 	const char *c_context = diff_get_color(opt->use_color, DIFF_CONTEXT);
 
 	if (!pair || !diff)
-		return;
+		goto out;
 
 	if (pair->one->oid_valid)
 		fill_line_ends(rev->diffopt.repo, pair->one, &p_lines, &p_ends);
@@ -1002,8 +1000,10 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang
 				   c_context, c_reset, opt->file);
 	}
 
+out:
 	free(p_ends);
 	free(t_ends);
+	free(prefix);
 }
 
 /*
@@ -1012,7 +1012,11 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang
  */
 static void dump_diff_hacky(struct rev_info *rev, struct line_log_data *range)
 {
-	fprintf(rev->diffopt.file, "%s\n", output_prefix(&rev->diffopt));
+	char *prefix = output_prefix(&rev->diffopt);
+
+	fprintf(rev->diffopt.file, "%s\n", prefix);
+	free(prefix);
+
 	while (range) {
 		dump_diff_hacky_one(rev, range);
 		range = range->next;
-- 
2.45.1.313.g3a57aa566a.dirty
Junio C Hamano· May 29, 2024, 19:51 UTC · re: Patrick Steinhardt · lore

Re: [PATCH 08/19] line-log: always allocate the output prefix

Patrick Steinhardt <ps@pks.im> writes:
Show 7 quoted lines
> The returned string by `output_prefix()` is sometimes a string constant
> and sometimes an allocated string. This has been fine until now because
> we always leak the allocated strings, and thus we never tried to free
> the string constant.
>
> Fix the code to always return an allocated string and free the returned
> value at all callsites.
Yay!

This leak always has bothered me, as it is not just once per process invocation, but once per each commit that is shown.

Patrick Steinhardt· May 29, 2024, 12:44 UTC · re: Patrick Steinhardt · lore

[PATCH 09/19] object-file: make `buf` parameter of `index_mem()` a constant

The `buf` parameter of `index_mem()` is a non-constant string. This will break once we enable `-Wwrite-strings` because we also pass constants from at least one callsite.

Adapt the parameter to be a constant. As we cannot free the buffer without casting now, this also requires us to move the lifetime of the nested buffer around.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 object-file.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)
Show changes to object-file.c +7 −7
diff --git a/object-file.c b/object-file.c
index c9e374e57e..46ea00ac46 100644
--- a/object-file.c
+++ b/object-file.c
@@ -2483,12 +2483,13 @@ static int hash_format_check_report(struct fsck_options *opts UNUSED,
 }
 
 static int index_mem(struct index_state *istate,
-		     struct object_id *oid, void *buf, size_t size,
+		     struct object_id *oid,
+		     const void *buf, size_t size,
 		     enum object_type type,
 		     const char *path, unsigned flags)
 {
+	struct strbuf nbuf = STRBUF_INIT;
 	int ret = 0;
-	int re_allocated = 0;
 	int write_object = flags & HASH_WRITE_OBJECT;
 
 	if (!type)
@@ -2498,11 +2499,10 @@ static int index_mem(struct index_state *istate,
 	 * Convert blobs to git internal format
 	 */
 	if ((type == OBJ_BLOB) && path) {
-		struct strbuf nbuf = STRBUF_INIT;
 		if (convert_to_git(istate, path, buf, size, &nbuf,
 				   get_conv_flags(flags))) {
-			buf = strbuf_detach(&nbuf, &size);
-			re_allocated = 1;
+			buf = nbuf.buf;
+			size = nbuf.len;
 		}
 	}
 	if (flags & HASH_FORMAT_CHECK) {
@@ -2519,8 +2519,8 @@ static int index_mem(struct index_state *istate,
 		ret = write_object_file(buf, size, type, oid);
 	else
 		hash_object_file(the_hash_algo, buf, size, type, oid);
-	if (re_allocated)
-		free(buf);
+
+	strbuf_release(&nbuf);
 	return ret;
 }
 
-- 
2.45.1.313.g3a57aa566a.dirty
Junio C Hamano· May 29, 2024, 20:01 UTC · re: Patrick Steinhardt · lore

Re: [PATCH 09/19] object-file: make `buf` parameter of `index_mem()` a constant

Patrick Steinhardt <ps@pks.im> writes:
Show 7 quoted lines
> The `buf` parameter of `index_mem()` is a non-constant string. This will
> break once we enable `-Wwrite-strings` because we also pass constants
> from at least one callsite.
>
> Adapt the parameter to be a constant. As we cannot free the buffer
> without casting now, this also requires us to move the lifetime of the
> nested buffer around.
Makes sense.
Patrick Steinhardt· May 29, 2024, 12:44 UTC · re: Patrick Steinhardt · lore

[PATCH 10/19] parse-options: cast long name for OPTION_ALIAS

We assign the long name for OPTION_ALIAS options to a non-constant value field. We know that the variable will never be written to, but this will cause warnings once we enable `-Wwrite-strings`.

Cast away the constness to be prepared for this change.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 parse-options.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to parse-options.h +1 −1
diff --git a/parse-options.h b/parse-options.h
index bd62e20268..ae15342390 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -355,7 +355,7 @@ struct option {
 	.type = OPTION_ALIAS, \
 	.short_name = (s), \
 	.long_name = (l), \
-	.value = (source_long_name), \
+	.value = (char *)(source_long_name), \
 }
 
 #define OPT_SUBCOMMAND_F(l, v, fn, f) { \
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 29, 2024, 12:44 UTC · re: Patrick Steinhardt · lore

[PATCH 11/19] send-pack: always allocate receive status

In `receive_status()`, we record the reason why ref updates have been rejected by the remote via the `remote_status`. But while we allocate the assigned string when a reason was given, we assign a string constant when no reason was given.

This has been working fine so far due to two reasons:
  - We don't ever free the refs in git-send-pack(1)'
  - Remotes always give a reason, at least as implemented by Git proper.

Adapt the code to always allocate the receive status string and free the refs.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/send-pack.c | 2 ++
 send-pack.c         | 2 +-
 2 files changed, 3 insertions(+), 1 deletion(-)
Show changes to 2 files +3 −1

builtin/send-pack.c, send-pack.c

diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index 3df9eaad09..17cae6bbbd 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -336,5 +336,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
 		/* stable plumbing output; do not modify or localize */
 		fprintf(stderr, "Everything up-to-date\n");
 
+	free_refs(remote_refs);
+	free_refs(local_refs);
 	return ret;
 }
diff --git a/send-pack.c b/send-pack.c
index 37f59d4f66..88e96d000b 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -259,7 +259,7 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)
 			if (p)
 				hint->remote_status = xstrdup(p);
 			else
-				hint->remote_status = "failed";
+				hint->remote_status = xstrdup("failed");
 		} else {
 			hint->status = REF_STATUS_OK;
 			hint->remote_status = xstrdup_or_null(p);
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 29, 2024, 12:44 UTC · re: Patrick Steinhardt · lore

[PATCH 12/19] remote-curl: avoid assigning string constant to non-const variable

When processing remote options, we split the option line into two by searching for a space. If there is one, we replace the space with '\0', otherwise we implicitly assume that the value is "true" and thus assign a string constant.

As the return value of strchr(3P) weirdly enough is a `char *` even though it gets a `const char *` as input, the assigned-to variable also is a non-constant. This is fine though because the argument is in fact an allocated string, and thus we are allowed to modify it. But this will break once we enable `-Wwrite-strings`.

Refactor the code stop splitting the fields with '\0' altogether. Instead, we can pass the length of the option name to `set_option()` and then use strncmp(3P) instead of strcmp(3P).

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 remote-curl.c | 58 +++++++++++++++++++++++++++------------------------
 1 file changed, 31 insertions(+), 27 deletions(-)
Show changes to remote-curl.c +31 −27
diff --git a/remote-curl.c b/remote-curl.c
index cae98384da..ac185d4f88 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -58,9 +58,9 @@ struct options {
 static struct options options;
 static struct string_list cas_options = STRING_LIST_INIT_DUP;
 
-static int set_option(const char *name, const char *value)
+static int set_option(const char *name, size_t namelen, const char *value)
 {
-	if (!strcmp(name, "verbosity")) {
+	if (!strncmp(name, "verbosity", namelen)) {
 		char *end;
 		int v = strtol(value, &end, 10);
 		if (value == end || *end)
@@ -68,7 +68,7 @@ static int set_option(const char *name, const char *value)
 		options.verbosity = v;
 		return 0;
 	}
-	else if (!strcmp(name, "progress")) {
+	else if (!strncmp(name, "progress", namelen)) {
 		if (!strcmp(value, "true"))
 			options.progress = 1;
 		else if (!strcmp(value, "false"))
@@ -77,7 +77,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "depth")) {
+	else if (!strncmp(name, "depth", namelen)) {
 		char *end;
 		unsigned long v = strtoul(value, &end, 10);
 		if (value == end || *end)
@@ -85,15 +85,15 @@ static int set_option(const char *name, const char *value)
 		options.depth = v;
 		return 0;
 	}
-	else if (!strcmp(name, "deepen-since")) {
+	else if (!strncmp(name, "deepen-since", namelen)) {
 		options.deepen_since = xstrdup(value);
 		return 0;
 	}
-	else if (!strcmp(name, "deepen-not")) {
+	else if (!strncmp(name, "deepen-not", namelen)) {
 		string_list_append(&options.deepen_not, value);
 		return 0;
 	}
-	else if (!strcmp(name, "deepen-relative")) {
+	else if (!strncmp(name, "deepen-relative", namelen)) {
 		if (!strcmp(value, "true"))
 			options.deepen_relative = 1;
 		else if (!strcmp(value, "false"))
@@ -102,7 +102,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "followtags")) {
+	else if (!strncmp(name, "followtags", namelen)) {
 		if (!strcmp(value, "true"))
 			options.followtags = 1;
 		else if (!strcmp(value, "false"))
@@ -111,7 +111,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "dry-run")) {
+	else if (!strncmp(name, "dry-run", namelen)) {
 		if (!strcmp(value, "true"))
 			options.dry_run = 1;
 		else if (!strcmp(value, "false"))
@@ -120,7 +120,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "check-connectivity")) {
+	else if (!strncmp(name, "check-connectivity", namelen)) {
 		if (!strcmp(value, "true"))
 			options.check_self_contained_and_connected = 1;
 		else if (!strcmp(value, "false"))
@@ -129,7 +129,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "cas")) {
+	else if (!strncmp(name, "cas", namelen)) {
 		struct strbuf val = STRBUF_INIT;
 		strbuf_addstr(&val, "--force-with-lease=");
 		if (*value != '"')
@@ -139,7 +139,7 @@ static int set_option(const char *name, const char *value)
 		string_list_append(&cas_options, val.buf);
 		strbuf_release(&val);
 		return 0;
-	} else if (!strcmp(name, TRANS_OPT_FORCE_IF_INCLUDES)) {
+	} else if (!strncmp(name, TRANS_OPT_FORCE_IF_INCLUDES, namelen)) {
 		if (!strcmp(value, "true"))
 			options.force_if_includes = 1;
 		else if (!strcmp(value, "false"))
@@ -147,7 +147,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "cloning")) {
+	} else if (!strncmp(name, "cloning", namelen)) {
 		if (!strcmp(value, "true"))
 			options.cloning = 1;
 		else if (!strcmp(value, "false"))
@@ -155,7 +155,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "update-shallow")) {
+	} else if (!strncmp(name, "update-shallow", namelen)) {
 		if (!strcmp(value, "true"))
 			options.update_shallow = 1;
 		else if (!strcmp(value, "false"))
@@ -163,7 +163,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "pushcert")) {
+	} else if (!strncmp(name, "pushcert", namelen)) {
 		if (!strcmp(value, "true"))
 			options.push_cert = SEND_PACK_PUSH_CERT_ALWAYS;
 		else if (!strcmp(value, "false"))
@@ -173,7 +173,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "atomic")) {
+	} else if (!strncmp(name, "atomic", namelen)) {
 		if (!strcmp(value, "true"))
 			options.atomic = 1;
 		else if (!strcmp(value, "false"))
@@ -181,7 +181,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "push-option")) {
+	} else if (!strncmp(name, "push-option", namelen)) {
 		if (*value != '"')
 			string_list_append(&options.push_options, value);
 		else {
@@ -192,7 +192,7 @@ static int set_option(const char *name, const char *value)
 						 strbuf_detach(&unquoted, NULL));
 		}
 		return 0;
-	} else if (!strcmp(name, "family")) {
+	} else if (!strncmp(name, "family", namelen)) {
 		if (!strcmp(value, "ipv4"))
 			git_curl_ipresolve = CURL_IPRESOLVE_V4;
 		else if (!strcmp(value, "ipv6"))
@@ -202,16 +202,16 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "from-promisor")) {
+	} else if (!strncmp(name, "from-promisor", namelen)) {
 		options.from_promisor = 1;
 		return 0;
-	} else if (!strcmp(name, "refetch")) {
+	} else if (!strncmp(name, "refetch", namelen)) {
 		options.refetch = 1;
 		return 0;
-	} else if (!strcmp(name, "filter")) {
+	} else if (!strncmp(name, "filter", namelen)) {
 		options.filter = xstrdup(value);
 		return 0;
-	} else if (!strcmp(name, "object-format")) {
+	} else if (!strncmp(name, "object-format", namelen)) {
 		options.object_format = 1;
 		if (strcmp(value, "true"))
 			die(_("unknown value for object-format: %s"), value);
@@ -1588,15 +1588,19 @@ int cmd_main(int argc, const char **argv)
 			parse_push(&buf);
 
 		} else if (skip_prefix(buf.buf, "option ", &arg)) {
-			char *value = strchr(arg, ' ');
+			const char *value = strchr(arg, ' ');
+			size_t arglen;
 			int result;
 
-			if (value)
-				*value++ = '\0';
-			else
+			if (value) {
+				arglen = value - arg;
+				value++;
+			} else {
+				arglen = strlen(arg);
 				value = "true";
+			}
 
-			result = set_option(arg, value);
+			result = set_option(arg, arglen, value);
 			if (!result)
 				printf("ok\n");
 			else if (result < 0)
-- 
2.45.1.313.g3a57aa566a.dirty
Junio C Hamano· May 29, 2024, 20:21 UTC · re: Patrick Steinhardt · lore

Re: [PATCH 12/19] remote-curl: avoid assigning string constant to non-const variable

Patrick Steinhardt <ps@pks.im> writes:
Show 12 quoted lines
>  		} else if (skip_prefix(buf.buf, "option ", &arg)) {
> +			const char *value = strchr(arg, ' ');
> +			size_t arglen;
>  			int result;
>  
> +			if (value) {
> +				arglen = value - arg;
> +				value++;
> +			} else {
> +				arglen = strlen(arg);
>  				value = "true";
> +			}
There is a micro optimization opportunity here.
	const char *value = strchrnul(arg, ' ');
	size_t arglen = value - arg;
	if (*value)
		value++; /* skip over SP */
	else
		value = "true";
But other than that, very cleanly done.
Patrick Steinhardt· May 30, 2024, 11:30 UTC · re: Junio C Hamano · lore

Re: [PATCH 12/19] remote-curl: avoid assigning string constant to non-const variable

On Wed, May 29, 2024 at 01:21:47PM -0700, Junio C Hamano wrote:
Show 26 quoted lines
> Patrick Steinhardt <ps@pks.im> writes:
> 
> >  		} else if (skip_prefix(buf.buf, "option ", &arg)) {
> > +			const char *value = strchr(arg, ' ');
> > +			size_t arglen;
> >  			int result;
> >  
> > +			if (value) {
> > +				arglen = value - arg;
> > +				value++;
> > +			} else {
> > +				arglen = strlen(arg);
> >  				value = "true";
> > +			}
> 
> There is a micro optimization opportunity here.
> 
> 	const char *value = strchrnul(arg, ' ');
> 	size_t arglen = value - arg;
> 
> 	if (*value)
> 		value++; /* skip over SP */
> 	else
> 		value = "true";
> 
> But other than that, very cleanly done.
Indeed, that version looks nicer. Thanks!
Patrick
Patrick Steinhardt· May 29, 2024, 12:45 UTC · re: Patrick Steinhardt · lore

[PATCH 13/19] revision: always store allocated strings in output encoding

The `git_log_output_encoding` variable can be set via the `--encoding=` option. When doing so, we conditionally either assign it to the passed value, or if the value is "none" we assign it the empty string. Depending on which of the both code paths we pick though, the variable may end up being assigned either an allocated string or a string constant.

This is somewhat risky and may easily lead to bugs when a different code path may want to reassign a new value to it, freeing the previous value. We already to this when parsing the "i18n.logoutputencoding" config in `git_default_i18n_config()`. But because the config is typically parsed before we parse command line options this has been fine so far.

Regardless of that, safeguard the code such that the variable always contains an allocated string. While at it, also free the old value in case there was any to plug a potential memory leak.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 revision.c             | 3 ++-
 t/t3900-i18n-commit.sh | 1 +
 t/t3901-i18n-patch.sh  | 1 +
 3 files changed, 4 insertions(+), 1 deletion(-)
Show changes to 3 files +4 −1

revision.c, t/t3900-i18n-commit.sh, t/t3901-i18n-patch.sh

diff --git a/revision.c b/revision.c
index 7ddf0f151a..2ee6886078 100644
--- a/revision.c
+++ b/revision.c
@@ -2650,10 +2650,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 	} else if (!strcmp(arg, "--invert-grep")) {
 		revs->grep_filter.no_body_match = 1;
 	} else if ((argcount = parse_long_opt("encoding", argv, &optarg))) {
+		free(git_log_output_encoding);
 		if (strcmp(optarg, "none"))
 			git_log_output_encoding = xstrdup(optarg);
 		else
-			git_log_output_encoding = "";
+			git_log_output_encoding = xstrdup("");
 		return argcount;
 	} else if (!strcmp(arg, "--reverse")) {
 		revs->reverse ^= 1;
diff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh
index f27d09cfd9..db7b403bc1 100755
--- a/t/t3900-i18n-commit.sh
+++ b/t/t3900-i18n-commit.sh
@@ -5,6 +5,7 @@
 
 test_description='commit and log output encodings'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 compare_with () {
diff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh
index 4b37f78829..5f0b9afc3f 100755
--- a/t/t3901-i18n-patch.sh
+++ b/t/t3901-i18n-patch.sh
@@ -8,6 +8,7 @@ test_description='i18n settings and format-patch | am pipe'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 check_encoding () {
-- 
2.45.1.313.g3a57aa566a.dirty
Junio C Hamano· May 29, 2024, 20:23 UTC · re: Patrick Steinhardt · lore

Re: [PATCH 13/19] revision: always store allocated strings in output encoding

Patrick Steinhardt <ps@pks.im> writes:
Show 16 quoted lines
> The `git_log_output_encoding` variable can be set via the `--encoding=`
> option. When doing so, we conditionally either assign it to the passed
> value, or if the value is "none" we assign it the empty string.
> Depending on which of the both code paths we pick though, the variable
> may end up being assigned either an allocated string or a string
> constant.
>
> This is somewhat risky and may easily lead to bugs when a different code
> path may want to reassign a new value to it, freeing the previous value.
> We already to this when parsing the "i18n.logoutputencoding" config in
> `git_default_i18n_config()`. But because the config is typically parsed
> before we parse command line options this has been fine so far.
>
> Regardless of that, safeguard the code such that the variable always
> contains an allocated string. While at it, also free the old value in
> case there was any to plug a potential memory leak.

Nice. Now the thing always has to be freed once we are done. Consistency is good.

Patrick Steinhardt· May 29, 2024, 12:45 UTC · re: Patrick Steinhardt · lore

[PATCH 14/19] mailmap: always store allocated strings in mailmap blob

Same as with the preceding commit, the `git_mailmap_blob` may sometimes contain an allocated string and sometimes it may contain a string constant. This is risky and can easily lead to bugs in case the variable is getting re-assigned, where the code may then try to free the previous value to avoid memory leaks.

Safeguard the code by always storing allocated strings in the variable.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 mailmap.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to mailmap.c +1 −1
diff --git a/mailmap.c b/mailmap.c
index b2efe29b3d..3d1e092fef 100644
--- a/mailmap.c
+++ b/mailmap.c
@@ -216,7 +216,7 @@ int read_mailmap(struct string_list *map)
 	map->cmp = namemap_cmp;
 
 	if (!git_mailmap_blob && is_bare_repository())
-		git_mailmap_blob = "HEAD:.mailmap";
+		git_mailmap_blob = xstrdup("HEAD:.mailmap");
 
 	if (!startup_info->have_repository || !is_bare_repository())
 		err |= read_mailmap_file(map, ".mailmap",
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 29, 2024, 12:45 UTC · re: Patrick Steinhardt · lore

[PATCH 15/19] imap-send: drop global `imap_server_conf` variable

In "imap-send.c", we have a global `sturct imap_server_conf` variable that keeps track of the configuration of the IMAP server. This variable is being populated mostly via the Git configuration.

Refactor the code to allocate the structure on the stack instead of having it globally. This change allows us to track its lifetime more closely.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 imap-send.c | 57 ++++++++++++++++++++++++++++-------------------------
 1 file changed, 30 insertions(+), 27 deletions(-)
Show changes to imap-send.c +30 −27
diff --git a/imap-send.c b/imap-send.c
index 8b723b34a5..67a7a6c456 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -82,10 +82,6 @@ struct imap_server_conf {
 	char *auth_method;
 };
 
-static struct imap_server_conf server = {
-	.ssl_verify = 1,
-};
-
 struct imap_socket {
 	int fd[2];
 	SSL *ssl;
@@ -110,6 +106,7 @@ struct imap {
 };
 
 struct imap_store {
+	const struct imap_server_conf *cfg;
 	/* currently open mailbox */
 	const char *name; /* foreign! maybe preset? */
 	int uidvalidity;
@@ -194,8 +191,8 @@ static void socket_perror(const char *func, struct imap_socket *sock, int ret)
 
 #ifdef NO_OPENSSL
 static int ssl_socket_connect(struct imap_socket *sock UNUSED,
-			      int use_tls_only UNUSED,
-			      int verify UNUSED)
+			      const struct imap_server_conf *cfg,
+			      int use_tls_only UNUSED)
 {
 	fprintf(stderr, "SSL requested but SSL support not compiled in\n");
 	return -1;
@@ -250,7 +247,9 @@ static int verify_hostname(X509 *cert, const char *hostname)
 		     cname, hostname);
 }
 
-static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int verify)
+static int ssl_socket_connect(struct imap_socket *sock,
+			      const struct imap_server_conf *cfg,
+			      int use_tls_only)
 {
 #if (OPENSSL_VERSION_NUMBER >= 0x10000000L)
 	const SSL_METHOD *meth;
@@ -279,7 +278,7 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
 	if (use_tls_only)
 		SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
 
-	if (verify)
+	if (cfg->ssl_verify)
 		SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
 
 	if (!SSL_CTX_set_default_verify_paths(ctx)) {
@@ -306,9 +305,9 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
 	 * OpenSSL does not document this function, but the implementation
 	 * returns 1 on success, 0 on failure after calling SSLerr().
 	 */
-	ret = SSL_set_tlsext_host_name(sock->ssl, server.host);
+	ret = SSL_set_tlsext_host_name(sock->ssl, cfg->host);
 	if (ret != 1)
-		warning("SSL_set_tlsext_host_name(%s) failed.", server.host);
+		warning("SSL_set_tlsext_host_name(%s) failed.", cfg->host);
 #endif
 
 	ret = SSL_connect(sock->ssl);
@@ -317,12 +316,12 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
 		return -1;
 	}
 
-	if (verify) {
+	if (cfg->ssl_verify) {
 		/* make sure the hostname matches that of the certificate */
 		cert = SSL_get_peer_certificate(sock->ssl);
 		if (!cert)
 			return error("unable to get peer certificate.");
-		if (verify_hostname(cert, server.host) < 0)
+		if (verify_hostname(cert, cfg->host) < 0)
 			return -1;
 	}
 
@@ -895,7 +894,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)
 	int ret;
 	char *response;
 
-	response = cram(prompt, server.user, server.pass);
+	response = cram(prompt, ctx->cfg->user, ctx->cfg->pass);
 
 	ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));
 	if (ret != strlen(response))
@@ -935,6 +934,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
 
 	CALLOC_ARRAY(ctx, 1);
 
+	ctx->cfg = srvc;
 	ctx->imap = CALLOC_ARRAY(imap, 1);
 	imap->buf.sock.fd[0] = imap->buf.sock.fd[1] = -1;
 	imap->in_progress_append = &imap->in_progress;
@@ -1035,7 +1035,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
 		imap->buf.sock.fd[1] = dup(s);
 
 		if (srvc->use_ssl &&
-		    ssl_socket_connect(&imap->buf.sock, 0, srvc->ssl_verify)) {
+		    ssl_socket_connect(&imap->buf.sock, srvc, 0)) {
 			close(s);
 			goto bail;
 		}
@@ -1068,8 +1068,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
 		if (!srvc->use_ssl && CAP(STARTTLS)) {
 			if (imap_exec(ctx, NULL, "STARTTLS") != RESP_OK)
 				goto bail;
-			if (ssl_socket_connect(&imap->buf.sock, 1,
-					       srvc->ssl_verify))
+			if (ssl_socket_connect(&imap->buf.sock, srvc, 1))
 				goto bail;
 			/* capabilities may have changed, so get the new capabilities */
 			if (imap_exec(ctx, NULL, "CAPABILITY") != RESP_OK)
@@ -1299,23 +1298,24 @@ static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, int *ofs)
 static int git_imap_config(const char *var, const char *val,
 			   const struct config_context *ctx, void *cb)
 {
+	struct imap_server_conf *cfg = cb;
 
 	if (!strcmp("imap.sslverify", var))
-		server.ssl_verify = git_config_bool(var, val);
+		cfg->ssl_verify = git_config_bool(var, val);
 	else if (!strcmp("imap.preformattedhtml", var))
-		server.use_html = git_config_bool(var, val);
+		cfg->use_html = git_config_bool(var, val);
 	else if (!strcmp("imap.folder", var))
-		return git_config_string(&server.folder, var, val);
+		return git_config_string(&cfg->folder, var, val);
 	else if (!strcmp("imap.user", var))
-		return git_config_string(&server.user, var, val);
+		return git_config_string(&cfg->user, var, val);
 	else if (!strcmp("imap.pass", var))
-		return git_config_string(&server.pass, var, val);
+		return git_config_string(&cfg->pass, var, val);
 	else if (!strcmp("imap.tunnel", var))
-		return git_config_string(&server.tunnel, var, val);
+		return git_config_string(&cfg->tunnel, var, val);
 	else if (!strcmp("imap.authmethod", var))
-		return git_config_string(&server.auth_method, var, val);
+		return git_config_string(&cfg->auth_method, var, val);
 	else if (!strcmp("imap.port", var))
-		server.port = git_config_int(var, val, ctx->kvi);
+		cfg->port = git_config_int(var, val, ctx->kvi);
 	else if (!strcmp("imap.host", var)) {
 		if (!val) {
 			return config_error_nonbool(var);
@@ -1324,11 +1324,11 @@ static int git_imap_config(const char *var, const char *val,
 				val += 5;
 			else if (starts_with(val, "imaps:")) {
 				val += 6;
-				server.use_ssl = 1;
+				cfg->use_ssl = 1;
 			}
 			if (starts_with(val, "//"))
 				val += 2;
-			server.host = xstrdup(val);
+			cfg->host = xstrdup(val);
 		}
 	} else
 		return git_default_config(var, val, ctx, cb);
@@ -1497,12 +1497,15 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,
 
 int cmd_main(int argc, const char **argv)
 {
+	struct imap_server_conf server = {
+		.ssl_verify = 1,
+	};
 	struct strbuf all_msgs = STRBUF_INIT;
 	int total;
 	int nongit_ok;
 
 	setup_git_directory_gently(&nongit_ok);
-	git_config(git_imap_config, NULL);
+	git_config(git_imap_config, &server);
 
 	argc = parse_options(argc, (const char **)argv, "", imap_send_options, imap_send_usage, 0);
 
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 29, 2024, 12:45 UTC · re: Patrick Steinhardt · lore

[PATCH 16/19] imap-send: fix leaking memory in `imap_server_conf`

We never free any of the config strings that we populate into the `struct imap_server_conf`. Fix this by creating a common exit path where we can free resources.

While at it, drop the unused variables `imap_server_conf::name` and `nongit_ok`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 imap-send.c | 68 +++++++++++++++++++++++++++++++++--------------------
 1 file changed, 43 insertions(+), 25 deletions(-)
Show changes to imap-send.c +43 −25
diff --git a/imap-send.c b/imap-send.c
index 67a7a6c456..a6fb33806c 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -69,7 +69,6 @@ static void imap_warn(const char *, ...);
 static char *next_arg(char **);
 
 struct imap_server_conf {
-	const char *name;
 	char *tunnel;
 	char *host;
 	int port;
@@ -1300,23 +1299,28 @@ static int git_imap_config(const char *var, const char *val,
 {
 	struct imap_server_conf *cfg = cb;
 
-	if (!strcmp("imap.sslverify", var))
+	if (!strcmp("imap.sslverify", var)) {
 		cfg->ssl_verify = git_config_bool(var, val);
-	else if (!strcmp("imap.preformattedhtml", var))
+	} else if (!strcmp("imap.preformattedhtml", var)) {
 		cfg->use_html = git_config_bool(var, val);
-	else if (!strcmp("imap.folder", var))
+	} else if (!strcmp("imap.folder", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->folder, var, val);
-	else if (!strcmp("imap.user", var))
+	} else if (!strcmp("imap.user", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->user, var, val);
-	else if (!strcmp("imap.pass", var))
+	} else if (!strcmp("imap.pass", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->pass, var, val);
-	else if (!strcmp("imap.tunnel", var))
+	} else if (!strcmp("imap.tunnel", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->tunnel, var, val);
-	else if (!strcmp("imap.authmethod", var))
+	} else if (!strcmp("imap.authmethod", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->auth_method, var, val);
-	else if (!strcmp("imap.port", var))
+	} else if (!strcmp("imap.port", var)) {
 		cfg->port = git_config_int(var, val, ctx->kvi);
-	else if (!strcmp("imap.host", var)) {
+	} else if (!strcmp("imap.host", var)) {
 		if (!val) {
 			return config_error_nonbool(var);
 		} else {
@@ -1330,8 +1334,9 @@ static int git_imap_config(const char *var, const char *val,
 				val += 2;
 			cfg->host = xstrdup(val);
 		}
-	} else
+	} else {
 		return git_default_config(var, val, ctx, cb);
+	}
 
 	return 0;
 }
@@ -1502,9 +1507,9 @@ int cmd_main(int argc, const char **argv)
 	};
 	struct strbuf all_msgs = STRBUF_INIT;
 	int total;
-	int nongit_ok;
+	int ret;
 
-	setup_git_directory_gently(&nongit_ok);
+	setup_git_directory_gently(NULL);
 	git_config(git_imap_config, &server);
 
 	argc = parse_options(argc, (const char **)argv, "", imap_send_options, imap_send_usage, 0);
@@ -1529,42 +1534,55 @@ int cmd_main(int argc, const char **argv)
 
 	if (!server.folder) {
 		fprintf(stderr, "no imap store specified\n");
-		return 1;
+		ret = 1;
+		goto out;
 	}
 	if (!server.host) {
 		if (!server.tunnel) {
 			fprintf(stderr, "no imap host specified\n");
-			return 1;
+			ret = 1;
+			goto out;
 		}
-		server.host = "tunnel";
+		server.host = xstrdup("tunnel");
 	}
 
 	/* read the messages */
 	if (strbuf_read(&all_msgs, 0, 0) < 0) {
 		error_errno(_("could not read from stdin"));
-		return 1;
+		ret = 1;
+		goto out;
 	}
 
 	if (all_msgs.len == 0) {
 		fprintf(stderr, "nothing to send\n");
-		return 1;
+		ret = 1;
+		goto out;
 	}
 
 	total = count_messages(&all_msgs);
 	if (!total) {
 		fprintf(stderr, "no messages to send\n");
-		return 1;
+		ret = 1;
+		goto out;
 	}
 
 	/* write it to the imap server */
 
 	if (server.tunnel)
-		return append_msgs_to_imap(&server, &all_msgs, total);
-
+		ret = append_msgs_to_imap(&server, &all_msgs, total);
 #ifdef USE_CURL_FOR_IMAP_SEND
-	if (use_curl)
-		return curl_append_msgs_to_imap(&server, &all_msgs, total);
+	else if (use_curl)
+		ret = curl_append_msgs_to_imap(&server, &all_msgs, total);
 #endif
-
-	return append_msgs_to_imap(&server, &all_msgs, total);
+	else
+		ret = append_msgs_to_imap(&server, &all_msgs, total);
+
+out:
+	free(server.tunnel);
+	free(server.host);
+	free(server.folder);
+	free(server.user);
+	free(server.pass);
+	free(server.auth_method);
+	return ret;
 }
-- 
2.45.1.313.g3a57aa566a.dirty
Junio C Hamano· May 29, 2024, 20:55 UTC · re: Patrick Steinhardt · lore

Re: [PATCH 16/19] imap-send: fix leaking memory in `imap_server_conf`

Patrick Steinhardt <ps@pks.im> writes:
> We never free any of the config strings that we populate into the
> `struct imap_server_conf`. Fix this by creating a common exit path where
> we can free resources.

This is more like the previous step got rid of the anchor that made these strings reachable, so we need to turn around to free them, which is sort-of Meh, especially given that the leaked pieces of memory are small and very much bounded.

The main benefit of this change is to allow us prepare on the constness change in the other (read: API this thing uses from elsewhere) parts of the system, which is a very worthy goal.

> While at it, drop the unused variables `imap_server_conf::name` and
> `nongit_ok`.

The removal of the .name member may be correct, but I suspect the change to nongit_ok is a change in behaviour, and it could even be a regression.

> -	setup_git_directory_gently(&nongit_ok);
> +	setup_git_directory_gently(NULL);
The general idea behind &nongit_ok is that
 - Usually setup_git_directory_gently() dies if NULL is passed
   instead of a pointer to &nongit_ok.  Most of the Git command
   wants to make sure they have a repository to operate on, so this
   is a reasonable default behaviour.
 - Some commands would want to work also without having any
   repository, possibly with limited capability (e.g., "git apply"
   may want to work as a better "GNU patch", but obviously it cannot
   do "git apply --binary --3way" without having the object
   database).  They tell setup_git_directory_gently() not to die
   when outside a repository by passing a pointer to &nongit_ok, and
   instead tell if we are in a repository by storing 0/1 in it.

The idea is that a command that is willing to work outside a repository can disable selected features based on what it sees in nongit_ok. In the case of "imap-send", there is no such features that it needs to special case, perhaps because everything it does is supposed to work outside a repository?

So the short version of what worries me in this change is that we used to be able to operate without having a repository at all, but now we would barf if run outside a repository, no?

Thanks.
Patrick Steinhardt· May 30, 2024, 11:31 UTC · re: Junio C Hamano · lore

Re: [PATCH 16/19] imap-send: fix leaking memory in `imap_server_conf`

On Wed, May 29, 2024 at 01:55:13PM -0700, Junio C Hamano wrote:
Show 14 quoted lines
> Patrick Steinhardt <ps@pks.im> writes:
> 
> > We never free any of the config strings that we populate into the
> > `struct imap_server_conf`. Fix this by creating a common exit path where
> > we can free resources.
> 
> This is more like the previous step got rid of the anchor that made
> these strings reachable, so we need to turn around to free them,
> which is sort-of Meh, especially given that the leaked pieces of
> memory are small and very much bounded.
> 
> The main benefit of this change is to allow us prepare on the
> constness change in the other (read: API this thing uses from
> elsewhere) parts of the system, which is a very worthy goal.

That's the motivation in this series at least. But I also see it as a good goal by itself to get rid of the global state that we had before the preceding patch. It may not be necessary, but it certainly helps me personally to reason about code better.

Show 34 quoted lines
> > While at it, drop the unused variables `imap_server_conf::name` and
> > `nongit_ok`.
> 
> The removal of the .name member may be correct, but I suspect the
> change to nongit_ok is a change in behaviour, and it could even be a
> regression.
> 
> > -	setup_git_directory_gently(&nongit_ok);
> > +	setup_git_directory_gently(NULL);
> 
> The general idea behind &nongit_ok is that
> 
>  - Usually setup_git_directory_gently() dies if NULL is passed
>    instead of a pointer to &nongit_ok.  Most of the Git command
>    wants to make sure they have a repository to operate on, so this
>    is a reasonable default behaviour.
> 
>  - Some commands would want to work also without having any
>    repository, possibly with limited capability (e.g., "git apply"
>    may want to work as a better "GNU patch", but obviously it cannot
>    do "git apply --binary --3way" without having the object
>    database).  They tell setup_git_directory_gently() not to die
>    when outside a repository by passing a pointer to &nongit_ok, and
>    instead tell if we are in a repository by storing 0/1 in it.
> 
> The idea is that a command that is willing to work outside a
> repository can disable selected features based on what it sees in
> nongit_ok.  In the case of "imap-send", there is no such features
> that it needs to special case, perhaps because everything it does is
> supposed to work outside a repository?
> 
> So the short version of what worries me in this change is that we
> used to be able to operate without having a repository at all, but
> now we would barf if run outside a repository, no?

Oh, I wasn't aware that the parameter being `NULL` actually causes a change in behaviour. Which nicely demonstrates that we have some missing test coverage for git-imap-send(1).

In fact, it's not only "some". We don't have any test coverage at all for git-imap-send(1) as far as I can see. Which does make me rest a bit uneasy. And I suspect that it wouldn't be trivial to add given that it kind of requires something that talks IMAP on the receiving end.

Patrick
Junio C Hamano· May 30, 2024, 16:30 UTC · re: Patrick Steinhardt · lore

Re: [PATCH 16/19] imap-send: fix leaking memory in `imap_server_conf`

Patrick Steinhardt <ps@pks.im> writes:
Show 8 quoted lines
> Oh, I wasn't aware that the parameter being `NULL` actually causes a
> change in behaviour. Which nicely demonstrates that we have some missing
> test coverage for git-imap-send(1).
>
> In fact, it's not only "some". We don't have any test coverage at all
> for git-imap-send(1) as far as I can see. Which does make me rest a bit
> uneasy. And I suspect that it wouldn't be trivial to add given that it
> kind of requires something that talks IMAP on the receiving end.

Quite honestly, there is absolutely nothing that makes imap-send necessary to be part of Git suite. It does depend on Git as it uses our configuration files to store its knobs, but if it were written outside the context of the Git project as a handy way to move a draft message to your imap draft folder, it would have been perfectly fine as a standalone tool (and it would have used its own simple configuration file format).

Other than its use of .gitconfig, it does not even know what a commit is, unlike, say send-email [*], which is also fairly unrelated to Git proper.

    Side note: send-email is only marginally closer to Git than
    imap-send, as it optionally can run format-patch as a part of
    its operation.  Without the format-patch integration, it is just
    a MUA whose primary purpose is not to munge its payload.

So I do not find it surprising at all that we have no tests for it. We could do something like this at least, perhaps on top of the recent topic that added a test to ensure commands that ought to be operable without any repository?

 t/t1517-outside-repo.sh | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
Show changes to diff +15 −0
diff --git c/t/t1517-outside-repo.sh w/t/t1517-outside-repo.sh
index 557808ffa7..6d5f07df93 100755
--- c/t/t1517-outside-repo.sh
+++ w/t/t1517-outside-repo.sh
@@ -56,4 +56,19 @@ test_expect_success 'grep outside repository' '
 	test_cmp expect actual
 '
 
+test_expect_success 'imap-send outside repository' '
+	test_config_global imap.host imaps://localhost &&
+	test_config_global imap.folder Drafts &&
+
+	echo nothing to send >expect &&
+	test_must_fail git imap-send -v </dev/null 2>actual &&
+	test_cmp expect actual &&
+
+	(
+		cd non-repo &&
+		test_must_fail git imap-send -v </dev/null 2>../actual
+	) &&
+	test_cmp expect actual
+'
+
 test_done
Patrick Steinhardt· May 29, 2024, 12:45 UTC · re: Patrick Steinhardt · lore

[PATCH 17/19] builtin/rebase: adapt code to not assign string constants to non-const

When computing the rebase strategy we temporarily assign a string constant to `options.strategy` before we call `xstrdup()` on it. Furthermore, the default backend is being assigned a string constant via `REBASE_OPTIONS_INIT`. Both of these will cause warnings once we enable `-Wwrite-strings`.

Adapt the code such that we only store allocated strings in those variables.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rebase.c | 16 +++++++++-------
 1 file changed, 9 insertions(+), 7 deletions(-)
Show changes to builtin/rebase.c +9 −7
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 14d4f0a5e6..b05c3b6be3 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -135,7 +135,6 @@ struct rebase_options {
 		.type = REBASE_UNSPECIFIED,	  	\
 		.empty = EMPTY_UNSPECIFIED,	  	\
 		.keep_empty = 1,			\
-		.default_backend = "merge",	  	\
 		.flags = REBASE_NO_QUIET, 		\
 		.git_am_opts = STRVEC_INIT,		\
 		.exec = STRING_LIST_INIT_NODUP,		\
@@ -796,6 +795,7 @@ static int rebase_config(const char *var, const char *value,
 	}
 
 	if (!strcmp(var, "rebase.backend")) {
+		FREE_AND_NULL(opts->default_backend);
 		return git_config_string(&opts->default_backend, var, value);
 	}
 
@@ -1471,12 +1471,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	}
 
 	if (options.strategy_opts.nr && !options.strategy)
-		options.strategy = "ort";
-
-	if (options.strategy) {
-		options.strategy = xstrdup(options.strategy);
+		options.strategy = xstrdup("ort");
+	else
+		options.strategy = xstrdup_or_null(options.strategy);
+	if (options.strategy)
 		imply_merge(&options, "--strategy");
-	}
 
 	if (options.root && !options.onto_name)
 		imply_merge(&options, "--root without --onto");
@@ -1522,7 +1521,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	}
 
 	if (options.type == REBASE_UNSPECIFIED) {
-		if (!strcmp(options.default_backend, "merge"))
+		if (!options.default_backend)
+			options.type = REBASE_MERGE;
+		else if (!strcmp(options.default_backend, "merge"))
 			options.type = REBASE_MERGE;
 		else if (!strcmp(options.default_backend, "apply"))
 			options.type = REBASE_APPLY;
@@ -1833,6 +1834,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 cleanup:
 	strbuf_release(&buf);
 	strbuf_release(&revisions);
+	free(options.default_backend);
 	free(options.reflog_action);
 	free(options.head_name);
 	strvec_clear(&options.git_am_opts);
-- 
2.45.1.313.g3a57aa566a.dirty
Junio C Hamano· May 29, 2024, 21:01 UTC · re: Patrick Steinhardt · lore

Re: [PATCH 17/19] builtin/rebase: adapt code to not assign string constants to non-const

Patrick Steinhardt <ps@pks.im> writes:
Show 13 quoted lines
> When computing the rebase strategy we temporarily assign a string
> constant to `options.strategy` before we call `xstrdup()` on it.
> Furthermore, the default backend is being assigned a string constant via
> `REBASE_OPTIONS_INIT`. Both of these will cause warnings once we enable
> `-Wwrite-strings`.
>
> Adapt the code such that we only store allocated strings in those
> variables.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  builtin/rebase.c | 16 +++++++++-------
>  1 file changed, 9 insertions(+), 7 deletions(-)

One gripe I have in this change is that it used to be crystal clear what the hardcoded default was (i.e. written in the initialization data), but now you have to follow the program flow to see what the hardcoded default is.

Show 8 quoted lines
>  	if (options.type == REBASE_UNSPECIFIED) {
> -		if (!strcmp(options.default_backend, "merge"))
> +		if (!options.default_backend)
> +			options.type = REBASE_MERGE;
> +		else if (!strcmp(options.default_backend, "merge"))
>  			options.type = REBASE_MERGE;
>  		else if (!strcmp(options.default_backend, "apply"))
>  			options.type = REBASE_APPLY;
Patrick Steinhardt· May 30, 2024, 11:31 UTC · re: Junio C Hamano · lore

Re: [PATCH 17/19] builtin/rebase: adapt code to not assign string constants to non-const

On Wed, May 29, 2024 at 02:01:08PM -0700, Junio C Hamano wrote:
Show 20 quoted lines
> Patrick Steinhardt <ps@pks.im> writes:
> 
> > When computing the rebase strategy we temporarily assign a string
> > constant to `options.strategy` before we call `xstrdup()` on it.
> > Furthermore, the default backend is being assigned a string constant via
> > `REBASE_OPTIONS_INIT`. Both of these will cause warnings once we enable
> > `-Wwrite-strings`.
> >
> > Adapt the code such that we only store allocated strings in those
> > variables.
> >
> > Signed-off-by: Patrick Steinhardt <ps@pks.im>
> > ---
> >  builtin/rebase.c | 16 +++++++++-------
> >  1 file changed, 9 insertions(+), 7 deletions(-)
> 
> One gripe I have in this change is that it used to be crystal clear
> what the hardcoded default was (i.e. written in the initialization
> data), but now you have to follow the program flow to see what the
> hardcoded default is.

True. We could adapt the macro to instead `xstrdup()` the default backend. But that feels a bit hidden, so I don't think this is a partiuclarly great option.

An alternative would be to wrap initialization into a function `rebase_options_init()` that instead allocates the default backend string. We can then also create a `rebase_options_release()` counter part that releases its resources such that this whole thing becomes a bit more self contained.

Patrick
Patrick Steinhardt· May 29, 2024, 12:45 UTC · re: Patrick Steinhardt · lore

[PATCH 18/19] builtin/merge: always store allocated strings in `pull_twohead`

The `pull_twohead` configuration may sometimes contain an allocated string, and sometimes it may contain a string constant. Refactor this to instead always store an allocated string such that we can release its resources without risk.

While at it, manage the lifetime of other config strings, as well. Note that we explicitly don't free `cleanup_arg` here. This is because the variable may be assigned a string constant via command line options.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/merge.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)
Show changes to builtin/merge.c +11 −7
diff --git a/builtin/merge.c b/builtin/merge.c
index daed2d4e1e..fb3eb15b89 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -611,17 +611,19 @@ static int git_merge_config(const char *k, const char *v,
 		return 0;
 	}
 
-	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
+	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat")) {
 		show_diffstat = git_config_bool(k, v);
-	else if (!strcmp(k, "merge.verifysignatures"))
+	} else if (!strcmp(k, "merge.verifysignatures")) {
 		verify_signatures = git_config_bool(k, v);
-	else if (!strcmp(k, "pull.twohead"))
+	} else if (!strcmp(k, "pull.twohead")) {
+		FREE_AND_NULL(pull_twohead);
 		return git_config_string(&pull_twohead, k, v);
-	else if (!strcmp(k, "pull.octopus"))
+	} else if (!strcmp(k, "pull.octopus")) {
+		FREE_AND_NULL(pull_octopus);
 		return git_config_string(&pull_octopus, k, v);
-	else if (!strcmp(k, "commit.cleanup"))
+	} else if (!strcmp(k, "commit.cleanup")) {
 		return git_config_string(&cleanup_arg, k, v);
-	else if (!strcmp(k, "merge.ff")) {
+	} else if (!strcmp(k, "merge.ff")) {
 		int boolval = git_parse_maybe_bool(v);
 		if (0 <= boolval) {
 			fast_forward = boolval ? FF_ALLOW : FF_NO;
@@ -1294,7 +1296,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 	if (!pull_twohead) {
 		char *default_strategy = getenv("GIT_TEST_MERGE_ALGORITHM");
 		if (default_strategy && !strcmp(default_strategy, "ort"))
-			pull_twohead = "ort";
+			pull_twohead = xstrdup("ort");
 	}
 
 	init_diff_ui_defaults();
@@ -1793,6 +1795,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 	}
 	strbuf_release(&buf);
 	free(branch_to_free);
+	free(pull_twohead);
+	free(pull_octopus);
 	discard_index(the_repository->index);
 	return ret;
 }
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 29, 2024, 12:45 UTC · re: Patrick Steinhardt · lore

[PATCH 19/19] config.mak.dev: enable `-Wwrite-strings` warning

Writing to string constants is undefined behaviour and must be avoided in C. Even so, the compiler does not help us with this by default because those constants are not in fact marked as `const`. This makes it rather easy to accidentally assign a constant to a non-const variable or field and then later on try to either free it or write to it.

Enable `-Wwrite-strings` to catch such mistakes. With this warning enabled, the type of string constants is changed to `const char[]` and will thus cause compiler warnings when being assigned to non-const fields and variables.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 config.mak.dev | 1 +
 1 file changed, 1 insertion(+)
Show changes to config.mak.dev +1 −0
diff --git a/config.mak.dev b/config.mak.dev
index 981304727c..1ce4c70613 100644
--- a/config.mak.dev
+++ b/config.mak.dev
@@ -37,6 +37,7 @@ DEVELOPER_CFLAGS += -Wpointer-arith
 DEVELOPER_CFLAGS += -Wstrict-prototypes
 DEVELOPER_CFLAGS += -Wunused
 DEVELOPER_CFLAGS += -Wvla
+DEVELOPER_CFLAGS += -Wwrite-strings
 DEVELOPER_CFLAGS += -fno-common
 
 ifneq ($(filter clang4,$(COMPILER_FEATURES)),)
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 29, 2024, 12:52 UTC · re: Patrick Steinhardt · lore

Re: [PATCH 00/19] Compile with `-Wwrite-strings`

On Wed, May 29, 2024 at 02:44:01PM +0200, Patrick Steinhardt wrote:
Show 52 quoted lines
> Hi,
> 
> there were some recent discussions about compiler warnings and how to
> stay on top of breaking changes in compilers in general [1] and about
> string constants in particular [2]. This made me look into what kind of
> warnings we should reasonably enable, which led me to the following
> list of warnings that may be sensible:
> 
>   - `-Wformat-nonliteral` to warn about non-constant strings being
>     passed as format string.
> 
>   - `-Wwrite-strings` to warn about string constants being assigned to a
>     non-constant variable.
> 
>   - `-Wredundant-decls` to warn about redundant declarations.
> 
>   - `-Wconversion` to warn about implicit integer casts when they may
>     alter the value.
> 
> This patch series adapts our code to compile with `-Wwrite-strings`.
> This option will change the type of string constants from `char []` to
> `const char []` such that it is now invalid to assign it to non-const
> variables without a cast. The intent is to avoid undefined behaviour
> when accedintally writing to such strings and to avoid free'ing such a
> variable.
> 
> There are quite some cases where we mishandle this. Oftentimes we just
> didn't bother to free any memory at all, which made it a non-issue in
> the first place. Other times we had some special logic that prevents
> writing or freeing such strings. But in most cases it was an accident
> waiting to happen.
> 
> Even though the changes are quite invasive, I think that this is a step
> into the right direction. Many of the constructs feel quite fragile, and
> most of those get fixed in this series. Some others I just paper over,
> for example when assigning to structures with global lifetime where we
> know that they are never released at all.
> 
> I also have a patch series cooking for `-Wredundant-decls`. But while
> that warning detects some redundant declarations indeed, it creates a
> problem with `extern char **environ`. There is no header for it and
> programs are asked to declare it by themselves. But of course, some libc
> implementations disagree and declare it. I haven't found a nice way to
> work around this issue, but may send the patches that drop the redundant
> declarations nonetheless.
> 
> The other two warnings I haven't yet looked into.
> 
> I ran some test jobs on both GitHub [3] and GitLab [4] to verify that
> the result is sane.
> 
> Thanks!

I forgot to say that this is based on top of 3a57aa566a (The eighth batch, 2024-05-28) with ps/leakfixes merged into it at ebdbefa4fe (builtin/mv: fix leaks for submodule gitfile paths, 2024-05-27).

Patrick
Patrick Steinhardt· May 30, 2024, 12:50 UTC · re: Patrick Steinhardt · lore

[PATCH v2 00/19] Compile with `-Wwrite-strings`

Hi,

this is the second version of my patch series that prepares our code base to compile with `-Wwrite-strings`. This warning will convert the type of string constants from `char []` to `const char[]` so that it becomes harder to for example write to or free such constants by accident.

Changes compared to v2:
  - Merged the reftable-specific test into the second patch. Instead of
    adding casts, we now allocate the required strings on the stack.
  - Apply a micro-optimization to "remote-curl.c" that was suggested by
    Junio.
  - Restore the `nongit_ok` variable in "imap-send.c". It's somewhat
    concerning that we do not have test coverage for git-imap-send(1) at
    all. But it might be a bit more involved as we do not have any IMAP
    test infra, to the best of my knowledge.
  - Rework the patch to "builtin/rebase.c". It is now split into two
    patches. The first patch reworks initialization of the rebase
    options so that the defaults are still self-contained in a single
    place. And the second patch refactors how we set up the merge
    strategy.
Thanks!
Patrick
Patrick Steinhardt (19):
  global: improve const correctness when assigning string constants
  global: assign non-const strings as required
  global: convert intentionally-leaking config strings to consts
  compat/win32: fix const-correctness with string constants
  refspec: remove global tag refspec structure
  http: do not assign string constant to non-const field
  line-log: always allocate the output prefix
  object-file: make `buf` parameter of `index_mem()` a constant
  parse-options: cast long name for OPTION_ALIAS
  send-pack: always allocate receive status
  remote-curl: avoid assigning string constant to non-const variable
  revision: always store allocated strings in output encoding
  mailmap: always store allocated strings in mailmap blob
  imap-send: drop global `imap_server_conf` variable
  imap-send: fix leaking memory in `imap_server_conf`
  builtin/rebase: do not assign default backend to non-constant field
  builtin/rebase: always store allocated string in `options.strategy`
  builtin/merge: always store allocated strings in `pull_twohead`
  config.mak.dev: enable `-Wwrite-strings` warning
 builtin/bisect.c             |   3 +-
 builtin/blame.c              |   2 +-
 builtin/bugreport.c          |   2 +-
 builtin/check-ignore.c       |   4 +-
 builtin/clone.c              |  14 ++--
 builtin/commit.c             |   6 +-
 builtin/diagnose.c           |   2 +-
 builtin/fetch.c              |  11 ++-
 builtin/log.c                |   2 +-
 builtin/mailsplit.c          |   4 +-
 builtin/merge.c              |  18 +++--
 builtin/pull.c               |  52 +++++++-------
 builtin/rebase.c             |  81 ++++++++++++----------
 builtin/receive-pack.c       |   4 +-
 builtin/remote.c             |   3 +-
 builtin/revert.c             |   2 +-
 builtin/send-pack.c          |   2 +
 compat/basename.c            |  15 +++-
 compat/mingw.c               |  25 +++----
 compat/regex/regcomp.c       |   2 +-
 compat/winansi.c             |   2 +-
 config.mak.dev               |   1 +
 diff.c                       |   7 +-
 diffcore-rename.c            |   6 +-
 entry.c                      |   7 +-
 fmt-merge-msg.c              |   2 +-
 fsck.c                       |   2 +-
 fsck.h                       |   2 +-
 gpg-interface.c              |   6 +-
 http-backend.c               |   2 +-
 http.c                       |   5 +-
 ident.c                      |   9 ++-
 imap-send.c                  | 130 ++++++++++++++++++++---------------
 line-log.c                   |  21 +++---
 mailmap.c                    |   2 +-
 merge-ll.c                   |  11 ++-
 object-file.c                |  17 ++---
 parse-options.h              |   2 +-
 pretty.c                     |   7 +-
 refs.c                       |   2 +-
 refs.h                       |   2 +-
 refs/reftable-backend.c      |   5 +-
 refspec.c                    |  13 ----
 refspec.h                    |   1 -
 reftable/basics_test.c       |   5 +-
 reftable/block_test.c        |   4 +-
 reftable/merged_test.c       |  52 +++++++-------
 reftable/readwrite_test.c    |  60 +++++++++-------
 reftable/record.c            |   6 +-
 reftable/stack_test.c        |  87 ++++++++++++-----------
 remote-curl.c                |  53 +++++++-------
 revision.c                   |   3 +-
 run-command.c                |   2 +-
 send-pack.c                  |   2 +-
 t/helper/test-hashmap.c      |   3 +-
 t/helper/test-json-writer.c  |  10 +--
 t/helper/test-regex.c        |   4 +-
 t/helper/test-rot13-filter.c |   5 +-
 t/t3900-i18n-commit.sh       |   1 +
 t/t3901-i18n-patch.sh        |   1 +
 t/unit-tests/t-strbuf.c      |  10 +--
 trailer.c                    |   2 +-
 userdiff.c                   |  10 +--
 userdiff.h                   |  12 ++--
 wt-status.c                  |   2 +-
 65 files changed, 479 insertions(+), 373 deletions(-)
Range-diff against v1:
 1:  25c31e550f =  1:  25c31e550f global: improve const correctness when assigning string constants
 5:  dc5d85257e !  2:  3430bcc09b reftable: improve const correctness when assigning string constants
    @@ Metadata
     Author: Patrick Steinhardt <ps@pks.im>
     
      ## Commit message ##
    -    reftable: improve const correctness when assigning string constants
    +    global: assign non-const strings as required
     
    -    There are many cases in the reftable tests where we assign string
    -    constants to non-const fields. This is harmless because we know that
    -    those fields are only used for reading access, but will break once we
    -    enable `-Wwrite-strings`. Add explicit casts to prepare for this.
    +    There are several cases where we initialize non-const fields with string
    +    constants. This is invalid and will cause warnings once we enable the
    +    `-Wwrite-strings` warning. Adapt those cases to instead use string
    +    arrays.
     
         Signed-off-by: Patrick Steinhardt <ps@pks.im>
     
    + ## builtin/remote.c ##
    +@@ builtin/remote.c: static int get_head_names(const struct ref *remote_refs, struct ref_states *stat
    + 	struct ref *ref, *matches;
    + 	struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;
    + 	struct refspec_item refspec;
    ++	char refspec_str[] = "refs/heads/*";
    + 
    + 	memset(&refspec, 0, sizeof(refspec));
    + 	refspec.force = 0;
    + 	refspec.pattern = 1;
    +-	refspec.src = refspec.dst = "refs/heads/*";
    ++	refspec.src = refspec.dst = refspec_str;
    + 	get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
    + 	matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
    + 				    fetch_map, 1);
    +
    + ## diff.c ##
    +@@ diff.c: size_t fill_textconv(struct repository *r,
    + 		     struct diff_filespec *df,
    + 		     char **outbuf)
    + {
    ++	static char empty_str[] = "";
    + 	size_t size;
    + 
    + 	if (!driver) {
    + 		if (!DIFF_FILE_VALID(df)) {
    +-			*outbuf = "";
    ++			*outbuf = empty_str;
    + 			return 0;
    + 		}
    + 		if (diff_populate_filespec(r, df, NULL))
    +
    + ## entry.c ##
    +@@ entry.c: int finish_delayed_checkout(struct checkout *state, int show_progress)
    + 	struct string_list_item *filter, *path;
    + 	struct progress *progress = NULL;
    + 	struct delayed_checkout *dco = state->delayed_checkout;
    ++	char empty_str[] = "";
    + 
    + 	if (!state->delayed_checkout)
    + 		return errs;
    +@@ entry.c: int finish_delayed_checkout(struct checkout *state, int show_progress)
    + 			if (!async_query_available_blobs(filter->string, &available_paths)) {
    + 				/* Filter reported an error */
    + 				errs = 1;
    +-				filter->string = "";
    ++				filter->string = empty_str;
    + 				continue;
    + 			}
    + 			if (available_paths.nr <= 0) {
    +@@ entry.c: int finish_delayed_checkout(struct checkout *state, int show_progress)
    + 				 * filter from the list (see
    + 				 * "string_list_remove_empty_items" call below).
    + 				 */
    +-				filter->string = "";
    ++				filter->string = empty_str;
    + 				continue;
    + 			}
    + 
    +@@ entry.c: int finish_delayed_checkout(struct checkout *state, int show_progress)
    + 					 * Do not ask the filter for available blobs,
    + 					 * again, as the filter is likely buggy.
    + 					 */
    +-					filter->string = "";
    ++					filter->string = empty_str;
    + 					continue;
    + 				}
    + 				ce = index_file_exists(state->istate, path->string,
    +
    + ## ident.c ##
    +@@ ident.c: static struct passwd *xgetpwuid_self(int *is_bogus)
    + 	pw = getpwuid(getuid());
    + 	if (!pw) {
    + 		static struct passwd fallback;
    +-		fallback.pw_name = "unknown";
    ++		static char fallback_name[] = "unknown";
    + #ifndef NO_GECOS_IN_PWENT
    +-		fallback.pw_gecos = "Unknown";
    ++		static char fallback_gcos[] = "Unknown";
    ++#endif
    ++
    ++		fallback.pw_name = fallback_name;
    ++#ifndef NO_GECOS_IN_PWENT
    ++		fallback.pw_gecos = fallback_gcos;
    + #endif
    + 		pw = &fallback;
    + 		if (is_bogus)
    +
    + ## line-log.c ##
    +@@ line-log.c: static int process_diff_filepair(struct rev_info *rev,
    + 	struct range_set tmp;
    + 	struct diff_ranges diff;
    + 	mmfile_t file_parent, file_target;
    ++	char empty_str[] = "";
    + 
    + 	assert(pair->two->path);
    + 	while (rg) {
    +@@ line-log.c: static int process_diff_filepair(struct rev_info *rev,
    + 		file_parent.ptr = pair->one->data;
    + 		file_parent.size = pair->one->size;
    + 	} else {
    +-		file_parent.ptr = "";
    ++		file_parent.ptr = empty_str;
    + 		file_parent.size = 0;
    + 	}
    + 
    +
    + ## object-file.c ##
    +@@ object-file.c: static struct cached_object {
    + } *cached_objects;
    + static int cached_object_nr, cached_object_alloc;
    + 
    ++static char empty_tree_buf[] = "";
    + static struct cached_object empty_tree = {
    + 	.oid = {
    + 		.hash = EMPTY_TREE_SHA1_BIN_LITERAL,
    + 	},
    + 	.type = OBJ_TREE,
    +-	.buf = "",
    ++	.buf = empty_tree_buf,
    + };
    + 
    + static struct cached_object *find_cached_object(const struct object_id *oid)
    +
    + ## pretty.c ##
    +@@ pretty.c: static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
    + 		return 1;
    + 	case 'D':
    + 		{
    ++			char empty_str[] = "";
    + 			const struct decoration_options opts = {
    +-				.prefix = "",
    +-				.suffix = ""
    ++				.prefix = empty_str,
    ++				.suffix = empty_str,
    + 			};
    + 
    + 			format_decorations(sb, commit, c->auto_color, &opts);
    +
    + ## refs/reftable-backend.c ##
    +@@ refs/reftable-backend.c: static int write_copy_table(struct reftable_writer *writer, void *cb_data)
    + 	struct strbuf errbuf = STRBUF_INIT;
    + 	size_t logs_nr = 0, logs_alloc = 0, i;
    + 	const char *committer_info;
    ++	char head[] = "HEAD";
    + 	int ret;
    + 
    + 	committer_info = git_committer_info(0);
    +@@ refs/reftable-backend.c: static int write_copy_table(struct reftable_writer *writer, void *cb_data)
    + 		if (append_head_reflog) {
    + 			ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
    + 			logs[logs_nr] = logs[logs_nr - 1];
    +-			logs[logs_nr].refname = "HEAD";
    ++			logs[logs_nr].refname = head;
    + 			logs_nr++;
    + 		}
    + 	}
    +@@ refs/reftable-backend.c: static int write_copy_table(struct reftable_writer *writer, void *cb_data)
    + 	string_list_clear(&skip, 0);
    + 	strbuf_release(&errbuf);
    + 	for (i = 0; i < logs_nr; i++) {
    +-		if (!strcmp(logs[i].refname, "HEAD"))
    ++		if (logs[i].refname == head)
    + 			continue;
    + 		logs[i].refname = NULL;
    + 		reftable_log_record_release(&logs[i]);
    +
      ## reftable/basics_test.c ##
     @@ reftable/basics_test.c: static void test_binsearch(void)
      
    @@ reftable/basics_test.c: static void test_binsearch(void)
      {
     -	char *a[] = { "a", "b", NULL };
     -	EXPECT(names_length(a) == 2);
    -+	char *names[] = { (char *)"a", (char *)"b", NULL };
    ++	char a[] = "a", b[] = "b";
    ++	char *names[] = { a, b, NULL };
     +	EXPECT(names_length(names) == 2);
      }
      
      static void test_parse_names_normal(void)
     
      ## reftable/block_test.c ##
    +@@ reftable/block_test.c: license that can be found in the LICENSE file or at
    + static void test_block_read_write(void)
    + {
    + 	const int header_off = 21; /* random */
    +-	char *names[30];
    ++	char *names[30], empty_str[] = "";
    + 	const int N = ARRAY_SIZE(names);
    + 	const int block_size = 1024;
    + 	struct reftable_block block = { NULL };
     @@ reftable/block_test.c: static void test_block_read_write(void)
      	block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,
      			  header_off, hash_size(GIT_SHA1_FORMAT_ID));
      
     -	rec.u.ref.refname = "";
    -+	rec.u.ref.refname = (char *)"";
    ++	rec.u.ref.refname = empty_str;
      	rec.u.ref.value_type = REFTABLE_REF_DELETION;
      	n = block_writer_add(&bw, &rec);
      	EXPECT(n == REFTABLE_API_ERROR);
     
      ## reftable/merged_test.c ##
     @@ reftable/merged_test.c: static void readers_destroy(struct reftable_reader **readers, size_t n)
    + 
      static void test_merged_between(void)
      {
    ++	char a[] = "a", b[] = "b";
      	struct reftable_ref_record r1[] = { {
     -		.refname = "b",
    -+		.refname = (char *)"b",
    ++		.refname = b,
      		.update_index = 1,
      		.value_type = REFTABLE_REF_VAL1,
      		.value.val1 = { 1, 2, 3, 0 },
      	} };
      	struct reftable_ref_record r2[] = { {
     -		.refname = "a",
    -+		.refname = (char *)"a",
    ++		.refname = a,
      		.update_index = 2,
      		.value_type = REFTABLE_REF_DELETION,
      	} };
    -@@ reftable/merged_test.c: static void test_merged(void)
    +@@ reftable/merged_test.c: static void test_merged_between(void)
    + 
    + static void test_merged(void)
      {
    ++	char a[] = "a", b[] = "b", c[] = "c", d[] = "d";
      	struct reftable_ref_record r1[] = {
      		{
     -			.refname = "a",
    -+			.refname = (char *)"a",
    ++			.refname = a,
      			.update_index = 1,
      			.value_type = REFTABLE_REF_VAL1,
      			.value.val1 = { 1 },
      		},
      		{
     -			.refname = "b",
    -+			.refname = (char *)"b",
    ++			.refname = b,
      			.update_index = 1,
      			.value_type = REFTABLE_REF_VAL1,
      			.value.val1 = { 1 },
      		},
      		{
     -			.refname = "c",
    -+			.refname = (char *)"c",
    ++			.refname = c,
      			.update_index = 1,
      			.value_type = REFTABLE_REF_VAL1,
      			.value.val1 = { 1 },
    @@ reftable/merged_test.c: static void test_merged(void)
      	};
      	struct reftable_ref_record r2[] = { {
     -		.refname = "a",
    -+		.refname = (char *)"a",
    ++		.refname = a,
      		.update_index = 2,
      		.value_type = REFTABLE_REF_DELETION,
      	} };
      	struct reftable_ref_record r3[] = {
      		{
     -			.refname = "c",
    -+			.refname = (char *)"c",
    ++			.refname = c,
      			.update_index = 3,
      			.value_type = REFTABLE_REF_VAL1,
      			.value.val1 = { 2 },
      		},
      		{
     -			.refname = "d",
    -+			.refname = (char *)"d",
    ++			.refname = d,
      			.update_index = 3,
      			.value_type = REFTABLE_REF_VAL1,
      			.value.val1 = { 1 },
    -@@ reftable/merged_test.c: static void test_merged_logs(void)
    +@@ reftable/merged_test.c: merged_table_from_log_records(struct reftable_log_record **logs,
    + 
    + static void test_merged_logs(void)
      {
    ++	char a[] = "a";
    ++	char name[] = "jane doe", email[] = "jane@invalid";
    ++	char message1[] = "message1", message2[] = "message2";
    ++	char message3[] = "message3";
      	struct reftable_log_record r1[] = {
      		{
     -			.refname = "a",
    -+			.refname = (char *)"a",
    ++			.refname = a,
      			.update_index = 2,
      			.value_type = REFTABLE_LOG_UPDATE,
      			.value.update = {
    @@ reftable/merged_test.c: static void test_merged_logs(void)
     -				.name = "jane doe",
     -				.email = "jane@invalid",
     -				.message = "message2",
    -+				.name = (char *)"jane doe",
    -+				.email = (char *)"jane@invalid",
    -+				.message = (char *)"message2",
    ++				.name = name,
    ++				.email = email,
    ++				.message = message2,
      			}
      		},
      		{
     -			.refname = "a",
    -+			.refname = (char *)"a",
    ++			.refname = a,
      			.update_index = 1,
      			.value_type = REFTABLE_LOG_UPDATE,
      			.value.update = {
    @@ reftable/merged_test.c: static void test_merged_logs(void)
     -				.name = "jane doe",
     -				.email = "jane@invalid",
     -				.message = "message1",
    -+				.name = (char *)"jane doe",
    -+				.email = (char *)"jane@invalid",
    -+				.message = (char *)"message1",
    ++				.name = name,
    ++				.email = email,
    ++				.message = message1,
      			}
      		},
      	};
      	struct reftable_log_record r2[] = {
      		{
     -			.refname = "a",
    -+			.refname = (char *)"a",
    ++			.refname = a,
      			.update_index = 3,
      			.value_type = REFTABLE_LOG_UPDATE,
      			.value.update = {
    @@ reftable/merged_test.c: static void test_merged_logs(void)
     -				.name = "jane doe",
     -				.email = "jane@invalid",
     -				.message = "message3",
    -+				.name = (char *)"jane doe",
    -+				.email = (char *)"jane@invalid",
    -+				.message = (char *)"message3",
    ++				.name = name,
    ++				.email = email,
    ++				.message = message3,
      			}
      		},
      	};
      	struct reftable_log_record r3[] = {
      		{
     -			.refname = "a",
    -+			.refname = (char *)"a",
    ++			.refname = a,
      			.update_index = 2,
      			.value_type = REFTABLE_LOG_DELETION,
      		},
    -@@ reftable/merged_test.c: static void test_default_write_opts(void)
    +@@ reftable/merged_test.c: static void test_merged_logs(void)
    + 
    + static void test_default_write_opts(void)
    + {
    ++	char master[] = "master";
    + 	struct reftable_write_options opts = { 0 };
      	struct strbuf buf = STRBUF_INIT;
      	struct reftable_writer *w =
      		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
     -
      	struct reftable_ref_record rec = {
     -		.refname = "master",
    -+		.refname = (char *)"master",
    ++		.refname = master,
      		.update_index = 1,
      	};
      	int err;
     
      ## reftable/readwrite_test.c ##
    +@@ reftable/readwrite_test.c: static void write_table(char ***names, struct strbuf *buf, int N,
    + 	int i = 0, n;
    + 	struct reftable_log_record log = { NULL };
    + 	const struct reftable_stats *stats = NULL;
    ++	char message[] = "message";
    + 
    + 	REFTABLE_CALLOC_ARRAY(*names, N + 1);
    + 
     @@ reftable/readwrite_test.c: static void write_table(char ***names, struct strbuf *buf, int N,
      		log.update_index = update_index;
      		log.value_type = REFTABLE_LOG_UPDATE;
      		set_test_hash(log.value.update.new_hash, i);
     -		log.value.update.message = "message";
    -+		log.value.update.message = (char *)"message";
    ++		log.value.update.message = message;
      
      		n = reftable_writer_add_log(w, &log);
      		EXPECT(n == 0);
    -@@ reftable/readwrite_test.c: static void test_log_buffer_size(void)
    +@@ reftable/readwrite_test.c: static void write_table(char ***names, struct strbuf *buf, int N,
    + 
    + static void test_log_buffer_size(void)
    + {
    ++	char refname[] = "refs/heads/master";
    ++	char name[] = "Han-Wen Hienhuys";
    ++	char email[] = "hanwen@google.com";
    ++	char message[] = "commit: 9\n";
    + 	struct strbuf buf = STRBUF_INIT;
    + 	struct reftable_write_options opts = {
    + 		.block_size = 4096,
      	};
      	int err;
      	int i;
    @@ reftable/readwrite_test.c: static void test_log_buffer_size(void)
     -					   .message = "commit: 9\n",
     -				   } } };
     +	struct reftable_log_record log = {
    -+		.refname = (char *)"refs/heads/master",
    ++		.refname = refname,
     +		.update_index = 0xa,
     +		.value_type = REFTABLE_LOG_UPDATE,
     +		.value.update = {
    -+			.name = (char *)"Han-Wen Nienhuys",
    -+			.email = (char *)"hanwen@google.com",
    ++			.name = name,
    ++			.email = email,
     +			.tz_offset = 100,
     +			.time = 0x5e430672,
    -+			.message = (char *)"commit: 9\n",
    ++			.message = message,
     +		},
     +	};
      	struct reftable_writer *w =
      		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
      
    +@@ reftable/readwrite_test.c: static void test_log_buffer_size(void)
    + 
    + static void test_log_overflow(void)
    + {
    ++	char refname[] = "refs/heads/master";
    ++	char name[] = "Han-Wen Hienhuys";
    ++	char email[] = "hanwen@google.com";
    + 	struct strbuf buf = STRBUF_INIT;
    + 	char msg[256] = { 0 };
    + 	struct reftable_write_options opts = {
     @@ reftable/readwrite_test.c: static void test_log_overflow(void)
      	};
      	int err;
      	struct reftable_log_record log = {
     -		.refname = "refs/heads/master",
    -+		.refname = (char *)"refs/heads/master",
    ++		.refname = refname,
      		.update_index = 0xa,
      		.value_type = REFTABLE_LOG_UPDATE,
      		.value = {
    @@ reftable/readwrite_test.c: static void test_log_overflow(void)
      				.new_hash = { 2 },
     -				.name = "Han-Wen Nienhuys",
     -				.email = "hanwen@google.com",
    -+				.name = (char *)"Han-Wen Nienhuys",
    -+				.email = (char *)"hanwen@google.com",
    ++				.name = name,
    ++				.email = email,
      				.tz_offset = 100,
      				.time = 0x5e430672,
      				.message = msg,
     @@ reftable/readwrite_test.c: static void test_log_zlib_corruption(void)
    + 	struct reftable_writer *w =
    + 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
    + 	const struct reftable_stats *stats = NULL;
    ++	char refname[] = "refname";
    ++	char name[] = "My Name";
    ++	char email[] = "myname@invalid";
      	char message[100] = { 0 };
      	int err, i, n;
      	struct reftable_log_record log = {
     -		.refname = "refname",
    -+		.refname = (char *)"refname",
    ++		.refname = refname,
      		.value_type = REFTABLE_LOG_UPDATE,
      		.value = {
      			.update = {
    @@ reftable/readwrite_test.c: static void test_log_zlib_corruption(void)
      				.old_hash = { 2 },
     -				.name = "My Name",
     -				.email = "myname@invalid",
    -+				.name = (char *)"My Name",
    -+				.email = (char *)"myname@invalid",
    ++				.name = name,
    ++				.email = email,
      				.message = message,
      			},
      		},
     @@ reftable/readwrite_test.c: static void test_write_empty_key(void)
    + 	struct strbuf buf = STRBUF_INIT;
      	struct reftable_writer *w =
      		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
    ++	char refname[] = "";
      	struct reftable_ref_record ref = {
     -		.refname = "",
    -+		.refname = (char *)"",
    ++		.refname = refname,
      		.update_index = 1,
      		.value_type = REFTABLE_REF_DELETION,
      	};
     @@ reftable/readwrite_test.c: static void test_write_key_order(void)
    + 	struct strbuf buf = STRBUF_INIT;
    + 	struct reftable_writer *w =
      		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
    ++	char a[] = "a", b[] = "b", target[] = "target";
      	struct reftable_ref_record refs[2] = {
      		{
     -			.refname = "b",
    -+			.refname = (char *)"b",
    ++			.refname = b,
      			.update_index = 1,
      			.value_type = REFTABLE_REF_SYMREF,
      			.value = {
     -				.symref = "target",
    -+				.symref = (char *)"target",
    ++				.symref = target,
      			},
      		}, {
     -			.refname = "a",
    -+			.refname = (char *)"a",
    ++			.refname = a,
      			.update_index = 1,
      			.value_type = REFTABLE_REF_SYMREF,
      			.value = {
     -				.symref = "target",
    -+				.symref = (char *)"target",
    ++				.symref = target,
      			},
      		}
      	};
    @@ reftable/stack_test.c: static void test_parse_names(void)
      
      static int write_test_ref(struct reftable_writer *wr, void *arg)
     @@ reftable/stack_test.c: static void test_reftable_stack_add_one(void)
    + 	};
      	struct reftable_stack *st = NULL;
      	int err;
    ++	char head[] = "HEAD", master[] = "master";
      	struct reftable_ref_record ref = {
     -		.refname = "HEAD",
    -+		.refname = (char *)"HEAD",
    ++		.refname = head,
      		.update_index = 1,
      		.value_type = REFTABLE_REF_SYMREF,
     -		.value.symref = "master",
    -+		.value.symref = (char *)"master",
    ++		.value.symref = master,
      	};
      	struct reftable_ref_record dest = { NULL };
      	struct stat stat_result = { 0 };
     @@ reftable/stack_test.c: static void test_reftable_stack_uptodate(void)
    + 	char *dir = get_tmp_dir(__LINE__);
      
      	int err;
    ++	char head[] = "HEAD", branch2[] = "branch2", master[] = "master";
      	struct reftable_ref_record ref1 = {
     -		.refname = "HEAD",
    -+		.refname = (char *)"HEAD",
    ++		.refname = head,
      		.update_index = 1,
      		.value_type = REFTABLE_REF_SYMREF,
     -		.value.symref = "master",
    -+		.value.symref = (char *)"master",
    ++		.value.symref = master,
      	};
      	struct reftable_ref_record ref2 = {
     -		.refname = "branch2",
    -+		.refname = (char *)"branch2",
    ++		.refname = branch2,
      		.update_index = 2,
      		.value_type = REFTABLE_REF_SYMREF,
     -		.value.symref = "master",
    -+		.value.symref = (char *)"master",
    ++		.value.symref = master,
      	};
      
      
     @@ reftable/stack_test.c: static void test_reftable_stack_transaction_api(void)
    + 	struct reftable_stack *st = NULL;
    + 	int err;
      	struct reftable_addition *add = NULL;
    - 
    +-
    ++	char head[] = "HEAD", master[] = "master";
      	struct reftable_ref_record ref = {
     -		.refname = "HEAD",
    -+		.refname = (char *)"HEAD",
    ++		.refname = head,
      		.update_index = 1,
      		.value_type = REFTABLE_REF_SYMREF,
     -		.value.symref = "master",
    -+		.value.symref = (char *)"master",
    ++		.value.symref = master,
      	};
      	struct reftable_ref_record dest = { NULL };
      
     @@ reftable/stack_test.c: static void test_reftable_stack_transaction_api_performs_auto_compaction(void)
    + 	EXPECT_ERR(err);
    + 
    + 	for (i = 0; i <= n; i++) {
    ++		char master[] = "master";
      		struct reftable_ref_record ref = {
      			.update_index = reftable_stack_next_update_index(st),
      			.value_type = REFTABLE_REF_SYMREF,
     -			.value.symref = "master",
    -+			.value.symref = (char *)"master",
    ++			.value.symref = master,
      		};
      		char name[100];
      
     @@ reftable/stack_test.c: static void test_reftable_stack_transaction_api_performs_auto_compaction(void)
    + 
      static void test_reftable_stack_auto_compaction_fails_gracefully(void)
      {
    ++	char master[] = "refs/meads/master";
      	struct reftable_ref_record ref = {
     -		.refname = "refs/heads/master",
    -+		.refname = (char *)"refs/heads/master",
    ++		.refname = master,
      		.update_index = 1,
      		.value_type = REFTABLE_REF_VAL1,
      		.value.val1 = {0x01},
    -@@ reftable/stack_test.c: static void test_reftable_stack_update_index_check(void)
    +@@ reftable/stack_test.c: static int write_error(struct reftable_writer *wr, void *arg)
    + static void test_reftable_stack_update_index_check(void)
    + {
    + 	char *dir = get_tmp_dir(__LINE__);
    +-
    + 	struct reftable_write_options cfg = { 0 };
      	struct reftable_stack *st = NULL;
      	int err;
    ++	char name1[] = "name1", name2[] = "name2", master[] = "master";
      	struct reftable_ref_record ref1 = {
     -		.refname = "name1",
    -+		.refname = (char *)"name1",
    ++		.refname = name1,
      		.update_index = 1,
      		.value_type = REFTABLE_REF_SYMREF,
     -		.value.symref = "master",
    -+		.value.symref = (char *)"master",
    ++		.value.symref = master,
      	};
      	struct reftable_ref_record ref2 = {
     -		.refname = "name2",
    -+		.refname = (char *)"name2",
    ++		.refname = name2,
      		.update_index = 1,
      		.value_type = REFTABLE_REF_SYMREF,
     -		.value.symref = "master",
    -+		.value.symref = (char *)"master",
    ++		.value.symref = master,
      	};
      
      	err = reftable_new_stack(&st, dir, cfg);
     @@ reftable/stack_test.c: static void test_reftable_stack_log_normalize(void)
    + 	};
      	struct reftable_stack *st = NULL;
      	char *dir = get_tmp_dir(__LINE__);
    ++	char branch[] = "branch";
    ++	char onetwomessage[] = "one\ntwo";
    ++	char onemessage[] = "one";
    ++	char twomessage[] = "two\n";
      	struct reftable_log_record input = {
     -		.refname = "branch",
    -+		.refname = (char *)"branch",
    ++		.refname = branch,
      		.update_index = 1,
      		.value_type = REFTABLE_LOG_UPDATE,
      		.value = {
    @@ reftable/stack_test.c: static void test_reftable_stack_log_normalize(void)
      	EXPECT_ERR(err);
      
     -	input.value.update.message = "one\ntwo";
    -+	input.value.update.message = (char *)"one\ntwo";
    ++	input.value.update.message = onetwomessage;
      	err = reftable_stack_add(st, &write_test_log, &arg);
      	EXPECT(err == REFTABLE_API_ERROR);
      
     -	input.value.update.message = "one";
    -+	input.value.update.message = (char *)"one";
    ++	input.value.update.message = onemessage;
      	err = reftable_stack_add(st, &write_test_log, &arg);
      	EXPECT_ERR(err);
      
    @@ reftable/stack_test.c: static void test_reftable_stack_log_normalize(void)
      	EXPECT(0 == strcmp(dest.value.update.message, "one\n"));
      
     -	input.value.update.message = "two\n";
    -+	input.value.update.message = (char *)"two\n";
    ++	input.value.update.message = twomessage;
      	arg.update_index = 2;
      	err = reftable_stack_add(st, &write_test_log, &arg);
      	EXPECT_ERR(err);
    -@@ reftable/stack_test.c: static void test_reftable_stack_hash_id(void)
    +@@ reftable/stack_test.c: static void test_reftable_stack_tombstone(void)
    + static void test_reftable_stack_hash_id(void)
    + {
    + 	char *dir = get_tmp_dir(__LINE__);
    +-
    + 	struct reftable_write_options cfg = { 0 };
    + 	struct reftable_stack *st = NULL;
      	int err;
    - 
    +-
    ++	char master[] = "master", target[] = "target";
      	struct reftable_ref_record ref = {
     -		.refname = "master",
    -+		.refname = (char *)"master",
    ++		.refname = master,
      		.value_type = REFTABLE_REF_SYMREF,
     -		.value.symref = "target",
    -+		.value.symref = (char *)"target",
    ++		.value.symref = target,
      		.update_index = 1,
      	};
      	struct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID };
     @@ reftable/stack_test.c: static void test_reftable_stack_auto_compaction(void)
    + 	EXPECT_ERR(err);
    + 
    + 	for (i = 0; i < N; i++) {
    +-		char name[100];
    ++		char name[100], master[] = "master";
    + 		struct reftable_ref_record ref = {
      			.refname = name,
      			.update_index = reftable_stack_next_update_index(st),
      			.value_type = REFTABLE_REF_SYMREF,
     -			.value.symref = "master",
    -+			.value.symref = (char *)"master",
    ++			.value.symref = master,
      		};
      		snprintf(name, sizeof(name), "branch%04d", i);
      
     @@ reftable/stack_test.c: static void test_reftable_stack_add_performs_auto_compaction(void)
    + 	EXPECT_ERR(err);
    + 
    + 	for (i = 0; i <= n; i++) {
    ++		char master[] = "master";
      		struct reftable_ref_record ref = {
      			.update_index = reftable_stack_next_update_index(st),
      			.value_type = REFTABLE_REF_SYMREF,
     -			.value.symref = "master",
    -+			.value.symref = (char *)"master",
    ++			.value.symref = master,
      		};
      
      		/*
     @@ reftable/stack_test.c: static void test_reftable_stack_compaction_concurrent(void)
    + 	EXPECT_ERR(err);
    + 
    + 	for (i = 0; i < N; i++) {
    +-		char name[100];
    ++		char name[100], master[] = "master";
    + 		struct reftable_ref_record ref = {
      			.refname = name,
      			.update_index = reftable_stack_next_update_index(st1),
      			.value_type = REFTABLE_REF_SYMREF,
     -			.value.symref = "master",
    -+			.value.symref = (char *)"master",
    ++			.value.symref = master,
      		};
      		snprintf(name, sizeof(name), "branch%04d", i);
      
     @@ reftable/stack_test.c: static void test_reftable_stack_compaction_concurrent_clean(void)
    + 	EXPECT_ERR(err);
    + 
    + 	for (i = 0; i < N; i++) {
    +-		char name[100];
    ++		char name[100], master[] = "master";
    + 		struct reftable_ref_record ref = {
      			.refname = name,
      			.update_index = reftable_stack_next_update_index(st1),
      			.value_type = REFTABLE_REF_SYMREF,
     -			.value.symref = "master",
    -+			.value.symref = (char *)"master",
    ++			.value.symref = master,
      		};
      		snprintf(name, sizeof(name), "branch%04d", i);
      
 3:  8f3decbb76 !  3:  8b71dfa208 global: convert intentionally-leaking config strings to consts
    @@ Commit message
             configured via `diff.<driver>.*` to add additional drivers. Again,
             these have a global lifetime and are never free'd.
     
    -    All of these are intentionally kept alive and never free'd. Let's mark
    -    the respective fields as `const char *` and cast away the constness when
    -    assigning those values.
    +    All of these are intentionally kept alive and never free'd. Furthermore,
    +    all of these are being assigned both string constants in some places,
    +    and allocated strings in other places. This will cause warnings once we
    +    enable `-Wwrite-strings`, so let's mark the respective fields as `const
    +    char *` and cast away the constness when assigning those values.
     
         Signed-off-by: Patrick Steinhardt <ps@pks.im>
     
 4:  ba50e49f86 =  4:  961b3357d5 compat/win32: fix const-correctness with string constants
 6:  0eaa73c109 =  5:  b73a45133b refspec: remove global tag refspec structure
 7:  03b13c449b =  6:  6da87a0905 http: do not assign string constant to non-const field
 8:  699eeae92c =  7:  3da7df97a5 line-log: always allocate the output prefix
 9:  6cbb8444a6 =  8:  e5d14a5173 object-file: make `buf` parameter of `index_mem()` a constant
10:  c07b27bbb4 =  9:  dd40c7464d parse-options: cast long name for OPTION_ALIAS
11:  3cd28ae38c = 10:  462502127d send-pack: always allocate receive status
12:  00b4a7dbbc ! 11:  884fbe1da5 remote-curl: avoid assigning string constant to non-const variable
    @@ remote-curl.c: int cmd_main(int argc, const char **argv)
      
      		} else if (skip_prefix(buf.buf, "option ", &arg)) {
     -			char *value = strchr(arg, ' ');
    -+			const char *value = strchr(arg, ' ');
    -+			size_t arglen;
    ++			const char *value = strchrnul(arg, ' ');
    ++			size_t arglen = value - arg;
      			int result;
      
     -			if (value)
     -				*value++ = '\0';
    --			else
    -+			if (value) {
    -+				arglen = value - arg;
    -+				value++;
    -+			} else {
    -+				arglen = strlen(arg);
    ++			if (*value)
    ++				value++; /* skip over SP */
    + 			else
      				value = "true";
    -+			}
      
     -			result = set_option(arg, value);
     +			result = set_option(arg, arglen, value);
13:  68a7d24e4a = 12:  502380c2ca revision: always store allocated strings in output encoding
14:  0e393fa6a7 = 13:  ffacdc3779 mailmap: always store allocated strings in mailmap blob
15:  18ba9f7b3b = 14:  c0fce9b87e imap-send: drop global `imap_server_conf` variable
16:  357d69fa8b ! 15:  e0a5b83f0e imap-send: fix leaking memory in `imap_server_conf`
    @@ Commit message
         `struct imap_server_conf`. Fix this by creating a common exit path where
         we can free resources.
     
    -    While at it, drop the unused variables `imap_server_conf::name` and
    -    `nongit_ok`.
    +    While at it, drop the unused member `imap_server_conf::name`.
     
         Signed-off-by: Patrick Steinhardt <ps@pks.im>
     
    @@ imap-send.c: static int git_imap_config(const char *var, const char *val,
      	return 0;
      }
     @@ imap-send.c: int cmd_main(int argc, const char **argv)
    - 	};
      	struct strbuf all_msgs = STRBUF_INIT;
      	int total;
    --	int nongit_ok;
    + 	int nongit_ok;
     +	int ret;
      
    --	setup_git_directory_gently(&nongit_ok);
    -+	setup_git_directory_gently(NULL);
    + 	setup_git_directory_gently(&nongit_ok);
      	git_config(git_imap_config, &server);
    - 
    - 	argc = parse_options(argc, (const char **)argv, "", imap_send_options, imap_send_usage, 0);
     @@ imap-send.c: int cmd_main(int argc, const char **argv)
      
      	if (!server.folder) {
 2:  51ee5660a1 ! 16:  36a7b0a4b0 global: assign non-const strings as required
    @@ Metadata
     Author: Patrick Steinhardt <ps@pks.im>
     
      ## Commit message ##
    -    global: assign non-const strings as required
    +    builtin/rebase: do not assign default backend to non-constant field
     
    -    There are several cases where we initialize non-const fields with string
    -    constants. This is invalid and will cause warnings once we enable the
    -    `-Wwrite-strings` warning. Adapt those cases to instead use string
    -    arrays.
    +    The `struct rebase_options::default_backend` field is a non-constant
    +    string, but is being assigned a constant via `REBASE_OPTIONS_INIT`.
    +    Refactor the code to initialize and release options via two functions
    +    `rebase_options_init()` and `rebase_options_release()`. Like this, we
    +    can easily adapt the former funnction to use `xstrdup()` on the default
    +    value without hiding it away in a macro.
     
         Signed-off-by: Patrick Steinhardt <ps@pks.im>
     
    - ## builtin/remote.c ##
    -@@ builtin/remote.c: static int get_head_names(const struct ref *remote_refs, struct ref_states *stat
    - 	struct ref *ref, *matches;
    - 	struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;
    - 	struct refspec_item refspec;
    -+	char refspec_str[] = "refs/heads/*";
    - 
    - 	memset(&refspec, 0, sizeof(refspec));
    - 	refspec.force = 0;
    - 	refspec.pattern = 1;
    --	refspec.src = refspec.dst = "refs/heads/*";
    -+	refspec.src = refspec.dst = refspec_str;
    - 	get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
    - 	matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
    - 				    fetch_map, 1);
    -
    - ## diff.c ##
    -@@ diff.c: size_t fill_textconv(struct repository *r,
    - 		     struct diff_filespec *df,
    - 		     char **outbuf)
    - {
    -+	static char empty_str[] = "";
    - 	size_t size;
    - 
    - 	if (!driver) {
    - 		if (!DIFF_FILE_VALID(df)) {
    --			*outbuf = "";
    -+			*outbuf = empty_str;
    - 			return 0;
    - 		}
    - 		if (diff_populate_filespec(r, df, NULL))
    -
    - ## entry.c ##
    -@@ entry.c: int finish_delayed_checkout(struct checkout *state, int show_progress)
    - 	struct string_list_item *filter, *path;
    - 	struct progress *progress = NULL;
    - 	struct delayed_checkout *dco = state->delayed_checkout;
    -+	char empty_str[] = "";
    - 
    - 	if (!state->delayed_checkout)
    - 		return errs;
    -@@ entry.c: int finish_delayed_checkout(struct checkout *state, int show_progress)
    - 			if (!async_query_available_blobs(filter->string, &available_paths)) {
    - 				/* Filter reported an error */
    - 				errs = 1;
    --				filter->string = "";
    -+				filter->string = empty_str;
    - 				continue;
    - 			}
    - 			if (available_paths.nr <= 0) {
    -@@ entry.c: int finish_delayed_checkout(struct checkout *state, int show_progress)
    - 				 * filter from the list (see
    - 				 * "string_list_remove_empty_items" call below).
    - 				 */
    --				filter->string = "";
    -+				filter->string = empty_str;
    - 				continue;
    - 			}
    + ## builtin/rebase.c ##
    +@@ builtin/rebase.c: struct rebase_options {
    + 	int config_update_refs;
    + };
      
    -@@ entry.c: int finish_delayed_checkout(struct checkout *state, int show_progress)
    - 					 * Do not ask the filter for available blobs,
    - 					 * again, as the filter is likely buggy.
    - 					 */
    --					filter->string = "";
    -+					filter->string = empty_str;
    - 					continue;
    - 				}
    - 				ce = index_file_exists(state->istate, path->string,
    -
    - ## ident.c ##
    -@@ ident.c: static struct passwd *xgetpwuid_self(int *is_bogus)
    - 	pw = getpwuid(getuid());
    - 	if (!pw) {
    - 		static struct passwd fallback;
    --		fallback.pw_name = "unknown";
    -+		static char fallback_name[] = "unknown";
    - #ifndef NO_GECOS_IN_PWENT
    --		fallback.pw_gecos = "Unknown";
    -+		static char fallback_gcos[] = "Unknown";
    -+#endif
    +-#define REBASE_OPTIONS_INIT {			  	\
    +-		.type = REBASE_UNSPECIFIED,	  	\
    +-		.empty = EMPTY_UNSPECIFIED,	  	\
    +-		.keep_empty = 1,			\
    +-		.default_backend = "merge",	  	\
    +-		.flags = REBASE_NO_QUIET, 		\
    +-		.git_am_opts = STRVEC_INIT,		\
    +-		.exec = STRING_LIST_INIT_NODUP,		\
    +-		.git_format_patch_opt = STRBUF_INIT,	\
    +-		.fork_point = -1,			\
    +-		.reapply_cherry_picks = -1,             \
    +-		.allow_empty_message = 1,               \
    +-		.autosquash = -1,                       \
    +-		.rebase_merges = -1,                    \
    +-		.config_rebase_merges = -1,             \
    +-		.update_refs = -1,                      \
    +-		.config_update_refs = -1,               \
    +-		.strategy_opts = STRING_LIST_INIT_NODUP,\
    +-	}
    ++static void rebase_options_init(struct rebase_options *opts)
    ++{
    ++	memset(opts, 0, sizeof(*opts));
    ++	opts->type = REBASE_UNSPECIFIED;
    ++	opts->empty = EMPTY_UNSPECIFIED;
    ++	opts->default_backend = xstrdup("merge");
    ++	opts->keep_empty = 1;
    ++	opts->flags = REBASE_NO_QUIET;
    ++	strvec_init(&opts->git_am_opts);
    ++	string_list_init_nodup(&opts->exec);
    ++	strbuf_init(&opts->git_format_patch_opt, 0);
    ++	opts->fork_point = -1;
    ++	opts->reapply_cherry_picks = -1;
    ++	opts->allow_empty_message = 1;
    ++	opts->autosquash = -1;
    ++	opts->rebase_merges = -1;
    ++	opts->config_rebase_merges = -1;
    ++	opts->update_refs = -1;
    ++	opts->config_update_refs = -1;
    ++	string_list_init_nodup(&opts->strategy_opts);
    ++}
     +
    -+		fallback.pw_name = fallback_name;
    -+#ifndef NO_GECOS_IN_PWENT
    -+		fallback.pw_gecos = fallback_gcos;
    - #endif
    - 		pw = &fallback;
    - 		if (is_bogus)
    -
    - ## line-log.c ##
    -@@ line-log.c: static int process_diff_filepair(struct rev_info *rev,
    - 	struct range_set tmp;
    - 	struct diff_ranges diff;
    - 	mmfile_t file_parent, file_target;
    -+	char empty_str[] = "";
    ++static void rebase_options_release(struct rebase_options *opts)
    ++{
    ++	free(opts->default_backend);
    ++	free(opts->reflog_action);
    ++	free(opts->head_name);
    ++	strvec_clear(&opts->git_am_opts);
    ++	free(opts->gpg_sign_opt);
    ++	string_list_clear(&opts->exec, 0);
    ++	free(opts->strategy);
    ++	string_list_clear(&opts->strategy_opts, 0);
    ++	strbuf_release(&opts->git_format_patch_opt);
    ++}
      
    - 	assert(pair->two->path);
    - 	while (rg) {
    -@@ line-log.c: static int process_diff_filepair(struct rev_info *rev,
    - 		file_parent.ptr = pair->one->data;
    - 		file_parent.size = pair->one->size;
    - 	} else {
    --		file_parent.ptr = "";
    -+		file_parent.ptr = empty_str;
    - 		file_parent.size = 0;
    + static struct replay_opts get_replay_opts(const struct rebase_options *opts)
    + {
    +@@ builtin/rebase.c: static int rebase_config(const char *var, const char *value,
      	}
      
    -
    - ## object-file.c ##
    -@@ object-file.c: static struct cached_object {
    - } *cached_objects;
    - static int cached_object_nr, cached_object_alloc;
    - 
    -+static char empty_tree_buf[] = "";
    - static struct cached_object empty_tree = {
    - 	.oid = {
    - 		.hash = EMPTY_TREE_SHA1_BIN_LITERAL,
    - 	},
    - 	.type = OBJ_TREE,
    --	.buf = "",
    -+	.buf = empty_tree_buf,
    - };
    + 	if (!strcmp(var, "rebase.backend")) {
    ++		FREE_AND_NULL(opts->default_backend);
    + 		return git_config_string(&opts->default_backend, var, value);
    + 	}
      
    - static struct cached_object *find_cached_object(const struct object_id *oid)
    -
    - ## pretty.c ##
    -@@ pretty.c: static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
    - 		return 1;
    - 	case 'D':
    - 		{
    -+			char empty_str[] = "";
    - 			const struct decoration_options opts = {
    --				.prefix = "",
    --				.suffix = ""
    -+				.prefix = empty_str,
    -+				.suffix = empty_str,
    - 			};
    +@@ builtin/rebase.c: static int check_exec_cmd(const char *cmd)
      
    - 			format_decorations(sb, commit, c->auto_color, &opts);
    -
    - ## refs/reftable-backend.c ##
    -@@ refs/reftable-backend.c: static int write_copy_table(struct reftable_writer *writer, void *cb_data)
    - 	struct strbuf errbuf = STRBUF_INIT;
    - 	size_t logs_nr = 0, logs_alloc = 0, i;
    - 	const char *committer_info;
    -+	char head[] = "HEAD";
    - 	int ret;
    + int cmd_rebase(int argc, const char **argv, const char *prefix)
    + {
    +-	struct rebase_options options = REBASE_OPTIONS_INIT;
    ++	struct rebase_options options;
    + 	const char *branch_name;
    + 	int ret, flags, total_argc, in_progress = 0;
    + 	int keep_base = 0;
    +@@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)
    + 	};
    + 	int i;
      
    - 	committer_info = git_committer_info(0);
    -@@ refs/reftable-backend.c: static int write_copy_table(struct reftable_writer *writer, void *cb_data)
    - 		if (append_head_reflog) {
    - 			ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
    - 			logs[logs_nr] = logs[logs_nr - 1];
    --			logs[logs_nr].refname = "HEAD";
    -+			logs[logs_nr].refname = head;
    - 			logs_nr++;
    - 		}
    - 	}
    -@@ refs/reftable-backend.c: static int write_copy_table(struct reftable_writer *writer, void *cb_data)
    - 	string_list_clear(&skip, 0);
    - 	strbuf_release(&errbuf);
    - 	for (i = 0; i < logs_nr; i++) {
    --		if (!strcmp(logs[i].refname, "HEAD"))
    -+		if (logs[i].refname == head)
    - 			continue;
    - 		logs[i].refname = NULL;
    - 		reftable_log_record_release(&logs[i]);
    ++	rebase_options_init(&options);
    ++
    + 	if (argc == 2 && !strcmp(argv[1], "-h"))
    + 		usage_with_options(builtin_rebase_usage,
    + 				   builtin_rebase_options);
    +@@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)
    + cleanup:
    + 	strbuf_release(&buf);
    + 	strbuf_release(&revisions);
    +-	free(options.reflog_action);
    +-	free(options.head_name);
    +-	strvec_clear(&options.git_am_opts);
    +-	free(options.gpg_sign_opt);
    +-	string_list_clear(&options.exec, 0);
    +-	free(options.strategy);
    +-	string_list_clear(&options.strategy_opts, 0);
    +-	strbuf_release(&options.git_format_patch_opt);
    ++	rebase_options_release(&options);
    + 	free(squash_onto_name);
    + 	free(keep_base_onto_name);
    + 	return !!ret;
17:  16d3d28243 ! 17:  3552ab9748 builtin/rebase: adapt code to not assign string constants to non-const
    @@ Metadata
     Author: Patrick Steinhardt <ps@pks.im>
     
      ## Commit message ##
    -    builtin/rebase: adapt code to not assign string constants to non-const
    +    builtin/rebase: always store allocated string in `options.strategy`
     
    -    When computing the rebase strategy we temporarily assign a string
    -    constant to `options.strategy` before we call `xstrdup()` on it.
    -    Furthermore, the default backend is being assigned a string constant via
    -    `REBASE_OPTIONS_INIT`. Both of these will cause warnings once we enable
    -    `-Wwrite-strings`.
    +    The `struct rebase_options::strategy` field is a `char *`, but we do end
    +    up assigning string constants to it in two cases:
     
    -    Adapt the code such that we only store allocated strings in those
    -    variables.
    +      - When being passed a `--strategy=` option via the command line.
    +
    +      - When being passed a strategy option via `--strategy-option=`, but
    +        not a strategy.
    +
    +    This will cause warnings once we enable `-Wwrite-strings`.
    +
    +    Ideally, we'd just convert the field to be a `const char *`. But we also
    +    assign to this field via the GIT_TEST_MERGE_ALGORITHM envvar, which we
    +    have to strdup(3P) into it.
    +
    +    Instead, refactor the code to make sure that we only ever assign
    +    allocated strings to this field.
     
         Signed-off-by: Patrick Steinhardt <ps@pks.im>
     
      ## builtin/rebase.c ##
    -@@ builtin/rebase.c: struct rebase_options {
    - 		.type = REBASE_UNSPECIFIED,	  	\
    - 		.empty = EMPTY_UNSPECIFIED,	  	\
    - 		.keep_empty = 1,			\
    --		.default_backend = "merge",	  	\
    - 		.flags = REBASE_NO_QUIET, 		\
    - 		.git_am_opts = STRVEC_INIT,		\
    - 		.exec = STRING_LIST_INIT_NODUP,		\
    -@@ builtin/rebase.c: static int rebase_config(const char *var, const char *value,
    - 	}
    - 
    - 	if (!strcmp(var, "rebase.backend")) {
    -+		FREE_AND_NULL(opts->default_backend);
    - 		return git_config_string(&opts->default_backend, var, value);
    - 	}
    - 
     @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)
    + {
    + 	struct rebase_options options;
    + 	const char *branch_name;
    ++	const char *strategy_opt = NULL;
    + 	int ret, flags, total_argc, in_progress = 0;
    + 	int keep_base = 0;
    + 	int ok_to_skip_pre_rebase = 0;
    +@@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)
    + 			PARSE_OPT_OPTARG, parse_opt_rebase_merges),
    + 		OPT_BOOL(0, "fork-point", &options.fork_point,
    + 			 N_("use 'merge-base --fork-point' to refine upstream")),
    +-		OPT_STRING('s', "strategy", &options.strategy,
    ++		OPT_STRING('s', "strategy", &strategy_opt,
    + 			   N_("strategy"), N_("use the given merge strategy")),
    + 		OPT_STRING_LIST('X', "strategy-option", &options.strategy_opts,
    + 				N_("option"),
    +@@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)
    + 		}
      	}
      
    - 	if (options.strategy_opts.nr && !options.strategy)
    +-	if (options.strategy_opts.nr && !options.strategy)
     -		options.strategy = "ort";
     -
     -	if (options.strategy) {
     -		options.strategy = xstrdup(options.strategy);
    ++	if (strategy_opt)
    ++		options.strategy = xstrdup(strategy_opt);
    ++	else if (options.strategy_opts.nr && !options.strategy)
     +		options.strategy = xstrdup("ort");
    -+	else
    -+		options.strategy = xstrdup_or_null(options.strategy);
     +	if (options.strategy)
      		imply_merge(&options, "--strategy");
     -	}
      
      	if (options.root && !options.onto_name)
      		imply_merge(&options, "--root without --onto");
    -@@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)
    - 	}
    - 
    - 	if (options.type == REBASE_UNSPECIFIED) {
    --		if (!strcmp(options.default_backend, "merge"))
    -+		if (!options.default_backend)
    -+			options.type = REBASE_MERGE;
    -+		else if (!strcmp(options.default_backend, "merge"))
    - 			options.type = REBASE_MERGE;
    - 		else if (!strcmp(options.default_backend, "apply"))
    - 			options.type = REBASE_APPLY;
    -@@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)
    - cleanup:
    - 	strbuf_release(&buf);
    - 	strbuf_release(&revisions);
    -+	free(options.default_backend);
    - 	free(options.reflog_action);
    - 	free(options.head_name);
    - 	strvec_clear(&options.git_am_opts);
18:  129482dbaa = 18:  bf854b3979 builtin/merge: always store allocated strings in `pull_twohead`
19:  37e7aaed97 = 19:  9b9d57ae84 config.mak.dev: enable `-Wwrite-strings` warning
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 30, 2024, 12:50 UTC · re: Patrick Steinhardt · lore

[PATCH v2 01/19] global: improve const correctness when assigning string constants

We're about to enable `-Wwrite-strings`, which changes the type of string constants to `const char[]`. Fix various sites where we assign such constants to non-const variables.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/bisect.c             |  3 ++-
 builtin/blame.c              |  2 +-
 builtin/bugreport.c          |  2 +-
 builtin/check-ignore.c       |  4 +--
 builtin/clone.c              |  6 ++---
 builtin/commit.c             |  6 ++---
 builtin/diagnose.c           |  2 +-
 builtin/log.c                |  2 +-
 builtin/mailsplit.c          |  4 +--
 builtin/pull.c               | 52 ++++++++++++++++++------------------
 builtin/receive-pack.c       |  4 +--
 builtin/revert.c             |  2 +-
 compat/regex/regcomp.c       |  2 +-
 diff.c                       |  4 +--
 diffcore-rename.c            |  6 ++---
 fmt-merge-msg.c              |  2 +-
 fsck.c                       |  2 +-
 fsck.h                       |  2 +-
 gpg-interface.c              |  2 +-
 http-backend.c               |  2 +-
 imap-send.c                  |  6 ++---
 pretty.c                     |  2 +-
 refs.c                       |  2 +-
 refs.h                       |  2 +-
 reftable/record.c            |  6 ++---
 run-command.c                |  2 +-
 t/helper/test-hashmap.c      |  3 ++-
 t/helper/test-json-writer.c  | 10 +++----
 t/helper/test-regex.c        |  4 +--
 t/helper/test-rot13-filter.c |  5 ++--
 t/unit-tests/t-strbuf.c      | 10 ++++---
 trailer.c                    |  2 +-
 wt-status.c                  |  2 +-
 33 files changed, 86 insertions(+), 81 deletions(-)
Show changes to 33 files +86 −81

builtin/bisect.c, builtin/blame.c, builtin/bugreport.c, builtin/check-ignore.c, builtin/clone.c, builtin/commit.c, builtin/diagnose.c, builtin/log.c, builtin/mailsplit.c, builtin/pull.c, builtin/receive-pack.c, builtin/revert.c, compat/regex/regcomp.c, diff.c, diffcore-rename.c, fmt-merge-msg.c, fsck.c, fsck.h, gpg-interface.c, http-backend.c, imap-send.c, pretty.c, refs.c, refs.h, reftable/record.c, run-command.c, t/helper/test-hashmap.c, t/helper/test-json-writer.c, t/helper/test-regex.c, t/helper/test-rot13-filter.c, t/unit-tests/t-strbuf.c, trailer.c, wt-status.c

diff --git a/builtin/bisect.c b/builtin/bisect.c
index a58432b9d9..dabce9b542 100644
--- a/builtin/bisect.c
+++ b/builtin/bisect.c
@@ -262,7 +262,8 @@ static int bisect_reset(const char *commit)
 	return bisect_clean_state();
 }
 
-static void log_commit(FILE *fp, char *fmt, const char *state,
+static void log_commit(FILE *fp,
+		       const char *fmt, const char *state,
 		       struct commit *commit)
 {
 	struct pretty_print_context pp = {0};
diff --git a/builtin/blame.c b/builtin/blame.c
index 838cd476be..98c7629b6a 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -134,7 +134,7 @@ static void get_ac_line(const char *inbuf, const char *what,
 {
 	struct ident_split ident;
 	size_t len, maillen, namelen;
-	char *tmp, *endp;
+	const char *tmp, *endp;
 	const char *namebuf, *mailbuf;
 
 	tmp = strstr(inbuf, what);
diff --git a/builtin/bugreport.c b/builtin/bugreport.c
index 25f860a0d9..b3cc77af53 100644
--- a/builtin/bugreport.c
+++ b/builtin/bugreport.c
@@ -107,7 +107,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix)
 	struct tm tm;
 	enum diagnose_mode diagnose = DIAGNOSE_NONE;
 	char *option_output = NULL;
-	char *option_suffix = "%Y-%m-%d-%H%M";
+	const char *option_suffix = "%Y-%m-%d-%H%M";
 	const char *user_relative_path = NULL;
 	char *prefixed_filename;
 	size_t output_path_len;
diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c
index 6c43430ec4..2bda6a1d46 100644
--- a/builtin/check-ignore.c
+++ b/builtin/check-ignore.c
@@ -35,8 +35,8 @@ static const struct option check_ignore_options[] = {
 
 static void output_pattern(const char *path, struct path_pattern *pattern)
 {
-	char *bang  = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE)  ? "!" : "";
-	char *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? "/" : "";
+	const char *bang  = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE)  ? "!" : "";
+	const char *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? "/" : "";
 	if (!nul_term_line) {
 		if (!verbose) {
 			write_name_quoted(path, stdout, '\n');
diff --git a/builtin/clone.c b/builtin/clone.c
index 23993b905b..92ab7d7165 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -71,7 +71,7 @@ static char *option_branch = NULL;
 static struct string_list option_not = STRING_LIST_INIT_NODUP;
 static const char *real_git_dir;
 static const char *ref_format;
-static char *option_upload_pack = "git-upload-pack";
+static const char *option_upload_pack = "git-upload-pack";
 static int option_verbosity;
 static int option_progress = -1;
 static int option_sparse_checkout;
@@ -177,8 +177,8 @@ static struct option builtin_clone_options[] = {
 
 static const char *get_repo_path_1(struct strbuf *path, int *is_bundle)
 {
-	static char *suffix[] = { "/.git", "", ".git/.git", ".git" };
-	static char *bundle_suffix[] = { ".bundle", "" };
+	static const char *suffix[] = { "/.git", "", ".git/.git", ".git" };
+	static const char *bundle_suffix[] = { ".bundle", "" };
 	size_t baselen = path->len;
 	struct stat st;
 	int i;
diff --git a/builtin/commit.c b/builtin/commit.c
index f53e7e86ff..75c741173e 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,7 +113,7 @@ static char *template_file;
  * the commit message and/or authorship.
  */
 static const char *author_message, *author_message_buffer;
-static char *edit_message, *use_message;
+static const char *edit_message, *use_message;
 static char *fixup_message, *fixup_commit, *squash_message;
 static const char *fixup_prefix;
 static int all, also, interactive, patch_interactive, only, amend, signoff;
@@ -121,8 +121,8 @@ static int edit_flag = -1; /* unspecified */
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
-static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
-static char *sign_commit, *pathspec_from_file;
+static const char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
+static const char *sign_commit, *pathspec_from_file;
 static struct strvec trailer_args = STRVEC_INIT;
 
 /*
diff --git a/builtin/diagnose.c b/builtin/diagnose.c
index 4f22eb2b55..4857a4395b 100644
--- a/builtin/diagnose.c
+++ b/builtin/diagnose.c
@@ -18,7 +18,7 @@ int cmd_diagnose(int argc, const char **argv, const char *prefix)
 	struct tm tm;
 	enum diagnose_mode mode = DIAGNOSE_STATS;
 	char *option_output = NULL;
-	char *option_suffix = "%Y-%m-%d-%H%M";
+	const char *option_suffix = "%Y-%m-%d-%H%M";
 	char *prefixed_filename;
 
 	const struct option diagnose_options[] = {
diff --git a/builtin/log.c b/builtin/log.c
index 78a247d8a9..b8846a9458 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1283,7 +1283,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids)
 	o2->flags = flags2;
 }
 
-static void gen_message_id(struct rev_info *info, char *base)
+static void gen_message_id(struct rev_info *info, const char *base)
 {
 	struct strbuf buf = STRBUF_INIT;
 	strbuf_addf(&buf, "%s.%"PRItime".git.%s", base,
diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c
index 3af9ddb8ae..fe6dbc5d05 100644
--- a/builtin/mailsplit.c
+++ b/builtin/mailsplit.c
@@ -113,8 +113,8 @@ static int populate_maildir_list(struct string_list *list, const char *path)
 	DIR *dir;
 	struct dirent *dent;
 	char *name = NULL;
-	char *subs[] = { "cur", "new", NULL };
-	char **sub;
+	const char *subs[] = { "cur", "new", NULL };
+	const char **sub;
 	int ret = -1;
 
 	for (sub = subs; *sub; ++sub) {
diff --git a/builtin/pull.c b/builtin/pull.c
index d622202bce..2d0429f14f 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -71,48 +71,48 @@ static const char * const pull_usage[] = {
 
 /* Shared options */
 static int opt_verbosity;
-static char *opt_progress;
+static const char *opt_progress;
 static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
 static int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT;
 
 /* Options passed to git-merge or git-rebase */
 static enum rebase_type opt_rebase = -1;
-static char *opt_diffstat;
-static char *opt_log;
-static char *opt_signoff;
-static char *opt_squash;
-static char *opt_commit;
-static char *opt_edit;
-static char *cleanup_arg;
-static char *opt_ff;
-static char *opt_verify_signatures;
-static char *opt_verify;
+static const char *opt_diffstat;
+static const char *opt_log;
+static const char *opt_signoff;
+static const char *opt_squash;
+static const char *opt_commit;
+static const char *opt_edit;
+static const char *cleanup_arg;
+static const char *opt_ff;
+static const char *opt_verify_signatures;
+static const char *opt_verify;
 static int opt_autostash = -1;
 static int config_autostash;
 static int check_trust_level = 1;
 static struct strvec opt_strategies = STRVEC_INIT;
 static struct strvec opt_strategy_opts = STRVEC_INIT;
-static char *opt_gpg_sign;
+static const char *opt_gpg_sign;
 static int opt_allow_unrelated_histories;
 
 /* Options passed to git-fetch */
-static char *opt_all;
-static char *opt_append;
-static char *opt_upload_pack;
+static const char *opt_all;
+static const char *opt_append;
+static const char *opt_upload_pack;
 static int opt_force;
-static char *opt_tags;
-static char *opt_prune;
-static char *max_children;
+static const char *opt_tags;
+static const char *opt_prune;
+static const char *max_children;
 static int opt_dry_run;
-static char *opt_keep;
-static char *opt_depth;
-static char *opt_unshallow;
-static char *opt_update_shallow;
-static char *opt_refmap;
-static char *opt_ipv4;
-static char *opt_ipv6;
+static const char *opt_keep;
+static const char *opt_depth;
+static const char *opt_unshallow;
+static const char *opt_update_shallow;
+static const char *opt_refmap;
+static const char *opt_ipv4;
+static const char *opt_ipv6;
 static int opt_show_forced_updates = -1;
-static char *set_upstream;
+static const char *set_upstream;
 static struct strvec opt_fetch = STRVEC_INIT;
 
 static struct option pull_options[] = {
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 01c1f04ece..c8d12ee0a7 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1249,7 +1249,7 @@ static int run_proc_receive_hook(struct command *commands,
 	return code;
 }
 
-static char *refuse_unconfigured_deny_msg =
+static const char *refuse_unconfigured_deny_msg =
 	N_("By default, updating the current branch in a non-bare repository\n"
 	   "is denied, because it will make the index and work tree inconsistent\n"
 	   "with what you pushed, and will require 'git reset --hard' to match\n"
@@ -1269,7 +1269,7 @@ static void refuse_unconfigured_deny(void)
 	rp_error("%s", _(refuse_unconfigured_deny_msg));
 }
 
-static char *refuse_unconfigured_deny_delete_current_msg =
+static const char *refuse_unconfigured_deny_delete_current_msg =
 	N_("By default, deleting the current branch is denied, because the next\n"
 	   "'git clone' won't result in any file checked out, causing confusion.\n"
 	   "\n"
diff --git a/builtin/revert.c b/builtin/revert.c
index 53935d2c68..7bf2b4e11d 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -179,7 +179,7 @@ static int run_sequencer(int argc, const char **argv, const char *prefix,
 
 	/* Check for incompatible command line arguments */
 	if (cmd) {
-		char *this_operation;
+		const char *this_operation;
 		if (cmd == 'q')
 			this_operation = "--quit";
 		else if (cmd == 'c')
diff --git a/compat/regex/regcomp.c b/compat/regex/regcomp.c
index 2bc0f1187a..6c5d455e92 100644
--- a/compat/regex/regcomp.c
+++ b/compat/regex/regcomp.c
@@ -848,7 +848,7 @@ init_dfa (re_dfa_t *dfa, size_t pat_len)
 {
   unsigned int table_size;
 #ifndef _LIBC
-  char *codeset_name;
+  const char *codeset_name;
 #endif
 
   memset (dfa, '\0', sizeof (re_dfa_t));
diff --git a/diff.c b/diff.c
index e70301df76..ffd867ef6c 100644
--- a/diff.c
+++ b/diff.c
@@ -3764,7 +3764,7 @@ static void builtin_diff(const char *name_a,
 	return;
 }
 
-static char *get_compact_summary(const struct diff_filepair *p, int is_renamed)
+static const char *get_compact_summary(const struct diff_filepair *p, int is_renamed)
 {
 	if (!is_renamed) {
 		if (p->status == DIFF_STATUS_ADDED) {
@@ -4076,7 +4076,7 @@ static int reuse_worktree_file(struct index_state *istate,
 static int diff_populate_gitlink(struct diff_filespec *s, int size_only)
 {
 	struct strbuf buf = STRBUF_INIT;
-	char *dirty = "";
+	const char *dirty = "";
 
 	/* Are we looking at the work tree? */
 	if (s->dirty_submodule)
diff --git a/diffcore-rename.c b/diffcore-rename.c
index 5a6e2bcac7..0e1adb87df 100644
--- a/diffcore-rename.c
+++ b/diffcore-rename.c
@@ -406,7 +406,7 @@ static const char *get_highest_rename_path(struct strintmap *counts)
 	return highest_destination_dir;
 }
 
-static char *UNKNOWN_DIR = "/";  /* placeholder -- short, illegal directory */
+static const char *UNKNOWN_DIR = "/";  /* placeholder -- short, illegal directory */
 
 static int dir_rename_already_determinable(struct strintmap *counts)
 {
@@ -429,8 +429,8 @@ static int dir_rename_already_determinable(struct strintmap *counts)
 }
 
 static void increment_count(struct dir_rename_info *info,
-			    char *old_dir,
-			    char *new_dir)
+			    const char *old_dir,
+			    const char *new_dir)
 {
 	struct strintmap *counts;
 	struct strmap_entry *e;
diff --git a/fmt-merge-msg.c b/fmt-merge-msg.c
index 7d144b803a..5af63ab5ab 100644
--- a/fmt-merge-msg.c
+++ b/fmt-merge-msg.c
@@ -447,7 +447,7 @@ static void fmt_merge_msg_title(struct strbuf *out,
 				const char *current_branch)
 {
 	int i = 0;
-	char *sep = "";
+	const char *sep = "";
 
 	strbuf_addstr(out, "Merge ");
 	for (i = 0; i < srcs.nr; i++) {
diff --git a/fsck.c b/fsck.c
index 7dff41413e..61cd48aa25 100644
--- a/fsck.c
+++ b/fsck.c
@@ -1231,7 +1231,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size,
 }
 
 int fsck_buffer(const struct object_id *oid, enum object_type type,
-		void *data, unsigned long size,
+		const void *data, unsigned long size,
 		struct fsck_options *options)
 {
 	if (type == OBJ_BLOB)
diff --git a/fsck.h b/fsck.h
index 17fa2dda5d..4f0c4e6479 100644
--- a/fsck.h
+++ b/fsck.h
@@ -202,7 +202,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size,
  * struct.
  */
 int fsck_buffer(const struct object_id *oid, enum object_type,
-		void *data, unsigned long size,
+		const void *data, unsigned long size,
 		struct fsck_options *options);
 
 /*
diff --git a/gpg-interface.c b/gpg-interface.c
index 5193223714..71a9382a61 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -727,7 +727,7 @@ static int git_gpg_config(const char *var, const char *value,
 			  void *cb UNUSED)
 {
 	struct gpg_format *fmt = NULL;
-	char *fmtname = NULL;
+	const char *fmtname = NULL;
 	char *trust;
 	int ret;
 
diff --git a/http-backend.c b/http-backend.c
index 5b65287ac9..5b4dca65ed 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -753,7 +753,7 @@ static int bad_request(struct strbuf *hdr, const struct service_cmd *c)
 
 int cmd_main(int argc UNUSED, const char **argv UNUSED)
 {
-	char *method = getenv("REQUEST_METHOD");
+	const char *method = getenv("REQUEST_METHOD");
 	const char *proto_header;
 	char *dir;
 	struct service_cmd *cmd = NULL;
diff --git a/imap-send.c b/imap-send.c
index a5d1510180..8b723b34a5 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -1215,9 +1215,9 @@ static int imap_store_msg(struct imap_store *ctx, struct strbuf *msg)
 static void wrap_in_html(struct strbuf *msg)
 {
 	struct strbuf buf = STRBUF_INIT;
-	static char *content_type = "Content-Type: text/html;\n";
-	static char *pre_open = "<pre>\n";
-	static char *pre_close = "</pre>\n";
+	static const char *content_type = "Content-Type: text/html;\n";
+	static const char *pre_open = "<pre>\n";
+	static const char *pre_close = "</pre>\n";
 	const char *body = strstr(msg->buf, "\n\n");
 
 	if (!body)
diff --git a/pretty.c b/pretty.c
index 22a81506b7..ec05db5655 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1325,7 +1325,7 @@ int format_set_trailers_options(struct process_trailer_options *opts,
 static size_t parse_describe_args(const char *start, struct strvec *args)
 {
 	struct {
-		char *name;
+		const char *name;
 		enum {
 			DESCRIBE_ARG_BOOL,
 			DESCRIBE_ARG_INTEGER,
diff --git a/refs.c b/refs.c
index 8260c27cde..292e8d947e 100644
--- a/refs.c
+++ b/refs.c
@@ -159,7 +159,7 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref)
 {
 	struct ref_namespace_info *info = &ref_namespace[namespace];
 	if (info->ref_updated)
-		free(info->ref);
+		free((char *)info->ref);
 	info->ref = ref;
 	info->ref_updated = 1;
 }
diff --git a/refs.h b/refs.h
index 34568ee1fb..923f751d18 100644
--- a/refs.h
+++ b/refs.h
@@ -975,7 +975,7 @@ struct ref_store *get_worktree_ref_store(const struct worktree *wt);
  */
 
 struct ref_namespace_info {
-	char *ref;
+	const char *ref;
 	enum decoration_type decoration;
 
 	/*
diff --git a/reftable/record.c b/reftable/record.c
index 5506f3e913..a2cba5ef74 100644
--- a/reftable/record.c
+++ b/reftable/record.c
@@ -116,7 +116,7 @@ static int decode_string(struct strbuf *dest, struct string_view in)
 	return start_len - in.len;
 }
 
-static int encode_string(char *str, struct string_view s)
+static int encode_string(const char *str, struct string_view s)
 {
 	struct string_view start = s;
 	int l = strlen(str);
@@ -969,9 +969,9 @@ static int reftable_log_record_decode(void *rec, struct strbuf key,
 	return REFTABLE_FORMAT_ERROR;
 }
 
-static int null_streq(char *a, char *b)
+static int null_streq(const char *a, const char *b)
 {
-	char *empty = "";
+	const char *empty = "";
 	if (!a)
 		a = empty;
 
diff --git a/run-command.c b/run-command.c
index 1b821042b4..7600531fb6 100644
--- a/run-command.c
+++ b/run-command.c
@@ -663,7 +663,7 @@ int start_command(struct child_process *cmd)
 	int need_in, need_out, need_err;
 	int fdin[2], fdout[2], fderr[2];
 	int failed_errno;
-	char *str;
+	const char *str;
 
 	/*
 	 * In case of errors we must keep the promise to close FDs
diff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c
index 0eb0b3d49c..2912899558 100644
--- a/t/helper/test-hashmap.c
+++ b/t/helper/test-hashmap.c
@@ -36,7 +36,8 @@ static int test_entry_cmp(const void *cmp_data,
 }
 
 static struct test_entry *alloc_test_entry(unsigned int hash,
-					   char *key, char *value)
+					   const char *key,
+					   const char *value)
 {
 	size_t klen = strlen(key);
 	size_t vlen = strlen(value);
diff --git a/t/helper/test-json-writer.c b/t/helper/test-json-writer.c
index afe393f597..ed52eb76bf 100644
--- a/t/helper/test-json-writer.c
+++ b/t/helper/test-json-writer.c
@@ -174,7 +174,7 @@ static void make_arr4(int pretty)
 	jw_end(&arr4);
 }
 
-static char *expect_nest1 =
+static const char *expect_nest1 =
 	"{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true},\"arr1\":[\"abc\",42,true]}";
 
 static struct json_writer nest1 = JSON_WRITER_INIT;
@@ -195,10 +195,10 @@ static void make_nest1(int pretty)
 	jw_release(&arr1);
 }
 
-static char *expect_inline1 =
+static const char *expect_inline1 =
 	"{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true},\"arr1\":[\"abc\",42,true]}";
 
-static char *pretty_inline1 =
+static const char *pretty_inline1 =
 	("{\n"
 	 "  \"obj1\": {\n"
 	 "    \"a\": \"abc\",\n"
@@ -236,10 +236,10 @@ static void make_inline1(int pretty)
 	jw_end(&inline1);
 }
 
-static char *expect_inline2 =
+static const char *expect_inline2 =
 	"[[1,2],[3,4],{\"a\":\"abc\"}]";
 
-static char *pretty_inline2 =
+static const char *pretty_inline2 =
 	("[\n"
 	 "  [\n"
 	 "    1,\n"
diff --git a/t/helper/test-regex.c b/t/helper/test-regex.c
index 80042eafc2..366bd70976 100644
--- a/t/helper/test-regex.c
+++ b/t/helper/test-regex.c
@@ -20,8 +20,8 @@ static struct reg_flag reg_flags[] = {
 
 static int test_regex_bug(void)
 {
-	char *pat = "[^={} \t]+";
-	char *str = "={}\nfred";
+	const char *pat = "[^={} \t]+";
+	const char *str = "={}\nfred";
 	regex_t r;
 	regmatch_t m[1];
 
diff --git a/t/helper/test-rot13-filter.c b/t/helper/test-rot13-filter.c
index f8d564c622..7e1d9e0ee4 100644
--- a/t/helper/test-rot13-filter.c
+++ b/t/helper/test-rot13-filter.c
@@ -136,7 +136,7 @@ static void free_delay_entries(void)
 	strmap_clear(&delay, 0);
 }
 
-static void add_delay_entry(char *pathname, int count, int requested)
+static void add_delay_entry(const char *pathname, int count, int requested)
 {
 	struct delay_entry *entry = xcalloc(1, sizeof(*entry));
 	entry->count = count;
@@ -189,7 +189,8 @@ static void reply_list_available_blobs_cmd(void)
 static void command_loop(void)
 {
 	for (;;) {
-		char *buf, *output;
+		char *buf;
+		const char *output;
 		char *pathname;
 		struct delay_entry *entry;
 		struct strbuf input = STRBUF_INIT;
diff --git a/t/unit-tests/t-strbuf.c b/t/unit-tests/t-strbuf.c
index de434a4441..6027dafef7 100644
--- a/t/unit-tests/t-strbuf.c
+++ b/t/unit-tests/t-strbuf.c
@@ -2,7 +2,8 @@
 #include "strbuf.h"
 
 /* wrapper that supplies tests with an empty, initialized strbuf */
-static void setup(void (*f)(struct strbuf*, void*), void *data)
+static void setup(void (*f)(struct strbuf*, const void*),
+		  const void *data)
 {
 	struct strbuf buf = STRBUF_INIT;
 
@@ -13,7 +14,8 @@ static void setup(void (*f)(struct strbuf*, void*), void *data)
 }
 
 /* wrapper that supplies tests with a populated, initialized strbuf */
-static void setup_populated(void (*f)(struct strbuf*, void*), char *init_str, void *data)
+static void setup_populated(void (*f)(struct strbuf*, const void*),
+			    const char *init_str, const void *data)
 {
 	struct strbuf buf = STRBUF_INIT;
 
@@ -64,7 +66,7 @@ static void t_dynamic_init(void)
 	strbuf_release(&buf);
 }
 
-static void t_addch(struct strbuf *buf, void *data)
+static void t_addch(struct strbuf *buf, const void *data)
 {
 	const char *p_ch = data;
 	const char ch = *p_ch;
@@ -83,7 +85,7 @@ static void t_addch(struct strbuf *buf, void *data)
 	check_char(buf->buf[buf->len], ==, '\0');
 }
 
-static void t_addstr(struct strbuf *buf, void *data)
+static void t_addstr(struct strbuf *buf, const void *data)
 {
 	const char *text = data;
 	size_t len = strlen(text);
diff --git a/trailer.c b/trailer.c
index 2bcb9ba8f7..72e5136c73 100644
--- a/trailer.c
+++ b/trailer.c
@@ -63,7 +63,7 @@ struct arg_item {
 
 static LIST_HEAD(conf_head);
 
-static char *separators = ":";
+static const char *separators = ":";
 
 static int configured;
 
diff --git a/wt-status.c b/wt-status.c
index ff4be071ca..7912545e4e 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -2408,7 +2408,7 @@ static void wt_porcelain_v2_print_unmerged_entry(
 		int mode;
 		struct object_id oid;
 	} stages[3];
-	char *key;
+	const char *key;
 	char submodule_token[5];
 	char unmerged_prefix = 'u';
 	char eol_char = s->null_termination ? '\0' : '\n';
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 30, 2024, 12:50 UTC · re: Patrick Steinhardt · lore

[PATCH v2 02/19] global: assign non-const strings as required

There are several cases where we initialize non-const fields with string constants. This is invalid and will cause warnings once we enable the `-Wwrite-strings` warning. Adapt those cases to instead use string arrays.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/remote.c          |  3 +-
 diff.c                    |  3 +-
 entry.c                   |  7 ++--
 ident.c                   |  9 +++-
 line-log.c                |  3 +-
 object-file.c             |  3 +-
 pretty.c                  |  5 ++-
 refs/reftable-backend.c   |  5 ++-
 reftable/basics_test.c    |  5 ++-
 reftable/block_test.c     |  4 +-
 reftable/merged_test.c    | 52 ++++++++++++-----------
 reftable/readwrite_test.c | 60 ++++++++++++++++-----------
 reftable/stack_test.c     | 87 +++++++++++++++++++++------------------
 13 files changed, 144 insertions(+), 102 deletions(-)
Show changes to 13 files +144 −102

builtin/remote.c, diff.c, entry.c, ident.c, line-log.c, object-file.c, pretty.c, refs/reftable-backend.c, reftable/basics_test.c, reftable/block_test.c, reftable/merged_test.c, reftable/readwrite_test.c, reftable/stack_test.c

diff --git a/builtin/remote.c b/builtin/remote.c
index d52b1c0e10..0324a5d48d 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -494,11 +494,12 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat
 	struct ref *ref, *matches;
 	struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;
 	struct refspec_item refspec;
+	char refspec_str[] = "refs/heads/*";
 
 	memset(&refspec, 0, sizeof(refspec));
 	refspec.force = 0;
 	refspec.pattern = 1;
-	refspec.src = refspec.dst = "refs/heads/*";
+	refspec.src = refspec.dst = refspec_str;
 	get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
 	matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
 				    fetch_map, 1);
diff --git a/diff.c b/diff.c
index ffd867ef6c..1439a5a01d 100644
--- a/diff.c
+++ b/diff.c
@@ -7231,11 +7231,12 @@ size_t fill_textconv(struct repository *r,
 		     struct diff_filespec *df,
 		     char **outbuf)
 {
+	static char empty_str[] = "";
 	size_t size;
 
 	if (!driver) {
 		if (!DIFF_FILE_VALID(df)) {
-			*outbuf = "";
+			*outbuf = empty_str;
 			return 0;
 		}
 		if (diff_populate_filespec(r, df, NULL))
diff --git a/entry.c b/entry.c
index b8c257f6f9..2fc06ac90c 100644
--- a/entry.c
+++ b/entry.c
@@ -175,6 +175,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 	struct string_list_item *filter, *path;
 	struct progress *progress = NULL;
 	struct delayed_checkout *dco = state->delayed_checkout;
+	char empty_str[] = "";
 
 	if (!state->delayed_checkout)
 		return errs;
@@ -189,7 +190,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 			if (!async_query_available_blobs(filter->string, &available_paths)) {
 				/* Filter reported an error */
 				errs = 1;
-				filter->string = "";
+				filter->string = empty_str;
 				continue;
 			}
 			if (available_paths.nr <= 0) {
@@ -199,7 +200,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 				 * filter from the list (see
 				 * "string_list_remove_empty_items" call below).
 				 */
-				filter->string = "";
+				filter->string = empty_str;
 				continue;
 			}
 
@@ -225,7 +226,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 					 * Do not ask the filter for available blobs,
 					 * again, as the filter is likely buggy.
 					 */
-					filter->string = "";
+					filter->string = empty_str;
 					continue;
 				}
 				ce = index_file_exists(state->istate, path->string,
diff --git a/ident.c b/ident.c
index cc7afdbf81..df7aa42802 100644
--- a/ident.c
+++ b/ident.c
@@ -46,9 +46,14 @@ static struct passwd *xgetpwuid_self(int *is_bogus)
 	pw = getpwuid(getuid());
 	if (!pw) {
 		static struct passwd fallback;
-		fallback.pw_name = "unknown";
+		static char fallback_name[] = "unknown";
 #ifndef NO_GECOS_IN_PWENT
-		fallback.pw_gecos = "Unknown";
+		static char fallback_gcos[] = "Unknown";
+#endif
+
+		fallback.pw_name = fallback_name;
+#ifndef NO_GECOS_IN_PWENT
+		fallback.pw_gecos = fallback_gcos;
 #endif
 		pw = &fallback;
 		if (is_bogus)
diff --git a/line-log.c b/line-log.c
index 8ff6ccb772..d9bf2c8120 100644
--- a/line-log.c
+++ b/line-log.c
@@ -1032,6 +1032,7 @@ static int process_diff_filepair(struct rev_info *rev,
 	struct range_set tmp;
 	struct diff_ranges diff;
 	mmfile_t file_parent, file_target;
+	char empty_str[] = "";
 
 	assert(pair->two->path);
 	while (rg) {
@@ -1056,7 +1057,7 @@ static int process_diff_filepair(struct rev_info *rev,
 		file_parent.ptr = pair->one->data;
 		file_parent.size = pair->one->size;
 	} else {
-		file_parent.ptr = "";
+		file_parent.ptr = empty_str;
 		file_parent.size = 0;
 	}
 
diff --git a/object-file.c b/object-file.c
index 610b1f465c..c9e374e57e 100644
--- a/object-file.c
+++ b/object-file.c
@@ -282,12 +282,13 @@ static struct cached_object {
 } *cached_objects;
 static int cached_object_nr, cached_object_alloc;
 
+static char empty_tree_buf[] = "";
 static struct cached_object empty_tree = {
 	.oid = {
 		.hash = EMPTY_TREE_SHA1_BIN_LITERAL,
 	},
 	.type = OBJ_TREE,
-	.buf = "",
+	.buf = empty_tree_buf,
 };
 
 static struct cached_object *find_cached_object(const struct object_id *oid)
diff --git a/pretty.c b/pretty.c
index ec05db5655..1a0030b32a 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1583,9 +1583,10 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
 		return 1;
 	case 'D':
 		{
+			char empty_str[] = "";
 			const struct decoration_options opts = {
-				.prefix = "",
-				.suffix = ""
+				.prefix = empty_str,
+				.suffix = empty_str,
 			};
 
 			format_decorations(sb, commit, c->auto_color, &opts);
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 1af86bbdec..1908e74dea 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1285,6 +1285,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 	struct strbuf errbuf = STRBUF_INIT;
 	size_t logs_nr = 0, logs_alloc = 0, i;
 	const char *committer_info;
+	char head[] = "HEAD";
 	int ret;
 
 	committer_info = git_committer_info(0);
@@ -1387,7 +1388,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 		if (append_head_reflog) {
 			ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 			logs[logs_nr] = logs[logs_nr - 1];
-			logs[logs_nr].refname = "HEAD";
+			logs[logs_nr].refname = head;
 			logs_nr++;
 		}
 	}
@@ -1463,7 +1464,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 	string_list_clear(&skip, 0);
 	strbuf_release(&errbuf);
 	for (i = 0; i < logs_nr; i++) {
-		if (!strcmp(logs[i].refname, "HEAD"))
+		if (logs[i].refname == head)
 			continue;
 		logs[i].refname = NULL;
 		reftable_log_record_release(&logs[i]);
diff --git a/reftable/basics_test.c b/reftable/basics_test.c
index 997c4d9e01..23fab22eb1 100644
--- a/reftable/basics_test.c
+++ b/reftable/basics_test.c
@@ -58,8 +58,9 @@ static void test_binsearch(void)
 
 static void test_names_length(void)
 {
-	char *a[] = { "a", "b", NULL };
-	EXPECT(names_length(a) == 2);
+	char a[] = "a", b[] = "b";
+	char *names[] = { a, b, NULL };
+	EXPECT(names_length(names) == 2);
 }
 
 static void test_parse_names_normal(void)
diff --git a/reftable/block_test.c b/reftable/block_test.c
index 26a9cfbc83..d5967b214d 100644
--- a/reftable/block_test.c
+++ b/reftable/block_test.c
@@ -19,7 +19,7 @@ license that can be found in the LICENSE file or at
 static void test_block_read_write(void)
 {
 	const int header_off = 21; /* random */
-	char *names[30];
+	char *names[30], empty_str[] = "";
 	const int N = ARRAY_SIZE(names);
 	const int block_size = 1024;
 	struct reftable_block block = { NULL };
@@ -42,7 +42,7 @@ static void test_block_read_write(void)
 	block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,
 			  header_off, hash_size(GIT_SHA1_FORMAT_ID));
 
-	rec.u.ref.refname = "";
+	rec.u.ref.refname = empty_str;
 	rec.u.ref.value_type = REFTABLE_REF_DELETION;
 	n = block_writer_add(&bw, &rec);
 	EXPECT(n == REFTABLE_API_ERROR);
diff --git a/reftable/merged_test.c b/reftable/merged_test.c
index 530fc82d1c..fd5a065e42 100644
--- a/reftable/merged_test.c
+++ b/reftable/merged_test.c
@@ -123,14 +123,15 @@ static void readers_destroy(struct reftable_reader **readers, size_t n)
 
 static void test_merged_between(void)
 {
+	char a[] = "a", b[] = "b";
 	struct reftable_ref_record r1[] = { {
-		.refname = "b",
+		.refname = b,
 		.update_index = 1,
 		.value_type = REFTABLE_REF_VAL1,
 		.value.val1 = { 1, 2, 3, 0 },
 	} };
 	struct reftable_ref_record r2[] = { {
-		.refname = "a",
+		.refname = a,
 		.update_index = 2,
 		.value_type = REFTABLE_REF_DELETION,
 	} };
@@ -163,40 +164,41 @@ static void test_merged_between(void)
 
 static void test_merged(void)
 {
+	char a[] = "a", b[] = "b", c[] = "c", d[] = "d";
 	struct reftable_ref_record r1[] = {
 		{
-			.refname = "a",
+			.refname = a,
 			.update_index = 1,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
 		},
 		{
-			.refname = "b",
+			.refname = b,
 			.update_index = 1,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
 		},
 		{
-			.refname = "c",
+			.refname = c,
 			.update_index = 1,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
 		}
 	};
 	struct reftable_ref_record r2[] = { {
-		.refname = "a",
+		.refname = a,
 		.update_index = 2,
 		.value_type = REFTABLE_REF_DELETION,
 	} };
 	struct reftable_ref_record r3[] = {
 		{
-			.refname = "c",
+			.refname = c,
 			.update_index = 3,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 2 },
 		},
 		{
-			.refname = "d",
+			.refname = d,
 			.update_index = 3,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
@@ -289,48 +291,52 @@ merged_table_from_log_records(struct reftable_log_record **logs,
 
 static void test_merged_logs(void)
 {
+	char a[] = "a";
+	char name[] = "jane doe", email[] = "jane@invalid";
+	char message1[] = "message1", message2[] = "message2";
+	char message3[] = "message3";
 	struct reftable_log_record r1[] = {
 		{
-			.refname = "a",
+			.refname = a,
 			.update_index = 2,
 			.value_type = REFTABLE_LOG_UPDATE,
 			.value.update = {
 				.old_hash = { 2 },
 				/* deletion */
-				.name = "jane doe",
-				.email = "jane@invalid",
-				.message = "message2",
+				.name = name,
+				.email = email,
+				.message = message2,
 			}
 		},
 		{
-			.refname = "a",
+			.refname = a,
 			.update_index = 1,
 			.value_type = REFTABLE_LOG_UPDATE,
 			.value.update = {
 				.old_hash = { 1 },
 				.new_hash = { 2 },
-				.name = "jane doe",
-				.email = "jane@invalid",
-				.message = "message1",
+				.name = name,
+				.email = email,
+				.message = message1,
 			}
 		},
 	};
 	struct reftable_log_record r2[] = {
 		{
-			.refname = "a",
+			.refname = a,
 			.update_index = 3,
 			.value_type = REFTABLE_LOG_UPDATE,
 			.value.update = {
 				.new_hash = { 3 },
-				.name = "jane doe",
-				.email = "jane@invalid",
-				.message = "message3",
+				.name = name,
+				.email = email,
+				.message = message3,
 			}
 		},
 	};
 	struct reftable_log_record r3[] = {
 		{
-			.refname = "a",
+			.refname = a,
 			.update_index = 2,
 			.value_type = REFTABLE_LOG_DELETION,
 		},
@@ -400,13 +406,13 @@ static void test_merged_logs(void)
 
 static void test_default_write_opts(void)
 {
+	char master[] = "master";
 	struct reftable_write_options opts = { 0 };
 	struct strbuf buf = STRBUF_INIT;
 	struct reftable_writer *w =
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
-
 	struct reftable_ref_record rec = {
-		.refname = "master",
+		.refname = master,
 		.update_index = 1,
 	};
 	int err;
diff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c
index a6dbd214c5..064d693111 100644
--- a/reftable/readwrite_test.c
+++ b/reftable/readwrite_test.c
@@ -56,6 +56,7 @@ static void write_table(char ***names, struct strbuf *buf, int N,
 	int i = 0, n;
 	struct reftable_log_record log = { NULL };
 	const struct reftable_stats *stats = NULL;
+	char message[] = "message";
 
 	REFTABLE_CALLOC_ARRAY(*names, N + 1);
 
@@ -86,7 +87,7 @@ static void write_table(char ***names, struct strbuf *buf, int N,
 		log.update_index = update_index;
 		log.value_type = REFTABLE_LOG_UPDATE;
 		set_test_hash(log.value.update.new_hash, i);
-		log.value.update.message = "message";
+		log.value.update.message = message;
 
 		n = reftable_writer_add_log(w, &log);
 		EXPECT(n == 0);
@@ -111,23 +112,28 @@ static void write_table(char ***names, struct strbuf *buf, int N,
 
 static void test_log_buffer_size(void)
 {
+	char refname[] = "refs/heads/master";
+	char name[] = "Han-Wen Hienhuys";
+	char email[] = "hanwen@google.com";
+	char message[] = "commit: 9\n";
 	struct strbuf buf = STRBUF_INIT;
 	struct reftable_write_options opts = {
 		.block_size = 4096,
 	};
 	int err;
 	int i;
-	struct reftable_log_record
-		log = { .refname = "refs/heads/master",
-			.update_index = 0xa,
-			.value_type = REFTABLE_LOG_UPDATE,
-			.value = { .update = {
-					   .name = "Han-Wen Nienhuys",
-					   .email = "hanwen@google.com",
-					   .tz_offset = 100,
-					   .time = 0x5e430672,
-					   .message = "commit: 9\n",
-				   } } };
+	struct reftable_log_record log = {
+		.refname = refname,
+		.update_index = 0xa,
+		.value_type = REFTABLE_LOG_UPDATE,
+		.value.update = {
+			.name = name,
+			.email = email,
+			.tz_offset = 100,
+			.time = 0x5e430672,
+			.message = message,
+		},
+	};
 	struct reftable_writer *w =
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
 
@@ -149,6 +155,9 @@ static void test_log_buffer_size(void)
 
 static void test_log_overflow(void)
 {
+	char refname[] = "refs/heads/master";
+	char name[] = "Han-Wen Hienhuys";
+	char email[] = "hanwen@google.com";
 	struct strbuf buf = STRBUF_INIT;
 	char msg[256] = { 0 };
 	struct reftable_write_options opts = {
@@ -156,15 +165,15 @@ static void test_log_overflow(void)
 	};
 	int err;
 	struct reftable_log_record log = {
-		.refname = "refs/heads/master",
+		.refname = refname,
 		.update_index = 0xa,
 		.value_type = REFTABLE_LOG_UPDATE,
 		.value = {
 			.update = {
 				.old_hash = { 1 },
 				.new_hash = { 2 },
-				.name = "Han-Wen Nienhuys",
-				.email = "hanwen@google.com",
+				.name = name,
+				.email = email,
 				.tz_offset = 100,
 				.time = 0x5e430672,
 				.message = msg,
@@ -290,17 +299,20 @@ static void test_log_zlib_corruption(void)
 	struct reftable_writer *w =
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
 	const struct reftable_stats *stats = NULL;
+	char refname[] = "refname";
+	char name[] = "My Name";
+	char email[] = "myname@invalid";
 	char message[100] = { 0 };
 	int err, i, n;
 	struct reftable_log_record log = {
-		.refname = "refname",
+		.refname = refname,
 		.value_type = REFTABLE_LOG_UPDATE,
 		.value = {
 			.update = {
 				.new_hash = { 1 },
 				.old_hash = { 2 },
-				.name = "My Name",
-				.email = "myname@invalid",
+				.name = name,
+				.email = email,
 				.message = message,
 			},
 		},
@@ -727,8 +739,9 @@ static void test_write_empty_key(void)
 	struct strbuf buf = STRBUF_INIT;
 	struct reftable_writer *w =
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
+	char refname[] = "";
 	struct reftable_ref_record ref = {
-		.refname = "",
+		.refname = refname,
 		.update_index = 1,
 		.value_type = REFTABLE_REF_DELETION,
 	};
@@ -750,20 +763,21 @@ static void test_write_key_order(void)
 	struct strbuf buf = STRBUF_INIT;
 	struct reftable_writer *w =
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
+	char a[] = "a", b[] = "b", target[] = "target";
 	struct reftable_ref_record refs[2] = {
 		{
-			.refname = "b",
+			.refname = b,
 			.update_index = 1,
 			.value_type = REFTABLE_REF_SYMREF,
 			.value = {
-				.symref = "target",
+				.symref = target,
 			},
 		}, {
-			.refname = "a",
+			.refname = a,
 			.update_index = 1,
 			.value_type = REFTABLE_REF_SYMREF,
 			.value = {
-				.symref = "target",
+				.symref = target,
 			},
 		}
 	};
diff --git a/reftable/stack_test.c b/reftable/stack_test.c
index 7889f818d1..c6d88e6ea8 100644
--- a/reftable/stack_test.c
+++ b/reftable/stack_test.c
@@ -83,7 +83,7 @@ static void test_read_file(void)
 	char out[1024] = "line1\n\nline2\nline3";
 	int n, err;
 	char **names = NULL;
-	char *want[] = { "line1", "line2", "line3" };
+	const char *want[] = { "line1", "line2", "line3" };
 	int i = 0;
 
 	EXPECT(fd > 0);
@@ -116,13 +116,14 @@ static void test_parse_names(void)
 
 static void test_names_equal(void)
 {
-	char *a[] = { "a", "b", "c", NULL };
-	char *b[] = { "a", "b", "d", NULL };
-	char *c[] = { "a", "b", NULL };
-
-	EXPECT(names_equal(a, a));
-	EXPECT(!names_equal(a, b));
-	EXPECT(!names_equal(a, c));
+	char a[] = "a", b[] = "b", c[] = "c", d[] = "d";
+	char *abc[] = { a, b, c, NULL };
+	char *abd[] = { a, b, d, NULL };
+	char *ab[] = { a, b, NULL };
+
+	EXPECT(names_equal(abc, abc));
+	EXPECT(!names_equal(abc, abd));
+	EXPECT(!names_equal(abc, ab));
 }
 
 static int write_test_ref(struct reftable_writer *wr, void *arg)
@@ -155,11 +156,12 @@ static void test_reftable_stack_add_one(void)
 	};
 	struct reftable_stack *st = NULL;
 	int err;
+	char head[] = "HEAD", master[] = "master";
 	struct reftable_ref_record ref = {
-		.refname = "HEAD",
+		.refname = head,
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = master,
 	};
 	struct reftable_ref_record dest = { NULL };
 	struct stat stat_result = { 0 };
@@ -215,17 +217,18 @@ static void test_reftable_stack_uptodate(void)
 	char *dir = get_tmp_dir(__LINE__);
 
 	int err;
+	char head[] = "HEAD", branch2[] = "branch2", master[] = "master";
 	struct reftable_ref_record ref1 = {
-		.refname = "HEAD",
+		.refname = head,
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = master,
 	};
 	struct reftable_ref_record ref2 = {
-		.refname = "branch2",
+		.refname = branch2,
 		.update_index = 2,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = master,
 	};
 
 
@@ -262,12 +265,12 @@ static void test_reftable_stack_transaction_api(void)
 	struct reftable_stack *st = NULL;
 	int err;
 	struct reftable_addition *add = NULL;
-
+	char head[] = "HEAD", master[] = "master";
 	struct reftable_ref_record ref = {
-		.refname = "HEAD",
+		.refname = head,
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = master,
 	};
 	struct reftable_ref_record dest = { NULL };
 
@@ -310,10 +313,11 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)
 	EXPECT_ERR(err);
 
 	for (i = 0; i <= n; i++) {
+		char master[] = "master";
 		struct reftable_ref_record ref = {
 			.update_index = reftable_stack_next_update_index(st),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = master,
 		};
 		char name[100];
 
@@ -355,8 +359,9 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)
 
 static void test_reftable_stack_auto_compaction_fails_gracefully(void)
 {
+	char master[] = "refs/meads/master";
 	struct reftable_ref_record ref = {
-		.refname = "refs/heads/master",
+		.refname = master,
 		.update_index = 1,
 		.value_type = REFTABLE_REF_VAL1,
 		.value.val1 = {0x01},
@@ -404,21 +409,21 @@ static int write_error(struct reftable_writer *wr, void *arg)
 static void test_reftable_stack_update_index_check(void)
 {
 	char *dir = get_tmp_dir(__LINE__);
-
 	struct reftable_write_options cfg = { 0 };
 	struct reftable_stack *st = NULL;
 	int err;
+	char name1[] = "name1", name2[] = "name2", master[] = "master";
 	struct reftable_ref_record ref1 = {
-		.refname = "name1",
+		.refname = name1,
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = master,
 	};
 	struct reftable_ref_record ref2 = {
-		.refname = "name2",
+		.refname = name2,
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = master,
 	};
 
 	err = reftable_new_stack(&st, dir, cfg);
@@ -560,8 +565,12 @@ static void test_reftable_stack_log_normalize(void)
 	};
 	struct reftable_stack *st = NULL;
 	char *dir = get_tmp_dir(__LINE__);
+	char branch[] = "branch";
+	char onetwomessage[] = "one\ntwo";
+	char onemessage[] = "one";
+	char twomessage[] = "two\n";
 	struct reftable_log_record input = {
-		.refname = "branch",
+		.refname = branch,
 		.update_index = 1,
 		.value_type = REFTABLE_LOG_UPDATE,
 		.value = {
@@ -582,11 +591,11 @@ static void test_reftable_stack_log_normalize(void)
 	err = reftable_new_stack(&st, dir, cfg);
 	EXPECT_ERR(err);
 
-	input.value.update.message = "one\ntwo";
+	input.value.update.message = onetwomessage;
 	err = reftable_stack_add(st, &write_test_log, &arg);
 	EXPECT(err == REFTABLE_API_ERROR);
 
-	input.value.update.message = "one";
+	input.value.update.message = onemessage;
 	err = reftable_stack_add(st, &write_test_log, &arg);
 	EXPECT_ERR(err);
 
@@ -594,7 +603,7 @@ static void test_reftable_stack_log_normalize(void)
 	EXPECT_ERR(err);
 	EXPECT(0 == strcmp(dest.value.update.message, "one\n"));
 
-	input.value.update.message = "two\n";
+	input.value.update.message = twomessage;
 	arg.update_index = 2;
 	err = reftable_stack_add(st, &write_test_log, &arg);
 	EXPECT_ERR(err);
@@ -691,15 +700,14 @@ static void test_reftable_stack_tombstone(void)
 static void test_reftable_stack_hash_id(void)
 {
 	char *dir = get_tmp_dir(__LINE__);
-
 	struct reftable_write_options cfg = { 0 };
 	struct reftable_stack *st = NULL;
 	int err;
-
+	char master[] = "master", target[] = "target";
 	struct reftable_ref_record ref = {
-		.refname = "master",
+		.refname = master,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "target",
+		.value.symref = target,
 		.update_index = 1,
 	};
 	struct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID };
@@ -874,12 +882,12 @@ static void test_reftable_stack_auto_compaction(void)
 	EXPECT_ERR(err);
 
 	for (i = 0; i < N; i++) {
-		char name[100];
+		char name[100], master[] = "master";
 		struct reftable_ref_record ref = {
 			.refname = name,
 			.update_index = reftable_stack_next_update_index(st),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = master,
 		};
 		snprintf(name, sizeof(name), "branch%04d", i);
 
@@ -910,10 +918,11 @@ static void test_reftable_stack_add_performs_auto_compaction(void)
 	EXPECT_ERR(err);
 
 	for (i = 0; i <= n; i++) {
+		char master[] = "master";
 		struct reftable_ref_record ref = {
 			.update_index = reftable_stack_next_update_index(st),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = master,
 		};
 
 		/*
@@ -959,12 +968,12 @@ static void test_reftable_stack_compaction_concurrent(void)
 	EXPECT_ERR(err);
 
 	for (i = 0; i < N; i++) {
-		char name[100];
+		char name[100], master[] = "master";
 		struct reftable_ref_record ref = {
 			.refname = name,
 			.update_index = reftable_stack_next_update_index(st1),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = master,
 		};
 		snprintf(name, sizeof(name), "branch%04d", i);
 
@@ -1009,12 +1018,12 @@ static void test_reftable_stack_compaction_concurrent_clean(void)
 	EXPECT_ERR(err);
 
 	for (i = 0; i < N; i++) {
-		char name[100];
+		char name[100], master[] = "master";
 		struct reftable_ref_record ref = {
 			.refname = name,
 			.update_index = reftable_stack_next_update_index(st1),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = master,
 		};
 		snprintf(name, sizeof(name), "branch%04d", i);
 
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 30, 2024, 12:50 UTC · re: Patrick Steinhardt · lore

[PATCH v2 03/19] global: convert intentionally-leaking config strings to consts

There are multiple cases where we intentionally leak config strings:
  - `struct gpg_format` is used to track programs that can be used for
    signing commits, either via gpg(1), gpgsm(1) or ssh-keygen(1). The
    user can override the commands via several config variables. As the
    array is populated once, only, and will never be free'd, it is fine
    to treat the program as a quasi-constant.
  - `struct ll_merge_driver` is used to track merge drivers. Same as
    with the GPG format, these drivers are populated once and then
    reused. Its data is never free'd, either.
  - `struct userdiff_funcname` and `struct userdiff_driver` can be
    configured via `diff.<driver>.*` to add additional drivers. Again,
    these have a global lifetime and are never free'd.

All of these are intentionally kept alive and never free'd. Furthermore, all of these are being assigned both string constants in some places, and allocated strings in other places. This will cause warnings once we enable `-Wwrite-strings`, so let's mark the respective fields as `const char *` and cast away the constness when assigning those values.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 gpg-interface.c |  4 ++--
 merge-ll.c      | 11 ++++++++---
 userdiff.c      | 10 +++++-----
 userdiff.h      | 12 ++++++------
 4 files changed, 21 insertions(+), 16 deletions(-)
Show changes to 4 files +21 −16

gpg-interface.c, merge-ll.c, userdiff.c, userdiff.h

diff --git a/gpg-interface.c b/gpg-interface.c
index 71a9382a61..5c824aeb25 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -34,7 +34,7 @@ static enum signature_trust_level configured_min_trust_level = TRUST_UNDEFINED;
 
 struct gpg_format {
 	const char *name;
-	char *program;
+	const char *program;
 	const char **verify_args;
 	const char **sigs;
 	int (*verify_signed_buffer)(struct signature_check *sigc,
@@ -783,7 +783,7 @@ static int git_gpg_config(const char *var, const char *value,
 
 	if (fmtname) {
 		fmt = get_format_by_name(fmtname);
-		return git_config_string(&fmt->program, var, value);
+		return git_config_string((char **) &fmt->program, var, value);
 	}
 
 	return 0;
diff --git a/merge-ll.c b/merge-ll.c
index e29b15fa4a..180c19df67 100644
--- a/merge-ll.c
+++ b/merge-ll.c
@@ -27,7 +27,7 @@ typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *,
 
 struct ll_merge_driver {
 	const char *name;
-	char *description;
+	const char *description;
 	ll_merge_fn fn;
 	char *recursive;
 	struct ll_merge_driver *next;
@@ -304,8 +304,13 @@ static int read_merge_config(const char *var, const char *value,
 		ll_user_merge_tail = &(fn->next);
 	}
 
-	if (!strcmp("name", key))
-		return git_config_string(&fn->description, var, value);
+	if (!strcmp("name", key)) {
+		/*
+		 * The description is leaking, but that's okay as we want to
+		 * keep around the merge drivers anyway.
+		 */
+		return git_config_string((char **) &fn->description, var, value);
+	}
 
 	if (!strcmp("driver", key)) {
 		if (!value)
diff --git a/userdiff.c b/userdiff.c
index 82bc76b910..371032a413 100644
--- a/userdiff.c
+++ b/userdiff.c
@@ -399,7 +399,7 @@ static struct userdiff_driver *userdiff_find_by_namelen(const char *name, size_t
 static int parse_funcname(struct userdiff_funcname *f, const char *k,
 		const char *v, int cflags)
 {
-	if (git_config_string(&f->pattern, k, v) < 0)
+	if (git_config_string((char **) &f->pattern, k, v) < 0)
 		return -1;
 	f->cflags = cflags;
 	return 0;
@@ -445,15 +445,15 @@ int userdiff_config(const char *k, const char *v)
 	if (!strcmp(type, "binary"))
 		return parse_tristate(&drv->binary, k, v);
 	if (!strcmp(type, "command"))
-		return git_config_string(&drv->external, k, v);
+		return git_config_string((char **) &drv->external, k, v);
 	if (!strcmp(type, "textconv"))
-		return git_config_string(&drv->textconv, k, v);
+		return git_config_string((char **) &drv->textconv, k, v);
 	if (!strcmp(type, "cachetextconv"))
 		return parse_bool(&drv->textconv_want_cache, k, v);
 	if (!strcmp(type, "wordregex"))
-		return git_config_string(&drv->word_regex, k, v);
+		return git_config_string((char **) &drv->word_regex, k, v);
 	if (!strcmp(type, "algorithm"))
-		return git_config_string(&drv->algorithm, k, v);
+		return git_config_string((char **) &drv->algorithm, k, v);
 
 	return 0;
 }
diff --git a/userdiff.h b/userdiff.h
index cc8e5abfef..d726804c3e 100644
--- a/userdiff.h
+++ b/userdiff.h
@@ -7,19 +7,19 @@ struct index_state;
 struct repository;
 
 struct userdiff_funcname {
-	char *pattern;
+	const char *pattern;
 	int cflags;
 };
 
 struct userdiff_driver {
 	const char *name;
-	char *external;
-	char *algorithm;
+	const char *external;
+	const char *algorithm;
 	int binary;
 	struct userdiff_funcname funcname;
-	char *word_regex;
-	char *word_regex_multi_byte;
-	char *textconv;
+	const char *word_regex;
+	const char *word_regex_multi_byte;
+	const char *textconv;
 	struct notes_cache *textconv_cache;
 	int textconv_want_cache;
 };
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 30, 2024, 12:50 UTC · re: Patrick Steinhardt · lore

[PATCH v2 04/19] compat/win32: fix const-correctness with string constants

Adjust various places in our Win32 compatibility layer where we are not assigning string constants to `const char *` variables.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 compat/basename.c | 15 +++++++++++++--
 compat/mingw.c    | 25 +++++++++++++------------
 compat/winansi.c  |  2 +-
 3 files changed, 27 insertions(+), 15 deletions(-)
Show changes to 3 files +27 −15

compat/basename.c, compat/mingw.c, compat/winansi.c

diff --git a/compat/basename.c b/compat/basename.c
index 96bd9533b4..c3c9d65fac 100644
--- a/compat/basename.c
+++ b/compat/basename.c
@@ -1,6 +1,13 @@
 #include "../git-compat-util.h"
 #include "../strbuf.h"
 
+/*
+ * Both basename(3P) and dirname(3P) are mis-specified because they return a
+ * non-constant pointer even though it is specified that they may return a
+ * pointer to internal memory. This variable here is a result of that.
+ */
+static char current_directory[] = ".";
+
 /* Adapted from libiberty's basename.c.  */
 char *gitbasename (char *path)
 {
@@ -10,7 +17,7 @@ char *gitbasename (char *path)
 		skip_dos_drive_prefix(&path);
 
 	if (!path || !*path)
-		return ".";
+		return current_directory;
 
 	for (base = path; *path; path++) {
 		if (!is_dir_sep(*path))
@@ -33,8 +40,12 @@ char *gitdirname(char *path)
 	char *p = path, *slash = NULL, c;
 	int dos_drive_prefix;
 
+	/*
+	 * Same here, dirname(3P) is broken because it returns a non-constant
+	 * pointer that may point to internal memory.
+	 */
 	if (!p)
-		return ".";
+		return current_directory;
 
 	if ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p)
 		goto dot;
diff --git a/compat/mingw.c b/compat/mingw.c
index 6b06ea540f..60f0986f76 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -2257,6 +2257,7 @@ struct passwd *getpwuid(int uid)
 {
 	static unsigned initialized;
 	static char user_name[100];
+	static char unknown[] = "unknown";
 	static struct passwd *p;
 	wchar_t buf[100];
 	DWORD len;
@@ -2279,7 +2280,7 @@ struct passwd *getpwuid(int uid)
 	p->pw_name = user_name;
 	p->pw_gecos = get_extended_user_info(NameDisplay);
 	if (!p->pw_gecos)
-		p->pw_gecos = "unknown";
+		p->pw_gecos = unknown;
 	p->pw_dir = NULL;
 
 	initialized = 1;
@@ -2800,16 +2801,16 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)
 			strbuf_addf(report, "'%s' is on a file system that does "
 				    "not record ownership\n", path);
 		} else if (report) {
-			LPSTR str1, str2, str3, str4, to_free1 = NULL,
-			    to_free3 = NULL, to_local_free2 = NULL,
-			    to_local_free4 = NULL;
+			PCSTR str1, str2, str3, str4;
+			LPSTR to_free1 = NULL, to_free3 = NULL,
+			    to_local_free2 = NULL, to_local_free4 = NULL;
 
-			if (user_sid_to_user_name(sid, &str1))
-				to_free1 = str1;
+			if (user_sid_to_user_name(sid, &to_free1))
+				str1 = to_free1;
 			else
 				str1 = "(inconvertible)";
-			if (ConvertSidToStringSidA(sid, &str2))
-				to_local_free2 = str2;
+			if (ConvertSidToStringSidA(sid, &to_local_free2))
+				str2 = to_local_free2;
 			else
 				str2 = "(inconvertible)";
 
@@ -2822,13 +2823,13 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)
 				str4 = "(invalid)";
 			} else {
 				if (user_sid_to_user_name(current_user_sid,
-							  &str3))
-					to_free3 = str3;
+							  &to_free3))
+					str3 = to_free3;
 				else
 					str3 = "(inconvertible)";
 				if (ConvertSidToStringSidA(current_user_sid,
-							   &str4))
-					to_local_free4 = str4;
+							   &to_local_free4))
+					str4 = to_local_free4;
 				else
 					str4 = "(inconvertible)";
 			}
diff --git a/compat/winansi.c b/compat/winansi.c
index f83610f684..575813bde8 100644
--- a/compat/winansi.c
+++ b/compat/winansi.c
@@ -139,7 +139,7 @@ static void write_console(unsigned char *str, size_t len)
 	/* convert utf-8 to utf-16 */
 	int wlen = xutftowcsn(wbuf, (char*) str, ARRAY_SIZE(wbuf), len);
 	if (wlen < 0) {
-		wchar_t *err = L"[invalid]";
+		const wchar_t *err = L"[invalid]";
 		WriteConsoleW(console, err, wcslen(err), &dummy, NULL);
 		return;
 	}
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 30, 2024, 12:50 UTC · re: Patrick Steinhardt · lore

[PATCH v2 05/19] refspec: remove global tag refspec structure

We have a global tag refspec structure that is used by both git-clone(1) and git-fetch(1). Initialization fo the structure will break once we enable `-Wwrite-strings`, even though the breakage is harmless. While we could just add casts, the structure isn't really required in the first place as we can simply initialize the structures at the respective callsites.

Refactor the code accordingly.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c |  8 ++++++--
 builtin/fetch.c | 11 ++++++++---
 refspec.c       | 13 -------------
 refspec.h       |  1 -
 4 files changed, 14 insertions(+), 19 deletions(-)
Show changes to 4 files +14 −19

builtin/clone.c, builtin/fetch.c, refspec.c, refspec.h

diff --git a/builtin/clone.c b/builtin/clone.c
index 92ab7d7165..bde1d284a2 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -523,6 +523,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
 	struct ref *head = copy_ref(find_ref_by_name(refs, "HEAD"));
 	struct ref *local_refs = head;
 	struct ref **tail = head ? &head->next : &local_refs;
+	struct refspec_item tag_refspec;
+
+	refspec_item_init(&tag_refspec, TAG_REFSPEC, 0);
 
 	if (option_single_branch) {
 		struct ref *remote_head = NULL;
@@ -545,7 +548,7 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
 					      &tail, 0);
 
 			/* if --branch=tag, pull the requested tag explicitly */
-			get_fetch_map(remote_head, tag_refspec, &tail, 0);
+			get_fetch_map(remote_head, &tag_refspec, &tail, 0);
 		}
 		free_refs(remote_head);
 	} else {
@@ -555,8 +558,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
 	}
 
 	if (!option_mirror && !option_single_branch && !option_no_tags)
-		get_fetch_map(refs, tag_refspec, &tail, 0);
+		get_fetch_map(refs, &tag_refspec, &tail, 0);
 
+	refspec_item_clear(&tag_refspec);
 	return local_refs;
 }
 
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 75255dc600..06b60867f5 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -582,11 +582,16 @@ static struct ref *get_ref_map(struct remote *remote,
 		}
 	}
 
-	if (tags == TAGS_SET)
+	if (tags == TAGS_SET) {
+		struct refspec_item tag_refspec;
+
 		/* also fetch all tags */
-		get_fetch_map(remote_refs, tag_refspec, &tail, 0);
-	else if (tags == TAGS_DEFAULT && *autotags)
+		refspec_item_init(&tag_refspec, TAG_REFSPEC, 0);
+		get_fetch_map(remote_refs, &tag_refspec, &tail, 0);
+		refspec_item_clear(&tag_refspec);
+	} else if (tags == TAGS_DEFAULT && *autotags) {
 		find_non_local_tags(remote_refs, NULL, &ref_map, &tail);
+	}
 
 	/* Now append any refs to be updated opportunistically: */
 	*tail = orefs;
diff --git a/refspec.c b/refspec.c
index d60932f4de..1df5de6c2f 100644
--- a/refspec.c
+++ b/refspec.c
@@ -7,19 +7,6 @@
 #include "refspec.h"
 #include "strbuf.h"
 
-static struct refspec_item s_tag_refspec = {
-	.force = 0,
-	.pattern = 1,
-	.matching = 0,
-	.exact_sha1 = 0,
-	.negative = 0,
-	.src = "refs/tags/*",
-	.dst = "refs/tags/*",
-};
-
-/* See TAG_REFSPEC for the string version */
-const struct refspec_item *tag_refspec = &s_tag_refspec;
-
 /*
  * Parses the provided refspec 'refspec' and populates the refspec_item 'item'.
  * Returns 1 if successful and 0 if the refspec is invalid.
diff --git a/refspec.h b/refspec.h
index 8c0c446993..754be45cee 100644
--- a/refspec.h
+++ b/refspec.h
@@ -2,7 +2,6 @@
 #define REFSPEC_H
 
 #define TAG_REFSPEC "refs/tags/*:refs/tags/*"
-extern const struct refspec_item *tag_refspec;
 
 /**
  * A struct refspec_item holds the parsed interpretation of a refspec.  If it
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 30, 2024, 12:50 UTC · re: Patrick Steinhardt · lore

[PATCH v2 06/19] http: do not assign string constant to non-const field

In `write_accept_language()`, we put all acceptable languages into an array. While all entries in that array are allocated strings, the final entry in that array is a string constant. This is fine because we explicitly skip over the last entry when freeing the array, but will cause warnings once we enable `-Wwrite-strings`.

Adapt the code to also allocate the final entry.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 http.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)
Show changes to http.c +2 −3
diff --git a/http.c b/http.c
index 67cc47d28f..2dea2d03da 100644
--- a/http.c
+++ b/http.c
@@ -1974,7 +1974,7 @@ static void write_accept_language(struct strbuf *buf)
 
 		/* add '*' */
 		REALLOC_ARRAY(language_tags, num_langs + 1);
-		language_tags[num_langs++] = "*"; /* it's OK; this won't be freed */
+		language_tags[num_langs++] = xstrdup("*");
 
 		/* compute decimal_places */
 		for (max_q = 1, decimal_places = 0;
@@ -2004,8 +2004,7 @@ static void write_accept_language(struct strbuf *buf)
 		}
 	}
 
-	/* free language tags -- last one is a static '*' */
-	for (i = 0; i < num_langs - 1; i++)
+	for (i = 0; i < num_langs; i++)
 		free(language_tags[i]);
 	free(language_tags);
 }
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 30, 2024, 12:51 UTC · re: Patrick Steinhardt · lore

[PATCH v2 07/19] line-log: always allocate the output prefix

The returned string by `output_prefix()` is sometimes a string constant and sometimes an allocated string. This has been fine until now because we always leak the allocated strings, and thus we never tried to free the string constant.

Fix the code to always return an allocated string and free the returned value at all callsites.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 line-log.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)
Show changes to line-log.c +11 −7
diff --git a/line-log.c b/line-log.c
index d9bf2c8120..9a298209d0 100644
--- a/line-log.c
+++ b/line-log.c
@@ -899,14 +899,12 @@ static void print_line(const char *prefix, char first,
 
 static char *output_prefix(struct diff_options *opt)
 {
-	char *prefix = "";
-
 	if (opt->output_prefix) {
 		struct strbuf *sb = opt->output_prefix(opt, opt->output_prefix_data);
-		prefix = sb->buf;
+		return sb->buf;
+	} else {
+		return xstrdup("");
 	}
-
-	return prefix;
 }
 
 static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *range)
@@ -927,7 +925,7 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang
 	const char *c_context = diff_get_color(opt->use_color, DIFF_CONTEXT);
 
 	if (!pair || !diff)
-		return;
+		goto out;
 
 	if (pair->one->oid_valid)
 		fill_line_ends(rev->diffopt.repo, pair->one, &p_lines, &p_ends);
@@ -1002,8 +1000,10 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang
 				   c_context, c_reset, opt->file);
 	}
 
+out:
 	free(p_ends);
 	free(t_ends);
+	free(prefix);
 }
 
 /*
@@ -1012,7 +1012,11 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang
  */
 static void dump_diff_hacky(struct rev_info *rev, struct line_log_data *range)
 {
-	fprintf(rev->diffopt.file, "%s\n", output_prefix(&rev->diffopt));
+	char *prefix = output_prefix(&rev->diffopt);
+
+	fprintf(rev->diffopt.file, "%s\n", prefix);
+	free(prefix);
+
 	while (range) {
 		dump_diff_hacky_one(rev, range);
 		range = range->next;
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 30, 2024, 12:51 UTC · re: Patrick Steinhardt · lore

[PATCH v2 08/19] object-file: make `buf` parameter of `index_mem()` a constant

The `buf` parameter of `index_mem()` is a non-constant string. This will break once we enable `-Wwrite-strings` because we also pass constants from at least one callsite.

Adapt the parameter to be a constant. As we cannot free the buffer without casting now, this also requires us to move the lifetime of the nested buffer around.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 object-file.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)
Show changes to object-file.c +7 −7
diff --git a/object-file.c b/object-file.c
index c9e374e57e..46ea00ac46 100644
--- a/object-file.c
+++ b/object-file.c
@@ -2483,12 +2483,13 @@ static int hash_format_check_report(struct fsck_options *opts UNUSED,
 }
 
 static int index_mem(struct index_state *istate,
-		     struct object_id *oid, void *buf, size_t size,
+		     struct object_id *oid,
+		     const void *buf, size_t size,
 		     enum object_type type,
 		     const char *path, unsigned flags)
 {
+	struct strbuf nbuf = STRBUF_INIT;
 	int ret = 0;
-	int re_allocated = 0;
 	int write_object = flags & HASH_WRITE_OBJECT;
 
 	if (!type)
@@ -2498,11 +2499,10 @@ static int index_mem(struct index_state *istate,
 	 * Convert blobs to git internal format
 	 */
 	if ((type == OBJ_BLOB) && path) {
-		struct strbuf nbuf = STRBUF_INIT;
 		if (convert_to_git(istate, path, buf, size, &nbuf,
 				   get_conv_flags(flags))) {
-			buf = strbuf_detach(&nbuf, &size);
-			re_allocated = 1;
+			buf = nbuf.buf;
+			size = nbuf.len;
 		}
 	}
 	if (flags & HASH_FORMAT_CHECK) {
@@ -2519,8 +2519,8 @@ static int index_mem(struct index_state *istate,
 		ret = write_object_file(buf, size, type, oid);
 	else
 		hash_object_file(the_hash_algo, buf, size, type, oid);
-	if (re_allocated)
-		free(buf);
+
+	strbuf_release(&nbuf);
 	return ret;
 }
 
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 30, 2024, 12:51 UTC · re: Patrick Steinhardt · lore

[PATCH v2 09/19] parse-options: cast long name for OPTION_ALIAS

We assign the long name for OPTION_ALIAS options to a non-constant value field. We know that the variable will never be written to, but this will cause warnings once we enable `-Wwrite-strings`.

Cast away the constness to be prepared for this change.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 parse-options.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to parse-options.h +1 −1
diff --git a/parse-options.h b/parse-options.h
index bd62e20268..ae15342390 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -355,7 +355,7 @@ struct option {
 	.type = OPTION_ALIAS, \
 	.short_name = (s), \
 	.long_name = (l), \
-	.value = (source_long_name), \
+	.value = (char *)(source_long_name), \
 }
 
 #define OPT_SUBCOMMAND_F(l, v, fn, f) { \
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 30, 2024, 12:51 UTC · re: Patrick Steinhardt · lore

[PATCH v2 10/19] send-pack: always allocate receive status

In `receive_status()`, we record the reason why ref updates have been rejected by the remote via the `remote_status`. But while we allocate the assigned string when a reason was given, we assign a string constant when no reason was given.

This has been working fine so far due to two reasons:
  - We don't ever free the refs in git-send-pack(1)'
  - Remotes always give a reason, at least as implemented by Git proper.

Adapt the code to always allocate the receive status string and free the refs.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/send-pack.c | 2 ++
 send-pack.c         | 2 +-
 2 files changed, 3 insertions(+), 1 deletion(-)
Show changes to 2 files +3 −1

builtin/send-pack.c, send-pack.c

diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index 3df9eaad09..17cae6bbbd 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -336,5 +336,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
 		/* stable plumbing output; do not modify or localize */
 		fprintf(stderr, "Everything up-to-date\n");
 
+	free_refs(remote_refs);
+	free_refs(local_refs);
 	return ret;
 }
diff --git a/send-pack.c b/send-pack.c
index 37f59d4f66..88e96d000b 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -259,7 +259,7 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)
 			if (p)
 				hint->remote_status = xstrdup(p);
 			else
-				hint->remote_status = "failed";
+				hint->remote_status = xstrdup("failed");
 		} else {
 			hint->status = REF_STATUS_OK;
 			hint->remote_status = xstrdup_or_null(p);
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 30, 2024, 12:51 UTC · re: Patrick Steinhardt · lore

[PATCH v2 11/19] remote-curl: avoid assigning string constant to non-const variable

When processing remote options, we split the option line into two by searching for a space. If there is one, we replace the space with '\0', otherwise we implicitly assume that the value is "true" and thus assign a string constant.

As the return value of strchr(3P) weirdly enough is a `char *` even though it gets a `const char *` as input, the assigned-to variable also is a non-constant. This is fine though because the argument is in fact an allocated string, and thus we are allowed to modify it. But this will break once we enable `-Wwrite-strings`.

Refactor the code stop splitting the fields with '\0' altogether. Instead, we can pass the length of the option name to `set_option()` and then use strncmp(3P) instead of strcmp(3P).

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 remote-curl.c | 53 ++++++++++++++++++++++++++-------------------------
 1 file changed, 27 insertions(+), 26 deletions(-)
Show changes to remote-curl.c +27 −26
diff --git a/remote-curl.c b/remote-curl.c
index cae98384da..d0f767df8e 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -58,9 +58,9 @@ struct options {
 static struct options options;
 static struct string_list cas_options = STRING_LIST_INIT_DUP;
 
-static int set_option(const char *name, const char *value)
+static int set_option(const char *name, size_t namelen, const char *value)
 {
-	if (!strcmp(name, "verbosity")) {
+	if (!strncmp(name, "verbosity", namelen)) {
 		char *end;
 		int v = strtol(value, &end, 10);
 		if (value == end || *end)
@@ -68,7 +68,7 @@ static int set_option(const char *name, const char *value)
 		options.verbosity = v;
 		return 0;
 	}
-	else if (!strcmp(name, "progress")) {
+	else if (!strncmp(name, "progress", namelen)) {
 		if (!strcmp(value, "true"))
 			options.progress = 1;
 		else if (!strcmp(value, "false"))
@@ -77,7 +77,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "depth")) {
+	else if (!strncmp(name, "depth", namelen)) {
 		char *end;
 		unsigned long v = strtoul(value, &end, 10);
 		if (value == end || *end)
@@ -85,15 +85,15 @@ static int set_option(const char *name, const char *value)
 		options.depth = v;
 		return 0;
 	}
-	else if (!strcmp(name, "deepen-since")) {
+	else if (!strncmp(name, "deepen-since", namelen)) {
 		options.deepen_since = xstrdup(value);
 		return 0;
 	}
-	else if (!strcmp(name, "deepen-not")) {
+	else if (!strncmp(name, "deepen-not", namelen)) {
 		string_list_append(&options.deepen_not, value);
 		return 0;
 	}
-	else if (!strcmp(name, "deepen-relative")) {
+	else if (!strncmp(name, "deepen-relative", namelen)) {
 		if (!strcmp(value, "true"))
 			options.deepen_relative = 1;
 		else if (!strcmp(value, "false"))
@@ -102,7 +102,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "followtags")) {
+	else if (!strncmp(name, "followtags", namelen)) {
 		if (!strcmp(value, "true"))
 			options.followtags = 1;
 		else if (!strcmp(value, "false"))
@@ -111,7 +111,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "dry-run")) {
+	else if (!strncmp(name, "dry-run", namelen)) {
 		if (!strcmp(value, "true"))
 			options.dry_run = 1;
 		else if (!strcmp(value, "false"))
@@ -120,7 +120,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "check-connectivity")) {
+	else if (!strncmp(name, "check-connectivity", namelen)) {
 		if (!strcmp(value, "true"))
 			options.check_self_contained_and_connected = 1;
 		else if (!strcmp(value, "false"))
@@ -129,7 +129,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "cas")) {
+	else if (!strncmp(name, "cas", namelen)) {
 		struct strbuf val = STRBUF_INIT;
 		strbuf_addstr(&val, "--force-with-lease=");
 		if (*value != '"')
@@ -139,7 +139,7 @@ static int set_option(const char *name, const char *value)
 		string_list_append(&cas_options, val.buf);
 		strbuf_release(&val);
 		return 0;
-	} else if (!strcmp(name, TRANS_OPT_FORCE_IF_INCLUDES)) {
+	} else if (!strncmp(name, TRANS_OPT_FORCE_IF_INCLUDES, namelen)) {
 		if (!strcmp(value, "true"))
 			options.force_if_includes = 1;
 		else if (!strcmp(value, "false"))
@@ -147,7 +147,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "cloning")) {
+	} else if (!strncmp(name, "cloning", namelen)) {
 		if (!strcmp(value, "true"))
 			options.cloning = 1;
 		else if (!strcmp(value, "false"))
@@ -155,7 +155,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "update-shallow")) {
+	} else if (!strncmp(name, "update-shallow", namelen)) {
 		if (!strcmp(value, "true"))
 			options.update_shallow = 1;
 		else if (!strcmp(value, "false"))
@@ -163,7 +163,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "pushcert")) {
+	} else if (!strncmp(name, "pushcert", namelen)) {
 		if (!strcmp(value, "true"))
 			options.push_cert = SEND_PACK_PUSH_CERT_ALWAYS;
 		else if (!strcmp(value, "false"))
@@ -173,7 +173,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "atomic")) {
+	} else if (!strncmp(name, "atomic", namelen)) {
 		if (!strcmp(value, "true"))
 			options.atomic = 1;
 		else if (!strcmp(value, "false"))
@@ -181,7 +181,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "push-option")) {
+	} else if (!strncmp(name, "push-option", namelen)) {
 		if (*value != '"')
 			string_list_append(&options.push_options, value);
 		else {
@@ -192,7 +192,7 @@ static int set_option(const char *name, const char *value)
 						 strbuf_detach(&unquoted, NULL));
 		}
 		return 0;
-	} else if (!strcmp(name, "family")) {
+	} else if (!strncmp(name, "family", namelen)) {
 		if (!strcmp(value, "ipv4"))
 			git_curl_ipresolve = CURL_IPRESOLVE_V4;
 		else if (!strcmp(value, "ipv6"))
@@ -202,16 +202,16 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "from-promisor")) {
+	} else if (!strncmp(name, "from-promisor", namelen)) {
 		options.from_promisor = 1;
 		return 0;
-	} else if (!strcmp(name, "refetch")) {
+	} else if (!strncmp(name, "refetch", namelen)) {
 		options.refetch = 1;
 		return 0;
-	} else if (!strcmp(name, "filter")) {
+	} else if (!strncmp(name, "filter", namelen)) {
 		options.filter = xstrdup(value);
 		return 0;
-	} else if (!strcmp(name, "object-format")) {
+	} else if (!strncmp(name, "object-format", namelen)) {
 		options.object_format = 1;
 		if (strcmp(value, "true"))
 			die(_("unknown value for object-format: %s"), value);
@@ -1588,15 +1588,16 @@ int cmd_main(int argc, const char **argv)
 			parse_push(&buf);
 
 		} else if (skip_prefix(buf.buf, "option ", &arg)) {
-			char *value = strchr(arg, ' ');
+			const char *value = strchrnul(arg, ' ');
+			size_t arglen = value - arg;
 			int result;
 
-			if (value)
-				*value++ = '\0';
+			if (*value)
+				value++; /* skip over SP */
 			else
 				value = "true";
 
-			result = set_option(arg, value);
+			result = set_option(arg, arglen, value);
 			if (!result)
 				printf("ok\n");
 			else if (result < 0)
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 30, 2024, 12:51 UTC · re: Patrick Steinhardt · lore

[PATCH v2 12/19] revision: always store allocated strings in output encoding

The `git_log_output_encoding` variable can be set via the `--encoding=` option. When doing so, we conditionally either assign it to the passed value, or if the value is "none" we assign it the empty string. Depending on which of the both code paths we pick though, the variable may end up being assigned either an allocated string or a string constant.

This is somewhat risky and may easily lead to bugs when a different code path may want to reassign a new value to it, freeing the previous value. We already to this when parsing the "i18n.logoutputencoding" config in `git_default_i18n_config()`. But because the config is typically parsed before we parse command line options this has been fine so far.

Regardless of that, safeguard the code such that the variable always contains an allocated string. While at it, also free the old value in case there was any to plug a potential memory leak.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 revision.c             | 3 ++-
 t/t3900-i18n-commit.sh | 1 +
 t/t3901-i18n-patch.sh  | 1 +
 3 files changed, 4 insertions(+), 1 deletion(-)
Show changes to 3 files +4 −1

revision.c, t/t3900-i18n-commit.sh, t/t3901-i18n-patch.sh

diff --git a/revision.c b/revision.c
index 7ddf0f151a..2ee6886078 100644
--- a/revision.c
+++ b/revision.c
@@ -2650,10 +2650,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 	} else if (!strcmp(arg, "--invert-grep")) {
 		revs->grep_filter.no_body_match = 1;
 	} else if ((argcount = parse_long_opt("encoding", argv, &optarg))) {
+		free(git_log_output_encoding);
 		if (strcmp(optarg, "none"))
 			git_log_output_encoding = xstrdup(optarg);
 		else
-			git_log_output_encoding = "";
+			git_log_output_encoding = xstrdup("");
 		return argcount;
 	} else if (!strcmp(arg, "--reverse")) {
 		revs->reverse ^= 1;
diff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh
index f27d09cfd9..db7b403bc1 100755
--- a/t/t3900-i18n-commit.sh
+++ b/t/t3900-i18n-commit.sh
@@ -5,6 +5,7 @@
 
 test_description='commit and log output encodings'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 compare_with () {
diff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh
index 4b37f78829..5f0b9afc3f 100755
--- a/t/t3901-i18n-patch.sh
+++ b/t/t3901-i18n-patch.sh
@@ -8,6 +8,7 @@ test_description='i18n settings and format-patch | am pipe'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 check_encoding () {
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 30, 2024, 12:51 UTC · re: Patrick Steinhardt · lore

[PATCH v2 13/19] mailmap: always store allocated strings in mailmap blob

Same as with the preceding commit, the `git_mailmap_blob` may sometimes contain an allocated string and sometimes it may contain a string constant. This is risky and can easily lead to bugs in case the variable is getting re-assigned, where the code may then try to free the previous value to avoid memory leaks.

Safeguard the code by always storing allocated strings in the variable.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 mailmap.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to mailmap.c +1 −1
diff --git a/mailmap.c b/mailmap.c
index b2efe29b3d..3d1e092fef 100644
--- a/mailmap.c
+++ b/mailmap.c
@@ -216,7 +216,7 @@ int read_mailmap(struct string_list *map)
 	map->cmp = namemap_cmp;
 
 	if (!git_mailmap_blob && is_bare_repository())
-		git_mailmap_blob = "HEAD:.mailmap";
+		git_mailmap_blob = xstrdup("HEAD:.mailmap");
 
 	if (!startup_info->have_repository || !is_bare_repository())
 		err |= read_mailmap_file(map, ".mailmap",
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 30, 2024, 12:51 UTC · re: Patrick Steinhardt · lore

[PATCH v2 14/19] imap-send: drop global `imap_server_conf` variable

In "imap-send.c", we have a global `sturct imap_server_conf` variable that keeps track of the configuration of the IMAP server. This variable is being populated mostly via the Git configuration.

Refactor the code to allocate the structure on the stack instead of having it globally. This change allows us to track its lifetime more closely.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 imap-send.c | 57 ++++++++++++++++++++++++++++-------------------------
 1 file changed, 30 insertions(+), 27 deletions(-)
Show changes to imap-send.c +30 −27
diff --git a/imap-send.c b/imap-send.c
index 8b723b34a5..67a7a6c456 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -82,10 +82,6 @@ struct imap_server_conf {
 	char *auth_method;
 };
 
-static struct imap_server_conf server = {
-	.ssl_verify = 1,
-};
-
 struct imap_socket {
 	int fd[2];
 	SSL *ssl;
@@ -110,6 +106,7 @@ struct imap {
 };
 
 struct imap_store {
+	const struct imap_server_conf *cfg;
 	/* currently open mailbox */
 	const char *name; /* foreign! maybe preset? */
 	int uidvalidity;
@@ -194,8 +191,8 @@ static void socket_perror(const char *func, struct imap_socket *sock, int ret)
 
 #ifdef NO_OPENSSL
 static int ssl_socket_connect(struct imap_socket *sock UNUSED,
-			      int use_tls_only UNUSED,
-			      int verify UNUSED)
+			      const struct imap_server_conf *cfg,
+			      int use_tls_only UNUSED)
 {
 	fprintf(stderr, "SSL requested but SSL support not compiled in\n");
 	return -1;
@@ -250,7 +247,9 @@ static int verify_hostname(X509 *cert, const char *hostname)
 		     cname, hostname);
 }
 
-static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int verify)
+static int ssl_socket_connect(struct imap_socket *sock,
+			      const struct imap_server_conf *cfg,
+			      int use_tls_only)
 {
 #if (OPENSSL_VERSION_NUMBER >= 0x10000000L)
 	const SSL_METHOD *meth;
@@ -279,7 +278,7 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
 	if (use_tls_only)
 		SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
 
-	if (verify)
+	if (cfg->ssl_verify)
 		SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
 
 	if (!SSL_CTX_set_default_verify_paths(ctx)) {
@@ -306,9 +305,9 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
 	 * OpenSSL does not document this function, but the implementation
 	 * returns 1 on success, 0 on failure after calling SSLerr().
 	 */
-	ret = SSL_set_tlsext_host_name(sock->ssl, server.host);
+	ret = SSL_set_tlsext_host_name(sock->ssl, cfg->host);
 	if (ret != 1)
-		warning("SSL_set_tlsext_host_name(%s) failed.", server.host);
+		warning("SSL_set_tlsext_host_name(%s) failed.", cfg->host);
 #endif
 
 	ret = SSL_connect(sock->ssl);
@@ -317,12 +316,12 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
 		return -1;
 	}
 
-	if (verify) {
+	if (cfg->ssl_verify) {
 		/* make sure the hostname matches that of the certificate */
 		cert = SSL_get_peer_certificate(sock->ssl);
 		if (!cert)
 			return error("unable to get peer certificate.");
-		if (verify_hostname(cert, server.host) < 0)
+		if (verify_hostname(cert, cfg->host) < 0)
 			return -1;
 	}
 
@@ -895,7 +894,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)
 	int ret;
 	char *response;
 
-	response = cram(prompt, server.user, server.pass);
+	response = cram(prompt, ctx->cfg->user, ctx->cfg->pass);
 
 	ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));
 	if (ret != strlen(response))
@@ -935,6 +934,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
 
 	CALLOC_ARRAY(ctx, 1);
 
+	ctx->cfg = srvc;
 	ctx->imap = CALLOC_ARRAY(imap, 1);
 	imap->buf.sock.fd[0] = imap->buf.sock.fd[1] = -1;
 	imap->in_progress_append = &imap->in_progress;
@@ -1035,7 +1035,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
 		imap->buf.sock.fd[1] = dup(s);
 
 		if (srvc->use_ssl &&
-		    ssl_socket_connect(&imap->buf.sock, 0, srvc->ssl_verify)) {
+		    ssl_socket_connect(&imap->buf.sock, srvc, 0)) {
 			close(s);
 			goto bail;
 		}
@@ -1068,8 +1068,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
 		if (!srvc->use_ssl && CAP(STARTTLS)) {
 			if (imap_exec(ctx, NULL, "STARTTLS") != RESP_OK)
 				goto bail;
-			if (ssl_socket_connect(&imap->buf.sock, 1,
-					       srvc->ssl_verify))
+			if (ssl_socket_connect(&imap->buf.sock, srvc, 1))
 				goto bail;
 			/* capabilities may have changed, so get the new capabilities */
 			if (imap_exec(ctx, NULL, "CAPABILITY") != RESP_OK)
@@ -1299,23 +1298,24 @@ static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, int *ofs)
 static int git_imap_config(const char *var, const char *val,
 			   const struct config_context *ctx, void *cb)
 {
+	struct imap_server_conf *cfg = cb;
 
 	if (!strcmp("imap.sslverify", var))
-		server.ssl_verify = git_config_bool(var, val);
+		cfg->ssl_verify = git_config_bool(var, val);
 	else if (!strcmp("imap.preformattedhtml", var))
-		server.use_html = git_config_bool(var, val);
+		cfg->use_html = git_config_bool(var, val);
 	else if (!strcmp("imap.folder", var))
-		return git_config_string(&server.folder, var, val);
+		return git_config_string(&cfg->folder, var, val);
 	else if (!strcmp("imap.user", var))
-		return git_config_string(&server.user, var, val);
+		return git_config_string(&cfg->user, var, val);
 	else if (!strcmp("imap.pass", var))
-		return git_config_string(&server.pass, var, val);
+		return git_config_string(&cfg->pass, var, val);
 	else if (!strcmp("imap.tunnel", var))
-		return git_config_string(&server.tunnel, var, val);
+		return git_config_string(&cfg->tunnel, var, val);
 	else if (!strcmp("imap.authmethod", var))
-		return git_config_string(&server.auth_method, var, val);
+		return git_config_string(&cfg->auth_method, var, val);
 	else if (!strcmp("imap.port", var))
-		server.port = git_config_int(var, val, ctx->kvi);
+		cfg->port = git_config_int(var, val, ctx->kvi);
 	else if (!strcmp("imap.host", var)) {
 		if (!val) {
 			return config_error_nonbool(var);
@@ -1324,11 +1324,11 @@ static int git_imap_config(const char *var, const char *val,
 				val += 5;
 			else if (starts_with(val, "imaps:")) {
 				val += 6;
-				server.use_ssl = 1;
+				cfg->use_ssl = 1;
 			}
 			if (starts_with(val, "//"))
 				val += 2;
-			server.host = xstrdup(val);
+			cfg->host = xstrdup(val);
 		}
 	} else
 		return git_default_config(var, val, ctx, cb);
@@ -1497,12 +1497,15 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,
 
 int cmd_main(int argc, const char **argv)
 {
+	struct imap_server_conf server = {
+		.ssl_verify = 1,
+	};
 	struct strbuf all_msgs = STRBUF_INIT;
 	int total;
 	int nongit_ok;
 
 	setup_git_directory_gently(&nongit_ok);
-	git_config(git_imap_config, NULL);
+	git_config(git_imap_config, &server);
 
 	argc = parse_options(argc, (const char **)argv, "", imap_send_options, imap_send_usage, 0);
 
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 30, 2024, 12:51 UTC · re: Patrick Steinhardt · lore

[PATCH v2 15/19] imap-send: fix leaking memory in `imap_server_conf`

We never free any of the config strings that we populate into the `struct imap_server_conf`. Fix this by creating a common exit path where we can free resources.

While at it, drop the unused member `imap_server_conf::name`.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 imap-send.c | 65 ++++++++++++++++++++++++++++++++++-------------------
 1 file changed, 42 insertions(+), 23 deletions(-)
Show changes to imap-send.c +42 −23
diff --git a/imap-send.c b/imap-send.c
index 67a7a6c456..da3e7ec17e 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -69,7 +69,6 @@ static void imap_warn(const char *, ...);
 static char *next_arg(char **);
 
 struct imap_server_conf {
-	const char *name;
 	char *tunnel;
 	char *host;
 	int port;
@@ -1300,23 +1299,28 @@ static int git_imap_config(const char *var, const char *val,
 {
 	struct imap_server_conf *cfg = cb;
 
-	if (!strcmp("imap.sslverify", var))
+	if (!strcmp("imap.sslverify", var)) {
 		cfg->ssl_verify = git_config_bool(var, val);
-	else if (!strcmp("imap.preformattedhtml", var))
+	} else if (!strcmp("imap.preformattedhtml", var)) {
 		cfg->use_html = git_config_bool(var, val);
-	else if (!strcmp("imap.folder", var))
+	} else if (!strcmp("imap.folder", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->folder, var, val);
-	else if (!strcmp("imap.user", var))
+	} else if (!strcmp("imap.user", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->user, var, val);
-	else if (!strcmp("imap.pass", var))
+	} else if (!strcmp("imap.pass", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->pass, var, val);
-	else if (!strcmp("imap.tunnel", var))
+	} else if (!strcmp("imap.tunnel", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->tunnel, var, val);
-	else if (!strcmp("imap.authmethod", var))
+	} else if (!strcmp("imap.authmethod", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->auth_method, var, val);
-	else if (!strcmp("imap.port", var))
+	} else if (!strcmp("imap.port", var)) {
 		cfg->port = git_config_int(var, val, ctx->kvi);
-	else if (!strcmp("imap.host", var)) {
+	} else if (!strcmp("imap.host", var)) {
 		if (!val) {
 			return config_error_nonbool(var);
 		} else {
@@ -1330,8 +1334,9 @@ static int git_imap_config(const char *var, const char *val,
 				val += 2;
 			cfg->host = xstrdup(val);
 		}
-	} else
+	} else {
 		return git_default_config(var, val, ctx, cb);
+	}
 
 	return 0;
 }
@@ -1503,6 +1508,7 @@ int cmd_main(int argc, const char **argv)
 	struct strbuf all_msgs = STRBUF_INIT;
 	int total;
 	int nongit_ok;
+	int ret;
 
 	setup_git_directory_gently(&nongit_ok);
 	git_config(git_imap_config, &server);
@@ -1529,42 +1535,55 @@ int cmd_main(int argc, const char **argv)
 
 	if (!server.folder) {
 		fprintf(stderr, "no imap store specified\n");
-		return 1;
+		ret = 1;
+		goto out;
 	}
 	if (!server.host) {
 		if (!server.tunnel) {
 			fprintf(stderr, "no imap host specified\n");
-			return 1;
+			ret = 1;
+			goto out;
 		}
-		server.host = "tunnel";
+		server.host = xstrdup("tunnel");
 	}
 
 	/* read the messages */
 	if (strbuf_read(&all_msgs, 0, 0) < 0) {
 		error_errno(_("could not read from stdin"));
-		return 1;
+		ret = 1;
+		goto out;
 	}
 
 	if (all_msgs.len == 0) {
 		fprintf(stderr, "nothing to send\n");
-		return 1;
+		ret = 1;
+		goto out;
 	}
 
 	total = count_messages(&all_msgs);
 	if (!total) {
 		fprintf(stderr, "no messages to send\n");
-		return 1;
+		ret = 1;
+		goto out;
 	}
 
 	/* write it to the imap server */
 
 	if (server.tunnel)
-		return append_msgs_to_imap(&server, &all_msgs, total);
-
+		ret = append_msgs_to_imap(&server, &all_msgs, total);
 #ifdef USE_CURL_FOR_IMAP_SEND
-	if (use_curl)
-		return curl_append_msgs_to_imap(&server, &all_msgs, total);
+	else if (use_curl)
+		ret = curl_append_msgs_to_imap(&server, &all_msgs, total);
 #endif
-
-	return append_msgs_to_imap(&server, &all_msgs, total);
+	else
+		ret = append_msgs_to_imap(&server, &all_msgs, total);
+
+out:
+	free(server.tunnel);
+	free(server.host);
+	free(server.folder);
+	free(server.user);
+	free(server.pass);
+	free(server.auth_method);
+	return ret;
 }
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 30, 2024, 12:51 UTC · re: Patrick Steinhardt · lore

[PATCH v2 16/19] builtin/rebase: do not assign default backend to non-constant field

The `struct rebase_options::default_backend` field is a non-constant string, but is being assigned a constant via `REBASE_OPTIONS_INIT`. Refactor the code to initialize and release options via two functions `rebase_options_init()` and `rebase_options_release()`. Like this, we can easily adapt the former funnction to use `xstrdup()` on the default value without hiding it away in a macro.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rebase.c | 67 ++++++++++++++++++++++++++++--------------------
 1 file changed, 39 insertions(+), 28 deletions(-)
Show changes to builtin/rebase.c +39 −28
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 14d4f0a5e6..11f276012c 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -131,25 +131,40 @@ struct rebase_options {
 	int config_update_refs;
 };
 
-#define REBASE_OPTIONS_INIT {			  	\
-		.type = REBASE_UNSPECIFIED,	  	\
-		.empty = EMPTY_UNSPECIFIED,	  	\
-		.keep_empty = 1,			\
-		.default_backend = "merge",	  	\
-		.flags = REBASE_NO_QUIET, 		\
-		.git_am_opts = STRVEC_INIT,		\
-		.exec = STRING_LIST_INIT_NODUP,		\
-		.git_format_patch_opt = STRBUF_INIT,	\
-		.fork_point = -1,			\
-		.reapply_cherry_picks = -1,             \
-		.allow_empty_message = 1,               \
-		.autosquash = -1,                       \
-		.rebase_merges = -1,                    \
-		.config_rebase_merges = -1,             \
-		.update_refs = -1,                      \
-		.config_update_refs = -1,               \
-		.strategy_opts = STRING_LIST_INIT_NODUP,\
-	}
+static void rebase_options_init(struct rebase_options *opts)
+{
+	memset(opts, 0, sizeof(*opts));
+	opts->type = REBASE_UNSPECIFIED;
+	opts->empty = EMPTY_UNSPECIFIED;
+	opts->default_backend = xstrdup("merge");
+	opts->keep_empty = 1;
+	opts->flags = REBASE_NO_QUIET;
+	strvec_init(&opts->git_am_opts);
+	string_list_init_nodup(&opts->exec);
+	strbuf_init(&opts->git_format_patch_opt, 0);
+	opts->fork_point = -1;
+	opts->reapply_cherry_picks = -1;
+	opts->allow_empty_message = 1;
+	opts->autosquash = -1;
+	opts->rebase_merges = -1;
+	opts->config_rebase_merges = -1;
+	opts->update_refs = -1;
+	opts->config_update_refs = -1;
+	string_list_init_nodup(&opts->strategy_opts);
+}
+
+static void rebase_options_release(struct rebase_options *opts)
+{
+	free(opts->default_backend);
+	free(opts->reflog_action);
+	free(opts->head_name);
+	strvec_clear(&opts->git_am_opts);
+	free(opts->gpg_sign_opt);
+	string_list_clear(&opts->exec, 0);
+	free(opts->strategy);
+	string_list_clear(&opts->strategy_opts, 0);
+	strbuf_release(&opts->git_format_patch_opt);
+}
 
 static struct replay_opts get_replay_opts(const struct rebase_options *opts)
 {
@@ -796,6 +811,7 @@ static int rebase_config(const char *var, const char *value,
 	}
 
 	if (!strcmp(var, "rebase.backend")) {
+		FREE_AND_NULL(opts->default_backend);
 		return git_config_string(&opts->default_backend, var, value);
 	}
 
@@ -1045,7 +1061,7 @@ static int check_exec_cmd(const char *cmd)
 
 int cmd_rebase(int argc, const char **argv, const char *prefix)
 {
-	struct rebase_options options = REBASE_OPTIONS_INIT;
+	struct rebase_options options;
 	const char *branch_name;
 	int ret, flags, total_argc, in_progress = 0;
 	int keep_base = 0;
@@ -1178,6 +1194,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	};
 	int i;
 
+	rebase_options_init(&options);
+
 	if (argc == 2 && !strcmp(argv[1], "-h"))
 		usage_with_options(builtin_rebase_usage,
 				   builtin_rebase_options);
@@ -1833,14 +1851,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 cleanup:
 	strbuf_release(&buf);
 	strbuf_release(&revisions);
-	free(options.reflog_action);
-	free(options.head_name);
-	strvec_clear(&options.git_am_opts);
-	free(options.gpg_sign_opt);
-	string_list_clear(&options.exec, 0);
-	free(options.strategy);
-	string_list_clear(&options.strategy_opts, 0);
-	strbuf_release(&options.git_format_patch_opt);
+	rebase_options_release(&options);
 	free(squash_onto_name);
 	free(keep_base_onto_name);
 	return !!ret;
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 30, 2024, 12:51 UTC · re: Patrick Steinhardt · lore

[PATCH v2 17/19] builtin/rebase: always store allocated string in `options.strategy`

The `struct rebase_options::strategy` field is a `char *`, but we do end up assigning string constants to it in two cases:

  - When being passed a `--strategy=` option via the command line.
  - When being passed a strategy option via `--strategy-option=`, but
    not a strategy.
This will cause warnings once we enable `-Wwrite-strings`.

Ideally, we'd just convert the field to be a `const char *`. But we also assign to this field via the GIT_TEST_MERGE_ALGORITHM envvar, which we have to strdup(3P) into it.

Instead, refactor the code to make sure that we only ever assign allocated strings to this field.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rebase.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)
Show changes to builtin/rebase.c +7 −7
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 11f276012c..26068cf542 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1063,6 +1063,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 {
 	struct rebase_options options;
 	const char *branch_name;
+	const char *strategy_opt = NULL;
 	int ret, flags, total_argc, in_progress = 0;
 	int keep_base = 0;
 	int ok_to_skip_pre_rebase = 0;
@@ -1177,7 +1178,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			PARSE_OPT_OPTARG, parse_opt_rebase_merges),
 		OPT_BOOL(0, "fork-point", &options.fork_point,
 			 N_("use 'merge-base --fork-point' to refine upstream")),
-		OPT_STRING('s', "strategy", &options.strategy,
+		OPT_STRING('s', "strategy", &strategy_opt,
 			   N_("strategy"), N_("use the given merge strategy")),
 		OPT_STRING_LIST('X', "strategy-option", &options.strategy_opts,
 				N_("option"),
@@ -1488,13 +1489,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		}
 	}
 
-	if (options.strategy_opts.nr && !options.strategy)
-		options.strategy = "ort";
-
-	if (options.strategy) {
-		options.strategy = xstrdup(options.strategy);
+	if (strategy_opt)
+		options.strategy = xstrdup(strategy_opt);
+	else if (options.strategy_opts.nr && !options.strategy)
+		options.strategy = xstrdup("ort");
+	if (options.strategy)
 		imply_merge(&options, "--strategy");
-	}
 
 	if (options.root && !options.onto_name)
 		imply_merge(&options, "--root without --onto");
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 30, 2024, 12:51 UTC · re: Patrick Steinhardt · lore

[PATCH v2 18/19] builtin/merge: always store allocated strings in `pull_twohead`

The `pull_twohead` configuration may sometimes contain an allocated string, and sometimes it may contain a string constant. Refactor this to instead always store an allocated string such that we can release its resources without risk.

While at it, manage the lifetime of other config strings, as well. Note that we explicitly don't free `cleanup_arg` here. This is because the variable may be assigned a string constant via command line options.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/merge.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)
Show changes to builtin/merge.c +11 −7
diff --git a/builtin/merge.c b/builtin/merge.c
index daed2d4e1e..fb3eb15b89 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -611,17 +611,19 @@ static int git_merge_config(const char *k, const char *v,
 		return 0;
 	}
 
-	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
+	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat")) {
 		show_diffstat = git_config_bool(k, v);
-	else if (!strcmp(k, "merge.verifysignatures"))
+	} else if (!strcmp(k, "merge.verifysignatures")) {
 		verify_signatures = git_config_bool(k, v);
-	else if (!strcmp(k, "pull.twohead"))
+	} else if (!strcmp(k, "pull.twohead")) {
+		FREE_AND_NULL(pull_twohead);
 		return git_config_string(&pull_twohead, k, v);
-	else if (!strcmp(k, "pull.octopus"))
+	} else if (!strcmp(k, "pull.octopus")) {
+		FREE_AND_NULL(pull_octopus);
 		return git_config_string(&pull_octopus, k, v);
-	else if (!strcmp(k, "commit.cleanup"))
+	} else if (!strcmp(k, "commit.cleanup")) {
 		return git_config_string(&cleanup_arg, k, v);
-	else if (!strcmp(k, "merge.ff")) {
+	} else if (!strcmp(k, "merge.ff")) {
 		int boolval = git_parse_maybe_bool(v);
 		if (0 <= boolval) {
 			fast_forward = boolval ? FF_ALLOW : FF_NO;
@@ -1294,7 +1296,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 	if (!pull_twohead) {
 		char *default_strategy = getenv("GIT_TEST_MERGE_ALGORITHM");
 		if (default_strategy && !strcmp(default_strategy, "ort"))
-			pull_twohead = "ort";
+			pull_twohead = xstrdup("ort");
 	}
 
 	init_diff_ui_defaults();
@@ -1793,6 +1795,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 	}
 	strbuf_release(&buf);
 	free(branch_to_free);
+	free(pull_twohead);
+	free(pull_octopus);
 	discard_index(the_repository->index);
 	return ret;
 }
-- 
2.45.1.313.g3a57aa566a.dirty
Patrick Steinhardt· May 30, 2024, 12:52 UTC · re: Patrick Steinhardt · lore

[PATCH v2 19/19] config.mak.dev: enable `-Wwrite-strings` warning

Writing to string constants is undefined behaviour and must be avoided in C. Even so, the compiler does not help us with this by default because those constants are not in fact marked as `const`. This makes it rather easy to accidentally assign a constant to a non-const variable or field and then later on try to either free it or write to it.

Enable `-Wwrite-strings` to catch such mistakes. With this warning enabled, the type of string constants is changed to `const char[]` and will thus cause compiler warnings when being assigned to non-const fields and variables.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 config.mak.dev | 1 +
 1 file changed, 1 insertion(+)
Show changes to config.mak.dev +1 −0
diff --git a/config.mak.dev b/config.mak.dev
index 981304727c..1ce4c70613 100644
--- a/config.mak.dev
+++ b/config.mak.dev
@@ -37,6 +37,7 @@ DEVELOPER_CFLAGS += -Wpointer-arith
 DEVELOPER_CFLAGS += -Wstrict-prototypes
 DEVELOPER_CFLAGS += -Wunused
 DEVELOPER_CFLAGS += -Wvla
+DEVELOPER_CFLAGS += -Wwrite-strings
 DEVELOPER_CFLAGS += -fno-common
 
 ifneq ($(filter clang4,$(COMPILER_FEATURES)),)
-- 
2.45.1.313.g3a57aa566a.dirty
Junio C Hamano· May 31, 2024, 09:13 UTC · re: Patrick Steinhardt · lore

Re: [PATCH v2 00/19] Compile with `-Wwrite-strings`

Patrick Steinhardt <ps@pks.im> writes:
Show 5 quoted lines
>      @@ reftable/readwrite_test.c: static void test_write_key_order(void)
>     + 	struct strbuf buf = STRBUF_INIT;
>     + 	struct reftable_writer *w =
>       		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
>     ++	char a[] = "a", b[] = "b", target[] = "target";
So you decided to go in the complete opposite direction, hmph...

I was hoping that we do not add more "writable" pieces of memory like target[] only to please the constness-strict compilers.

 
Patrick Steinhardt· May 31, 2024, 12:10 UTC · re: Junio C Hamano · lore

Re: [PATCH v2 00/19] Compile with `-Wwrite-strings`

On Fri, May 31, 2024 at 02:13:27AM -0700, Junio C Hamano wrote:
Show 12 quoted lines
> Patrick Steinhardt <ps@pks.im> writes:
> 
> >      @@ reftable/readwrite_test.c: static void test_write_key_order(void)
> >     + 	struct strbuf buf = STRBUF_INIT;
> >     + 	struct reftable_writer *w =
> >       		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
> >     ++	char a[] = "a", b[] = "b", target[] = "target";
> 
> So you decided to go in the complete opposite direction, hmph...
> 
> I was hoping that we do not add more "writable" pieces of memory
> like target[] only to please the constness-strict compilers.

I guess I misunderstood what you were saying. I'll revise this to go into the other direction.

Patrick
Patrick Steinhardt· Jun 3, 2024, 09:38 UTC · re: Patrick Steinhardt · lore

[PATCH v3 00/27] Compile with `-Wwrite-strings`

Hi,

this is the third version of my patch series that prepares the Git codebase to compile with `-Wwrite-strings`. This compiler warning changes the type of string constants from `char []` to `const char []`, which helps to identify cases where we may accidentally write to or free such constants.

Changes compared to v2:
  - I have split up the second patch into multiple patches. This was
    done so that we can have a deeper look at the respective sites that
    need adjustment.
  - I dropped the local string arrays that I used in v2 and instead now
    use string constants with casts in some places where we expect that
    those should never be written to. This has the benefit that we would
    segfault in case that expectation is violated, instead of silently
    allowing a write to those local arrays.
  - I adapted multiple commit messages to not only mention freeing of
    string constants, but also that writing to string constants is
    illegal.

Due to the split-up patch the range-diff got quite messy and was barely readable anymore. I thus included an interdiff instead, which should be way easier to read.

I realize that this patch series has grown quite large. Please let me know in case I shall split it up into multiple patch series.

Patrick
Patrick Steinhardt (27):
  global: improve const correctness when assigning string constants
  global: convert intentionally-leaking config strings to consts
  refs/reftable: stop micro-optimizing refname allocations on copy
  reftable: cast away constness when assigning constants to records
  refspec: remove global tag refspec structure
  builtin/remote: cast away constness in `get_head_names()`
  diff: cast string constant in `fill_textconv()`
  line-log: stop assigning string constant to file parent buffer
  line-log: always allocate the output prefix
  entry: refactor how we remove items for delayed checkouts
  ident: add casts for fallback name and GECOS
  object-file: mark cached object buffers as const
  object-file: make `buf` parameter of `index_mem()` a constant
  pretty: add casts for decoration option pointers
  compat/win32: fix const-correctness with string constants
  http: do not assign string constant to non-const field
  parse-options: cast long name for OPTION_ALIAS
  send-pack: always allocate receive status
  remote-curl: avoid assigning string constant to non-const variable
  revision: always store allocated strings in output encoding
  mailmap: always store allocated strings in mailmap blob
  imap-send: drop global `imap_server_conf` variable
  imap-send: fix leaking memory in `imap_server_conf`
  builtin/rebase: do not assign default backend to non-constant field
  builtin/rebase: always store allocated string in `options.strategy`
  builtin/merge: always store allocated strings in `pull_twohead`
  config.mak.dev: enable `-Wwrite-strings` warning
 builtin/bisect.c             |   3 +-
 builtin/blame.c              |   2 +-
 builtin/bugreport.c          |   2 +-
 builtin/check-ignore.c       |   4 +-
 builtin/clone.c              |  14 ++--
 builtin/commit.c             |   6 +-
 builtin/diagnose.c           |   2 +-
 builtin/fetch.c              |  11 ++-
 builtin/log.c                |   2 +-
 builtin/mailsplit.c          |   4 +-
 builtin/merge.c              |  18 +++--
 builtin/pull.c               |  52 +++++++-------
 builtin/rebase.c             |  81 ++++++++++++----------
 builtin/receive-pack.c       |   4 +-
 builtin/remote.c             |  12 ++--
 builtin/revert.c             |   2 +-
 builtin/send-pack.c          |   2 +
 compat/basename.c            |  18 ++++-
 compat/mingw.c               |  28 ++++----
 compat/regex/regcomp.c       |   2 +-
 compat/winansi.c             |   2 +-
 config.mak.dev               |   1 +
 diff.c                       |   6 +-
 diffcore-rename.c            |   6 +-
 entry.c                      |  14 ++--
 fmt-merge-msg.c              |   2 +-
 fsck.c                       |   2 +-
 fsck.h                       |   2 +-
 gpg-interface.c              |   6 +-
 http-backend.c               |   2 +-
 http.c                       |   5 +-
 ident.c                      |   4 +-
 imap-send.c                  | 130 ++++++++++++++++++++---------------
 line-log.c                   |  22 +++---
 mailmap.c                    |   2 +-
 merge-ll.c                   |  11 ++-
 object-file.c                |  23 ++++---
 parse-options.h              |   2 +-
 pretty.c                     |   6 +-
 refs.c                       |   2 +-
 refs.h                       |   2 +-
 refs/reftable-backend.c      |  28 ++++----
 refspec.c                    |  13 ----
 refspec.h                    |   1 -
 reftable/basics.c            |  15 ++--
 reftable/basics.h            |   4 +-
 reftable/basics_test.c       |   4 +-
 reftable/block_test.c        |   2 +-
 reftable/merged_test.c       |  44 ++++++------
 reftable/readwrite_test.c    |  32 ++++-----
 reftable/record.c            |   6 +-
 reftable/stack.c             |  10 +--
 reftable/stack_test.c        |  56 +++++++--------
 remote-curl.c                |  53 +++++++-------
 revision.c                   |   3 +-
 run-command.c                |   2 +-
 send-pack.c                  |   2 +-
 t/helper/test-hashmap.c      |   3 +-
 t/helper/test-json-writer.c  |  10 +--
 t/helper/test-regex.c        |   4 +-
 t/helper/test-rot13-filter.c |   5 +-
 t/t3900-i18n-commit.sh       |   1 +
 t/t3901-i18n-patch.sh        |   1 +
 t/unit-tests/t-strbuf.c      |  10 +--
 trailer.c                    |   2 +-
 userdiff.c                   |  10 +--
 userdiff.h                   |  12 ++--
 wt-status.c                  |   2 +-
 68 files changed, 470 insertions(+), 386 deletions(-)
Interdiff against v2:
Show changes to 18 files +167 −189

builtin/remote.c, compat/basename.c, compat/mingw.c, diff.c, entry.c, ident.c, line-log.c, object-file.c, pretty.c, refs/reftable-backend.c, reftable/basics.c, reftable/basics.h, reftable/basics_test.c, reftable/block_test.c, reftable/merged_test.c, reftable/readwrite_test.c, reftable/stack.c, reftable/stack_test.c

diff --git a/builtin/remote.c b/builtin/remote.c
index 0324a5d48d..b44f580b8c 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -493,13 +493,13 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat
 {
 	struct ref *ref, *matches;
 	struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;
-	struct refspec_item refspec;
-	char refspec_str[] = "refs/heads/*";
+	struct refspec_item refspec = {
+		.force = 0,
+		.pattern = 1,
+		.src = (char *) "refs/heads/*",
+		.dst = (char *) "refs/heads/*",
+	};
 
-	memset(&refspec, 0, sizeof(refspec));
-	refspec.force = 0;
-	refspec.pattern = 1;
-	refspec.src = refspec.dst = refspec_str;
 	get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
 	matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
 				    fetch_map, 1);
@@ -508,7 +508,6 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat
 
 	free_refs(fetch_map);
 	free_refs(matches);
-
 	return 0;
 }
 
diff --git a/compat/basename.c b/compat/basename.c
index c3c9d65fac..5aa320306b 100644
--- a/compat/basename.c
+++ b/compat/basename.c
@@ -1,11 +1,6 @@
 #include "../git-compat-util.h"
 #include "../strbuf.h"
 
-/*
- * Both basename(3P) and dirname(3P) are mis-specified because they return a
- * non-constant pointer even though it is specified that they may return a
- * pointer to internal memory. This variable here is a result of that.
- */
 static char current_directory[] = ".";
 
 /* Adapted from libiberty's basename.c.  */
@@ -17,7 +12,13 @@ char *gitbasename (char *path)
 		skip_dos_drive_prefix(&path);
 
 	if (!path || !*path)
-		return current_directory;
+		/*
+		 * basename(3P) is mis-specified because it returns a
+		 * non-constant pointer even though it is specified to return a
+		 * pointer to internal memory at times. The cast is a result of
+		 * that.
+		 */
+		return (char *) "";
 
 	for (base = path; *path; path++) {
 		if (!is_dir_sep(*path))
@@ -40,12 +41,14 @@ char *gitdirname(char *path)
 	char *p = path, *slash = NULL, c;
 	int dos_drive_prefix;
 
-	/*
-	 * Same here, dirname(3P) is broken because it returns a non-constant
-	 * pointer that may point to internal memory.
-	 */
 	if (!p)
-		return current_directory;
+		/*
+		 * dirname(3P) is mis-specified because it returns a
+		 * non-constant pointer even though it is specified to return a
+		 * pointer to internal memory at times. The cast is a result of
+		 * that.
+		 */
+		return (char *) "";
 
 	if ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p)
 		goto dot;
diff --git a/compat/mingw.c b/compat/mingw.c
index 60f0986f76..d378cd04cb 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -2257,7 +2257,6 @@ struct passwd *getpwuid(int uid)
 {
 	static unsigned initialized;
 	static char user_name[100];
-	static char unknown[] = "unknown";
 	static struct passwd *p;
 	wchar_t buf[100];
 	DWORD len;
@@ -2280,7 +2279,11 @@ struct passwd *getpwuid(int uid)
 	p->pw_name = user_name;
 	p->pw_gecos = get_extended_user_info(NameDisplay);
 	if (!p->pw_gecos)
-		p->pw_gecos = unknown;
+		/*
+		 * Data returned by getpwuid(3P) is treated as internal and
+		 * must never be written to or freed.
+		 */
+		p->pw_gecos = (char *) "unknown";
 	p->pw_dir = NULL;
 
 	initialized = 1;
diff --git a/diff.c b/diff.c
index 1439a5a01d..cecda216cf 100644
--- a/diff.c
+++ b/diff.c
@@ -7231,12 +7231,11 @@ size_t fill_textconv(struct repository *r,
 		     struct diff_filespec *df,
 		     char **outbuf)
 {
-	static char empty_str[] = "";
 	size_t size;
 
 	if (!driver) {
 		if (!DIFF_FILE_VALID(df)) {
-			*outbuf = empty_str;
+			*outbuf = (char *) "";
 			return 0;
 		}
 		if (diff_populate_filespec(r, df, NULL))
diff --git a/entry.c b/entry.c
index 2fc06ac90c..f291d8eee6 100644
--- a/entry.c
+++ b/entry.c
@@ -167,6 +167,11 @@ static int remove_available_paths(struct string_list_item *item, void *cb_data)
 	return !available;
 }
 
+static int string_is_not_null(struct string_list_item *item, void *data UNUSED)
+{
+	return !!item->string;
+}
+
 int finish_delayed_checkout(struct checkout *state, int show_progress)
 {
 	int errs = 0;
@@ -175,7 +180,6 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 	struct string_list_item *filter, *path;
 	struct progress *progress = NULL;
 	struct delayed_checkout *dco = state->delayed_checkout;
-	char empty_str[] = "";
 
 	if (!state->delayed_checkout)
 		return errs;
@@ -190,7 +194,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 			if (!async_query_available_blobs(filter->string, &available_paths)) {
 				/* Filter reported an error */
 				errs = 1;
-				filter->string = empty_str;
+				filter->string = NULL;
 				continue;
 			}
 			if (available_paths.nr <= 0) {
@@ -200,7 +204,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 				 * filter from the list (see
 				 * "string_list_remove_empty_items" call below).
 				 */
-				filter->string = empty_str;
+				filter->string = NULL;
 				continue;
 			}
 
@@ -226,7 +230,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 					 * Do not ask the filter for available blobs,
 					 * again, as the filter is likely buggy.
 					 */
-					filter->string = empty_str;
+					filter->string = NULL;
 					continue;
 				}
 				ce = index_file_exists(state->istate, path->string,
@@ -240,7 +244,8 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 					errs = 1;
 			}
 		}
-		string_list_remove_empty_items(&dco->filters, 0);
+
+		filter_string_list(&dco->filters, 0, string_is_not_null, NULL);
 	}
 	stop_progress(&progress);
 	string_list_clear(&dco->filters, 0);
diff --git a/ident.c b/ident.c
index df7aa42802..caf41fb2a9 100644
--- a/ident.c
+++ b/ident.c
@@ -46,14 +46,9 @@ static struct passwd *xgetpwuid_self(int *is_bogus)
 	pw = getpwuid(getuid());
 	if (!pw) {
 		static struct passwd fallback;
-		static char fallback_name[] = "unknown";
+		fallback.pw_name = (char *) "unknown";
 #ifndef NO_GECOS_IN_PWENT
-		static char fallback_gcos[] = "Unknown";
-#endif
-
-		fallback.pw_name = fallback_name;
-#ifndef NO_GECOS_IN_PWENT
-		fallback.pw_gecos = fallback_gcos;
+		fallback.pw_gecos = (char *) "Unknown";
 #endif
 		pw = &fallback;
 		if (is_bogus)
diff --git a/line-log.c b/line-log.c
index 9a298209d0..67c80b39a0 100644
--- a/line-log.c
+++ b/line-log.c
@@ -1036,7 +1036,7 @@ static int process_diff_filepair(struct rev_info *rev,
 	struct range_set tmp;
 	struct diff_ranges diff;
 	mmfile_t file_parent, file_target;
-	char empty_str[] = "";
+	char *parent_data_to_free = NULL;
 
 	assert(pair->two->path);
 	while (rg) {
@@ -1061,7 +1061,7 @@ static int process_diff_filepair(struct rev_info *rev,
 		file_parent.ptr = pair->one->data;
 		file_parent.size = pair->one->size;
 	} else {
-		file_parent.ptr = empty_str;
+		file_parent.ptr = parent_data_to_free = xstrdup("");
 		file_parent.size = 0;
 	}
 
@@ -1080,6 +1080,7 @@ static int process_diff_filepair(struct rev_info *rev,
 
 	diff_ranges_release(&diff);
 
+	free(parent_data_to_free);
 	return ((*diff_out)->parent.nr > 0);
 }
 
diff --git a/object-file.c b/object-file.c
index 46ea00ac46..b5b5a59dc6 100644
--- a/object-file.c
+++ b/object-file.c
@@ -277,18 +277,17 @@ int hash_algo_by_length(int len)
 static struct cached_object {
 	struct object_id oid;
 	enum object_type type;
-	void *buf;
+	const void *buf;
 	unsigned long size;
 } *cached_objects;
 static int cached_object_nr, cached_object_alloc;
 
-static char empty_tree_buf[] = "";
 static struct cached_object empty_tree = {
 	.oid = {
 		.hash = EMPTY_TREE_SHA1_BIN_LITERAL,
 	},
 	.type = OBJ_TREE,
-	.buf = empty_tree_buf,
+	.buf = "",
 };
 
 static struct cached_object *find_cached_object(const struct object_id *oid)
@@ -1779,6 +1778,10 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,
 			struct object_id *oid)
 {
 	struct cached_object *co;
+	char *co_buf;
+
+	co_buf = xmalloc(len);
+	memcpy(co_buf, buf, len);
 
 	hash_object_file(the_hash_algo, buf, len, type, oid);
 	if (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||
@@ -1788,8 +1791,7 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,
 	co = &cached_objects[cached_object_nr++];
 	co->size = len;
 	co->type = type;
-	co->buf = xmalloc(len);
-	memcpy(co->buf, buf, len);
+	co->buf = co_buf;
 	oidcpy(&co->oid, oid);
 	return 0;
 }
diff --git a/pretty.c b/pretty.c
index 1a0030b32a..1df9d635fb 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1583,10 +1583,9 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
 		return 1;
 	case 'D':
 		{
-			char empty_str[] = "";
 			const struct decoration_options opts = {
-				.prefix = empty_str,
-				.suffix = empty_str,
+				.prefix = (char *) "",
+				.suffix = (char *) "",
 			};
 
 			format_decorations(sb, commit, c->auto_color, &opts);
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 1908e74dea..e77faa2b9d 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1285,7 +1285,6 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 	struct strbuf errbuf = STRBUF_INIT;
 	size_t logs_nr = 0, logs_alloc = 0, i;
 	const char *committer_info;
-	char head[] = "HEAD";
 	int ret;
 
 	committer_info = git_committer_info(0);
@@ -1341,10 +1340,10 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 	 * old reference.
 	 */
 	refs[0] = old_ref;
-	refs[0].refname = (char *)arg->newname;
+	refs[0].refname = xstrdup(arg->newname);
 	refs[0].update_index = creation_ts;
 	if (arg->delete_old) {
-		refs[1].refname = (char *)arg->oldname;
+		refs[1].refname = xstrdup(arg->oldname);
 		refs[1].value_type = REFTABLE_REF_DELETION;
 		refs[1].update_index = deletion_ts;
 	}
@@ -1367,7 +1366,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 		ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 		memset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));
 		fill_reftable_log_record(&logs[logs_nr], &committer_ident);
-		logs[logs_nr].refname = (char *)arg->newname;
+		logs[logs_nr].refname = xstrdup(arg->newname);
 		logs[logs_nr].update_index = deletion_ts;
 		logs[logs_nr].value.update.message =
 			xstrndup(arg->logmsg, arg->refs->write_options.block_size / 2);
@@ -1388,7 +1387,13 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 		if (append_head_reflog) {
 			ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 			logs[logs_nr] = logs[logs_nr - 1];
-			logs[logs_nr].refname = head;
+			logs[logs_nr].refname = xstrdup("HEAD");
+			logs[logs_nr].value.update.name =
+				xstrdup(logs[logs_nr].value.update.name);
+			logs[logs_nr].value.update.email =
+				xstrdup(logs[logs_nr].value.update.email);
+			logs[logs_nr].value.update.message =
+				xstrdup(logs[logs_nr].value.update.message);
 			logs_nr++;
 		}
 	}
@@ -1399,7 +1404,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 	ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 	memset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));
 	fill_reftable_log_record(&logs[logs_nr], &committer_ident);
-	logs[logs_nr].refname = (char *)arg->newname;
+	logs[logs_nr].refname = xstrdup(arg->newname);
 	logs[logs_nr].update_index = creation_ts;
 	logs[logs_nr].value.update.message =
 		xstrndup(arg->logmsg, arg->refs->write_options.block_size / 2);
@@ -1431,7 +1436,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 		 */
 		ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 		logs[logs_nr] = old_log;
-		logs[logs_nr].refname = (char *)arg->newname;
+		logs[logs_nr].refname = xstrdup(arg->newname);
 		logs_nr++;
 
 		/*
@@ -1440,7 +1445,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 		if (arg->delete_old) {
 			ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 			memset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));
-			logs[logs_nr].refname = (char *)arg->oldname;
+			logs[logs_nr].refname = xstrdup(arg->oldname);
 			logs[logs_nr].value_type = REFTABLE_LOG_DELETION;
 			logs[logs_nr].update_index = old_log.update_index;
 			logs_nr++;
@@ -1463,13 +1468,11 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 	reftable_iterator_destroy(&it);
 	string_list_clear(&skip, 0);
 	strbuf_release(&errbuf);
-	for (i = 0; i < logs_nr; i++) {
-		if (logs[i].refname == head)
-			continue;
-		logs[i].refname = NULL;
+	for (i = 0; i < logs_nr; i++)
 		reftable_log_record_release(&logs[i]);
-	}
 	free(logs);
+	for (i = 0; i < ARRAY_SIZE(refs); i++)
+		reftable_ref_record_release(&refs[i]);
 	reftable_ref_record_release(&old_ref);
 	reftable_log_record_release(&old_log);
 	return ret;
diff --git a/reftable/basics.c b/reftable/basics.c
index fea711db7e..0058619ca6 100644
--- a/reftable/basics.c
+++ b/reftable/basics.c
@@ -67,9 +67,9 @@ void free_names(char **a)
 	reftable_free(a);
 }
 
-size_t names_length(char **names)
+size_t names_length(const char **names)
 {
-	char **p = names;
+	const char **p = names;
 	while (*p)
 		p++;
 	return p - names;
@@ -102,15 +102,12 @@ void parse_names(char *buf, int size, char ***namesp)
 	*namesp = names;
 }
 
-int names_equal(char **a, char **b)
+int names_equal(const char **a, const char **b)
 {
-	int i = 0;
-	for (; a[i] && b[i]; i++) {
-		if (strcmp(a[i], b[i])) {
+	size_t i = 0;
+	for (; a[i] && b[i]; i++)
+		if (strcmp(a[i], b[i]))
 			return 0;
-		}
-	}
-
 	return a[i] == b[i];
 }
 
diff --git a/reftable/basics.h b/reftable/basics.h
index 523ecd5307..c8fec68d4e 100644
--- a/reftable/basics.h
+++ b/reftable/basics.h
@@ -42,10 +42,10 @@ void free_names(char **a);
 void parse_names(char *buf, int size, char ***namesp);
 
 /* compares two NULL-terminated arrays of strings. */
-int names_equal(char **a, char **b);
+int names_equal(const char **a, const char **b);
 
 /* returns the array size of a NULL-terminated array of strings. */
-size_t names_length(char **names);
+size_t names_length(const char **names);
 
 /* Allocation routines; they invoke the functions set through
  * reftable_set_alloc() */
diff --git a/reftable/basics_test.c b/reftable/basics_test.c
index 23fab22eb1..13bc761817 100644
--- a/reftable/basics_test.c
+++ b/reftable/basics_test.c
@@ -58,8 +58,7 @@ static void test_binsearch(void)
 
 static void test_names_length(void)
 {
-	char a[] = "a", b[] = "b";
-	char *names[] = { a, b, NULL };
+	const char *names[] = { "a", "b", NULL };
 	EXPECT(names_length(names) == 2);
 }
 
diff --git a/reftable/block_test.c b/reftable/block_test.c
index d5967b214d..90aecd5a7c 100644
--- a/reftable/block_test.c
+++ b/reftable/block_test.c
@@ -19,7 +19,7 @@ license that can be found in the LICENSE file or at
 static void test_block_read_write(void)
 {
 	const int header_off = 21; /* random */
-	char *names[30], empty_str[] = "";
+	char *names[30];
 	const int N = ARRAY_SIZE(names);
 	const int block_size = 1024;
 	struct reftable_block block = { NULL };
@@ -42,7 +42,7 @@ static void test_block_read_write(void)
 	block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,
 			  header_off, hash_size(GIT_SHA1_FORMAT_ID));
 
-	rec.u.ref.refname = empty_str;
+	rec.u.ref.refname = (char *) "";
 	rec.u.ref.value_type = REFTABLE_REF_DELETION;
 	n = block_writer_add(&bw, &rec);
 	EXPECT(n == REFTABLE_API_ERROR);
diff --git a/reftable/merged_test.c b/reftable/merged_test.c
index fd5a065e42..6d1159d12d 100644
--- a/reftable/merged_test.c
+++ b/reftable/merged_test.c
@@ -123,15 +123,14 @@ static void readers_destroy(struct reftable_reader **readers, size_t n)
 
 static void test_merged_between(void)
 {
-	char a[] = "a", b[] = "b";
 	struct reftable_ref_record r1[] = { {
-		.refname = b,
+		.refname = (char *) "b",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_VAL1,
 		.value.val1 = { 1, 2, 3, 0 },
 	} };
 	struct reftable_ref_record r2[] = { {
-		.refname = a,
+		.refname = (char *) "a",
 		.update_index = 2,
 		.value_type = REFTABLE_REF_DELETION,
 	} };
@@ -164,41 +163,40 @@ static void test_merged_between(void)
 
 static void test_merged(void)
 {
-	char a[] = "a", b[] = "b", c[] = "c", d[] = "d";
 	struct reftable_ref_record r1[] = {
 		{
-			.refname = a,
+			.refname = (char *) "a",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
 		},
 		{
-			.refname = b,
+			.refname = (char *) "b",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
 		},
 		{
-			.refname = c,
+			.refname = (char *) "c",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
 		}
 	};
 	struct reftable_ref_record r2[] = { {
-		.refname = a,
+		.refname = (char *) "a",
 		.update_index = 2,
 		.value_type = REFTABLE_REF_DELETION,
 	} };
 	struct reftable_ref_record r3[] = {
 		{
-			.refname = c,
+			.refname = (char *) "c",
 			.update_index = 3,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 2 },
 		},
 		{
-			.refname = d,
+			.refname = (char *) "d",
 			.update_index = 3,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
@@ -291,52 +289,48 @@ merged_table_from_log_records(struct reftable_log_record **logs,
 
 static void test_merged_logs(void)
 {
-	char a[] = "a";
-	char name[] = "jane doe", email[] = "jane@invalid";
-	char message1[] = "message1", message2[] = "message2";
-	char message3[] = "message3";
 	struct reftable_log_record r1[] = {
 		{
-			.refname = a,
+			.refname = (char *) "a",
 			.update_index = 2,
 			.value_type = REFTABLE_LOG_UPDATE,
 			.value.update = {
 				.old_hash = { 2 },
 				/* deletion */
-				.name = name,
-				.email = email,
-				.message = message2,
+				.name = (char *) "jane doe",
+				.email = (char *) "jane@invalid",
+				.message = (char *) "message2",
 			}
 		},
 		{
-			.refname = a,
+			.refname = (char *) "a",
 			.update_index = 1,
 			.value_type = REFTABLE_LOG_UPDATE,
 			.value.update = {
 				.old_hash = { 1 },
 				.new_hash = { 2 },
-				.name = name,
-				.email = email,
-				.message = message1,
+				.name = (char *) "jane doe",
+				.email = (char *) "jane@invalid",
+				.message = (char *) "message1",
 			}
 		},
 	};
 	struct reftable_log_record r2[] = {
 		{
-			.refname = a,
+			.refname = (char *) "a",
 			.update_index = 3,
 			.value_type = REFTABLE_LOG_UPDATE,
 			.value.update = {
 				.new_hash = { 3 },
-				.name = name,
-				.email = email,
-				.message = message3,
+				.name = (char *) "jane doe",
+				.email = (char *) "jane@invalid",
+				.message = (char *) "message3",
 			}
 		},
 	};
 	struct reftable_log_record r3[] = {
 		{
-			.refname = a,
+			.refname = (char *) "a",
 			.update_index = 2,
 			.value_type = REFTABLE_LOG_DELETION,
 		},
@@ -406,13 +400,13 @@ static void test_merged_logs(void)
 
 static void test_default_write_opts(void)
 {
-	char master[] = "master";
 	struct reftable_write_options opts = { 0 };
 	struct strbuf buf = STRBUF_INIT;
 	struct reftable_writer *w =
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
+
 	struct reftable_ref_record rec = {
-		.refname = master,
+		.refname = (char *) "master",
 		.update_index = 1,
 	};
 	int err;
diff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c
index 064d693111..c55019232b 100644
--- a/reftable/readwrite_test.c
+++ b/reftable/readwrite_test.c
@@ -56,7 +56,6 @@ static void write_table(char ***names, struct strbuf *buf, int N,
 	int i = 0, n;
 	struct reftable_log_record log = { NULL };
 	const struct reftable_stats *stats = NULL;
-	char message[] = "message";
 
 	REFTABLE_CALLOC_ARRAY(*names, N + 1);
 
@@ -87,7 +86,7 @@ static void write_table(char ***names, struct strbuf *buf, int N,
 		log.update_index = update_index;
 		log.value_type = REFTABLE_LOG_UPDATE;
 		set_test_hash(log.value.update.new_hash, i);
-		log.value.update.message = message;
+		log.value.update.message = (char *) "message";
 
 		n = reftable_writer_add_log(w, &log);
 		EXPECT(n == 0);
@@ -112,28 +111,23 @@ static void write_table(char ***names, struct strbuf *buf, int N,
 
 static void test_log_buffer_size(void)
 {
-	char refname[] = "refs/heads/master";
-	char name[] = "Han-Wen Hienhuys";
-	char email[] = "hanwen@google.com";
-	char message[] = "commit: 9\n";
 	struct strbuf buf = STRBUF_INIT;
 	struct reftable_write_options opts = {
 		.block_size = 4096,
 	};
 	int err;
 	int i;
-	struct reftable_log_record log = {
-		.refname = refname,
-		.update_index = 0xa,
-		.value_type = REFTABLE_LOG_UPDATE,
-		.value.update = {
-			.name = name,
-			.email = email,
-			.tz_offset = 100,
-			.time = 0x5e430672,
-			.message = message,
-		},
-	};
+	struct reftable_log_record
+		log = { .refname = (char *) "refs/heads/master",
+			.update_index = 0xa,
+			.value_type = REFTABLE_LOG_UPDATE,
+			.value = { .update = {
+					   .name = (char *) "Han-Wen Nienhuys",
+					   .email = (char *) "hanwen@google.com",
+					   .tz_offset = 100,
+					   .time = 0x5e430672,
+					   .message = (char *) "commit: 9\n",
+				   } } };
 	struct reftable_writer *w =
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
 
@@ -155,9 +149,6 @@ static void test_log_buffer_size(void)
 
 static void test_log_overflow(void)
 {
-	char refname[] = "refs/heads/master";
-	char name[] = "Han-Wen Hienhuys";
-	char email[] = "hanwen@google.com";
 	struct strbuf buf = STRBUF_INIT;
 	char msg[256] = { 0 };
 	struct reftable_write_options opts = {
@@ -165,15 +156,15 @@ static void test_log_overflow(void)
 	};
 	int err;
 	struct reftable_log_record log = {
-		.refname = refname,
+		.refname = (char *) "refs/heads/master",
 		.update_index = 0xa,
 		.value_type = REFTABLE_LOG_UPDATE,
 		.value = {
 			.update = {
 				.old_hash = { 1 },
 				.new_hash = { 2 },
-				.name = name,
-				.email = email,
+				.name = (char *) "Han-Wen Nienhuys",
+				.email = (char *) "hanwen@google.com",
 				.tz_offset = 100,
 				.time = 0x5e430672,
 				.message = msg,
@@ -299,20 +290,17 @@ static void test_log_zlib_corruption(void)
 	struct reftable_writer *w =
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
 	const struct reftable_stats *stats = NULL;
-	char refname[] = "refname";
-	char name[] = "My Name";
-	char email[] = "myname@invalid";
 	char message[100] = { 0 };
 	int err, i, n;
 	struct reftable_log_record log = {
-		.refname = refname,
+		.refname = (char *) "refname",
 		.value_type = REFTABLE_LOG_UPDATE,
 		.value = {
 			.update = {
 				.new_hash = { 1 },
 				.old_hash = { 2 },
-				.name = name,
-				.email = email,
+				.name = (char *) "My Name",
+				.email = (char *) "myname@invalid",
 				.message = message,
 			},
 		},
@@ -739,9 +727,8 @@ static void test_write_empty_key(void)
 	struct strbuf buf = STRBUF_INIT;
 	struct reftable_writer *w =
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
-	char refname[] = "";
 	struct reftable_ref_record ref = {
-		.refname = refname,
+		.refname = (char *) "",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_DELETION,
 	};
@@ -763,21 +750,20 @@ static void test_write_key_order(void)
 	struct strbuf buf = STRBUF_INIT;
 	struct reftable_writer *w =
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
-	char a[] = "a", b[] = "b", target[] = "target";
 	struct reftable_ref_record refs[2] = {
 		{
-			.refname = b,
+			.refname = (char *) "b",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_SYMREF,
 			.value = {
-				.symref = target,
+				.symref = (char *) "target",
 			},
 		}, {
-			.refname = a,
+			.refname = (char *) "a",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_SYMREF,
 			.value = {
-				.symref = target,
+				.symref = (char *) "target",
 			},
 		}
 	};
diff --git a/reftable/stack.c b/reftable/stack.c
index a59ebe038d..09549c51c9 100644
--- a/reftable/stack.c
+++ b/reftable/stack.c
@@ -204,7 +204,8 @@ static struct reftable_reader **stack_copy_readers(struct reftable_stack *st,
 	return cur;
 }
 
-static int reftable_stack_reload_once(struct reftable_stack *st, char **names,
+static int reftable_stack_reload_once(struct reftable_stack *st,
+				      const char **names,
 				      int reuse_open)
 {
 	size_t cur_len = !st->merged ? 0 : st->merged->stack_len;
@@ -222,7 +223,7 @@ static int reftable_stack_reload_once(struct reftable_stack *st, char **names,
 
 	while (*names) {
 		struct reftable_reader *rd = NULL;
-		char *name = *names++;
+		const char *name = *names++;
 
 		/* this is linear; we assume compaction keeps the number of
 		   tables under control so this is not quadratic. */
@@ -354,7 +355,7 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
 				goto out;
 		}
 
-		err = reftable_stack_reload_once(st, names, reuse_open);
+		err = reftable_stack_reload_once(st, (const char **) names, reuse_open);
 		if (!err)
 			break;
 		if (err != REFTABLE_NOT_EXIST_ERROR)
@@ -368,7 +369,8 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
 		err = read_lines(st->list_file, &names_after);
 		if (err < 0)
 			goto out;
-		if (names_equal(names_after, names)) {
+		if (names_equal((const char **) names_after,
+				(const char **) names)) {
 			err = REFTABLE_NOT_EXIST_ERROR;
 			goto out;
 		}
diff --git a/reftable/stack_test.c b/reftable/stack_test.c
index c6d88e6ea8..4abf92636d 100644
--- a/reftable/stack_test.c
+++ b/reftable/stack_test.c
@@ -116,14 +116,13 @@ static void test_parse_names(void)
 
 static void test_names_equal(void)
 {
-	char a[] = "a", b[] = "b", c[] = "c", d[] = "d";
-	char *abc[] = { a, b, c, NULL };
-	char *abd[] = { a, b, d, NULL };
-	char *ab[] = { a, b, NULL };
-
-	EXPECT(names_equal(abc, abc));
-	EXPECT(!names_equal(abc, abd));
-	EXPECT(!names_equal(abc, ab));
+	const char *a[] = { "a", "b", "c", NULL };
+	const char *b[] = { "a", "b", "d", NULL };
+	const char *c[] = { "a", "b", NULL };
+
+	EXPECT(names_equal(a, a));
+	EXPECT(!names_equal(a, b));
+	EXPECT(!names_equal(a, c));
 }
 
 static int write_test_ref(struct reftable_writer *wr, void *arg)
@@ -156,12 +155,11 @@ static void test_reftable_stack_add_one(void)
 	};
 	struct reftable_stack *st = NULL;
 	int err;
-	char head[] = "HEAD", master[] = "master";
 	struct reftable_ref_record ref = {
-		.refname = head,
+		.refname = (char *) "HEAD",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = master,
+		.value.symref = (char *) "master",
 	};
 	struct reftable_ref_record dest = { NULL };
 	struct stat stat_result = { 0 };
@@ -217,18 +215,17 @@ static void test_reftable_stack_uptodate(void)
 	char *dir = get_tmp_dir(__LINE__);
 
 	int err;
-	char head[] = "HEAD", branch2[] = "branch2", master[] = "master";
 	struct reftable_ref_record ref1 = {
-		.refname = head,
+		.refname = (char *) "HEAD",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = master,
+		.value.symref = (char *) "master",
 	};
 	struct reftable_ref_record ref2 = {
-		.refname = branch2,
+		.refname = (char *) "branch2",
 		.update_index = 2,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = master,
+		.value.symref = (char *) "master",
 	};
 
 
@@ -265,12 +262,12 @@ static void test_reftable_stack_transaction_api(void)
 	struct reftable_stack *st = NULL;
 	int err;
 	struct reftable_addition *add = NULL;
-	char head[] = "HEAD", master[] = "master";
+
 	struct reftable_ref_record ref = {
-		.refname = head,
+		.refname = (char *) "HEAD",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = master,
+		.value.symref = (char *) "master",
 	};
 	struct reftable_ref_record dest = { NULL };
 
@@ -313,11 +310,10 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)
 	EXPECT_ERR(err);
 
 	for (i = 0; i <= n; i++) {
-		char master[] = "master";
 		struct reftable_ref_record ref = {
 			.update_index = reftable_stack_next_update_index(st),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = master,
+			.value.symref = (char *) "master",
 		};
 		char name[100];
 
@@ -359,9 +355,8 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)
 
 static void test_reftable_stack_auto_compaction_fails_gracefully(void)
 {
-	char master[] = "refs/meads/master";
 	struct reftable_ref_record ref = {
-		.refname = master,
+		.refname = (char *) "refs/heads/master",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_VAL1,
 		.value.val1 = {0x01},
@@ -409,21 +404,21 @@ static int write_error(struct reftable_writer *wr, void *arg)
 static void test_reftable_stack_update_index_check(void)
 {
 	char *dir = get_tmp_dir(__LINE__);
+
 	struct reftable_write_options cfg = { 0 };
 	struct reftable_stack *st = NULL;
 	int err;
-	char name1[] = "name1", name2[] = "name2", master[] = "master";
 	struct reftable_ref_record ref1 = {
-		.refname = name1,
+		.refname = (char *) "name1",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = master,
+		.value.symref = (char *) "master",
 	};
 	struct reftable_ref_record ref2 = {
-		.refname = name2,
+		.refname = (char *) "name2",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = master,
+		.value.symref = (char *) "master",
 	};
 
 	err = reftable_new_stack(&st, dir, cfg);
@@ -565,12 +560,8 @@ static void test_reftable_stack_log_normalize(void)
 	};
 	struct reftable_stack *st = NULL;
 	char *dir = get_tmp_dir(__LINE__);
-	char branch[] = "branch";
-	char onetwomessage[] = "one\ntwo";
-	char onemessage[] = "one";
-	char twomessage[] = "two\n";
 	struct reftable_log_record input = {
-		.refname = branch,
+		.refname = (char *) "branch",
 		.update_index = 1,
 		.value_type = REFTABLE_LOG_UPDATE,
 		.value = {
@@ -591,11 +582,11 @@ static void test_reftable_stack_log_normalize(void)
 	err = reftable_new_stack(&st, dir, cfg);
 	EXPECT_ERR(err);
 
-	input.value.update.message = onetwomessage;
+	input.value.update.message = (char *) "one\ntwo";
 	err = reftable_stack_add(st, &write_test_log, &arg);
 	EXPECT(err == REFTABLE_API_ERROR);
 
-	input.value.update.message = onemessage;
+	input.value.update.message = (char *) "one";
 	err = reftable_stack_add(st, &write_test_log, &arg);
 	EXPECT_ERR(err);
 
@@ -603,7 +594,7 @@ static void test_reftable_stack_log_normalize(void)
 	EXPECT_ERR(err);
 	EXPECT(0 == strcmp(dest.value.update.message, "one\n"));
 
-	input.value.update.message = twomessage;
+	input.value.update.message = (char *) "two\n";
 	arg.update_index = 2;
 	err = reftable_stack_add(st, &write_test_log, &arg);
 	EXPECT_ERR(err);
@@ -700,14 +691,15 @@ static void test_reftable_stack_tombstone(void)
 static void test_reftable_stack_hash_id(void)
 {
 	char *dir = get_tmp_dir(__LINE__);
+
 	struct reftable_write_options cfg = { 0 };
 	struct reftable_stack *st = NULL;
 	int err;
-	char master[] = "master", target[] = "target";
+
 	struct reftable_ref_record ref = {
-		.refname = master,
+		.refname = (char *) "master",
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = target,
+		.value.symref = (char *) "target",
 		.update_index = 1,
 	};
 	struct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID };
@@ -882,12 +874,12 @@ static void test_reftable_stack_auto_compaction(void)
 	EXPECT_ERR(err);
 
 	for (i = 0; i < N; i++) {
-		char name[100], master[] = "master";
+		char name[100];
 		struct reftable_ref_record ref = {
 			.refname = name,
 			.update_index = reftable_stack_next_update_index(st),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = master,
+			.value.symref = (char *) "master",
 		};
 		snprintf(name, sizeof(name), "branch%04d", i);
 
@@ -918,11 +910,10 @@ static void test_reftable_stack_add_performs_auto_compaction(void)
 	EXPECT_ERR(err);
 
 	for (i = 0; i <= n; i++) {
-		char master[] = "master";
 		struct reftable_ref_record ref = {
 			.update_index = reftable_stack_next_update_index(st),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = master,
+			.value.symref = (char *) "master",
 		};
 
 		/*
@@ -968,12 +959,12 @@ static void test_reftable_stack_compaction_concurrent(void)
 	EXPECT_ERR(err);
 
 	for (i = 0; i < N; i++) {
-		char name[100], master[] = "master";
+		char name[100];
 		struct reftable_ref_record ref = {
 			.refname = name,
 			.update_index = reftable_stack_next_update_index(st1),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = master,
+			.value.symref = (char *) "master",
 		};
 		snprintf(name, sizeof(name), "branch%04d", i);
 
@@ -1018,12 +1009,12 @@ static void test_reftable_stack_compaction_concurrent_clean(void)
 	EXPECT_ERR(err);
 
 	for (i = 0; i < N; i++) {
-		char name[100], master[] = "master";
+		char name[100];
 		struct reftable_ref_record ref = {
 			.refname = name,
 			.update_index = reftable_stack_next_update_index(st1),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = master,
+			.value.symref = (char *) "master",
 		};
 		snprintf(name, sizeof(name), "branch%04d", i);
 
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 3, 2024, 09:39 UTC · re: Patrick Steinhardt · lore

[PATCH v3 01/27] global: improve const correctness when assigning string constants

We're about to enable `-Wwrite-strings`, which changes the type of string constants to `const char[]`. Fix various sites where we assign such constants to non-const variables.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/bisect.c             |  3 ++-
 builtin/blame.c              |  2 +-
 builtin/bugreport.c          |  2 +-
 builtin/check-ignore.c       |  4 +--
 builtin/clone.c              |  6 ++---
 builtin/commit.c             |  6 ++---
 builtin/diagnose.c           |  2 +-
 builtin/log.c                |  2 +-
 builtin/mailsplit.c          |  4 +--
 builtin/pull.c               | 52 ++++++++++++++++++------------------
 builtin/receive-pack.c       |  4 +--
 builtin/revert.c             |  2 +-
 compat/regex/regcomp.c       |  2 +-
 diff.c                       |  4 +--
 diffcore-rename.c            |  6 ++---
 fmt-merge-msg.c              |  2 +-
 fsck.c                       |  2 +-
 fsck.h                       |  2 +-
 gpg-interface.c              |  2 +-
 http-backend.c               |  2 +-
 imap-send.c                  |  6 ++---
 pretty.c                     |  2 +-
 refs.c                       |  2 +-
 refs.h                       |  2 +-
 reftable/basics.c            | 15 +++++------
 reftable/basics.h            |  4 +--
 reftable/basics_test.c       |  4 +--
 reftable/record.c            |  6 ++---
 reftable/stack.c             | 10 ++++---
 reftable/stack_test.c        |  8 +++---
 run-command.c                |  2 +-
 t/helper/test-hashmap.c      |  3 ++-
 t/helper/test-json-writer.c  | 10 +++----
 t/helper/test-regex.c        |  4 +--
 t/helper/test-rot13-filter.c |  5 ++--
 t/unit-tests/t-strbuf.c      | 10 ++++---
 trailer.c                    |  2 +-
 wt-status.c                  |  2 +-
 38 files changed, 106 insertions(+), 102 deletions(-)
Show changes to 38 files +106 −102

builtin/bisect.c, builtin/blame.c, builtin/bugreport.c, builtin/check-ignore.c, builtin/clone.c, builtin/commit.c, builtin/diagnose.c, builtin/log.c, builtin/mailsplit.c, builtin/pull.c, builtin/receive-pack.c, builtin/revert.c, compat/regex/regcomp.c, diff.c, diffcore-rename.c, fmt-merge-msg.c, fsck.c, fsck.h, gpg-interface.c, http-backend.c, imap-send.c, pretty.c, refs.c, refs.h, reftable/basics.c, reftable/basics.h, reftable/basics_test.c, reftable/record.c, reftable/stack.c, reftable/stack_test.c, run-command.c, t/helper/test-hashmap.c, t/helper/test-json-writer.c, t/helper/test-regex.c, t/helper/test-rot13-filter.c, t/unit-tests/t-strbuf.c, trailer.c, wt-status.c

diff --git a/builtin/bisect.c b/builtin/bisect.c
index a58432b9d9..dabce9b542 100644
--- a/builtin/bisect.c
+++ b/builtin/bisect.c
@@ -262,7 +262,8 @@ static int bisect_reset(const char *commit)
 	return bisect_clean_state();
 }
 
-static void log_commit(FILE *fp, char *fmt, const char *state,
+static void log_commit(FILE *fp,
+		       const char *fmt, const char *state,
 		       struct commit *commit)
 {
 	struct pretty_print_context pp = {0};
diff --git a/builtin/blame.c b/builtin/blame.c
index 838cd476be..98c7629b6a 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -134,7 +134,7 @@ static void get_ac_line(const char *inbuf, const char *what,
 {
 	struct ident_split ident;
 	size_t len, maillen, namelen;
-	char *tmp, *endp;
+	const char *tmp, *endp;
 	const char *namebuf, *mailbuf;
 
 	tmp = strstr(inbuf, what);
diff --git a/builtin/bugreport.c b/builtin/bugreport.c
index 25f860a0d9..b3cc77af53 100644
--- a/builtin/bugreport.c
+++ b/builtin/bugreport.c
@@ -107,7 +107,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix)
 	struct tm tm;
 	enum diagnose_mode diagnose = DIAGNOSE_NONE;
 	char *option_output = NULL;
-	char *option_suffix = "%Y-%m-%d-%H%M";
+	const char *option_suffix = "%Y-%m-%d-%H%M";
 	const char *user_relative_path = NULL;
 	char *prefixed_filename;
 	size_t output_path_len;
diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c
index 6c43430ec4..2bda6a1d46 100644
--- a/builtin/check-ignore.c
+++ b/builtin/check-ignore.c
@@ -35,8 +35,8 @@ static const struct option check_ignore_options[] = {
 
 static void output_pattern(const char *path, struct path_pattern *pattern)
 {
-	char *bang  = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE)  ? "!" : "";
-	char *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? "/" : "";
+	const char *bang  = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE)  ? "!" : "";
+	const char *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? "/" : "";
 	if (!nul_term_line) {
 		if (!verbose) {
 			write_name_quoted(path, stdout, '\n');
diff --git a/builtin/clone.c b/builtin/clone.c
index 23993b905b..92ab7d7165 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -71,7 +71,7 @@ static char *option_branch = NULL;
 static struct string_list option_not = STRING_LIST_INIT_NODUP;
 static const char *real_git_dir;
 static const char *ref_format;
-static char *option_upload_pack = "git-upload-pack";
+static const char *option_upload_pack = "git-upload-pack";
 static int option_verbosity;
 static int option_progress = -1;
 static int option_sparse_checkout;
@@ -177,8 +177,8 @@ static struct option builtin_clone_options[] = {
 
 static const char *get_repo_path_1(struct strbuf *path, int *is_bundle)
 {
-	static char *suffix[] = { "/.git", "", ".git/.git", ".git" };
-	static char *bundle_suffix[] = { ".bundle", "" };
+	static const char *suffix[] = { "/.git", "", ".git/.git", ".git" };
+	static const char *bundle_suffix[] = { ".bundle", "" };
 	size_t baselen = path->len;
 	struct stat st;
 	int i;
diff --git a/builtin/commit.c b/builtin/commit.c
index f53e7e86ff..75c741173e 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,7 +113,7 @@ static char *template_file;
  * the commit message and/or authorship.
  */
 static const char *author_message, *author_message_buffer;
-static char *edit_message, *use_message;
+static const char *edit_message, *use_message;
 static char *fixup_message, *fixup_commit, *squash_message;
 static const char *fixup_prefix;
 static int all, also, interactive, patch_interactive, only, amend, signoff;
@@ -121,8 +121,8 @@ static int edit_flag = -1; /* unspecified */
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
-static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
-static char *sign_commit, *pathspec_from_file;
+static const char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
+static const char *sign_commit, *pathspec_from_file;
 static struct strvec trailer_args = STRVEC_INIT;
 
 /*
diff --git a/builtin/diagnose.c b/builtin/diagnose.c
index 4f22eb2b55..4857a4395b 100644
--- a/builtin/diagnose.c
+++ b/builtin/diagnose.c
@@ -18,7 +18,7 @@ int cmd_diagnose(int argc, const char **argv, const char *prefix)
 	struct tm tm;
 	enum diagnose_mode mode = DIAGNOSE_STATS;
 	char *option_output = NULL;
-	char *option_suffix = "%Y-%m-%d-%H%M";
+	const char *option_suffix = "%Y-%m-%d-%H%M";
 	char *prefixed_filename;
 
 	const struct option diagnose_options[] = {
diff --git a/builtin/log.c b/builtin/log.c
index 78a247d8a9..b8846a9458 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1283,7 +1283,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids)
 	o2->flags = flags2;
 }
 
-static void gen_message_id(struct rev_info *info, char *base)
+static void gen_message_id(struct rev_info *info, const char *base)
 {
 	struct strbuf buf = STRBUF_INIT;
 	strbuf_addf(&buf, "%s.%"PRItime".git.%s", base,
diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c
index 3af9ddb8ae..fe6dbc5d05 100644
--- a/builtin/mailsplit.c
+++ b/builtin/mailsplit.c
@@ -113,8 +113,8 @@ static int populate_maildir_list(struct string_list *list, const char *path)
 	DIR *dir;
 	struct dirent *dent;
 	char *name = NULL;
-	char *subs[] = { "cur", "new", NULL };
-	char **sub;
+	const char *subs[] = { "cur", "new", NULL };
+	const char **sub;
 	int ret = -1;
 
 	for (sub = subs; *sub; ++sub) {
diff --git a/builtin/pull.c b/builtin/pull.c
index d622202bce..2d0429f14f 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -71,48 +71,48 @@ static const char * const pull_usage[] = {
 
 /* Shared options */
 static int opt_verbosity;
-static char *opt_progress;
+static const char *opt_progress;
 static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
 static int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT;
 
 /* Options passed to git-merge or git-rebase */
 static enum rebase_type opt_rebase = -1;
-static char *opt_diffstat;
-static char *opt_log;
-static char *opt_signoff;
-static char *opt_squash;
-static char *opt_commit;
-static char *opt_edit;
-static char *cleanup_arg;
-static char *opt_ff;
-static char *opt_verify_signatures;
-static char *opt_verify;
+static const char *opt_diffstat;
+static const char *opt_log;
+static const char *opt_signoff;
+static const char *opt_squash;
+static const char *opt_commit;
+static const char *opt_edit;
+static const char *cleanup_arg;
+static const char *opt_ff;
+static const char *opt_verify_signatures;
+static const char *opt_verify;
 static int opt_autostash = -1;
 static int config_autostash;
 static int check_trust_level = 1;
 static struct strvec opt_strategies = STRVEC_INIT;
 static struct strvec opt_strategy_opts = STRVEC_INIT;
-static char *opt_gpg_sign;
+static const char *opt_gpg_sign;
 static int opt_allow_unrelated_histories;
 
 /* Options passed to git-fetch */
-static char *opt_all;
-static char *opt_append;
-static char *opt_upload_pack;
+static const char *opt_all;
+static const char *opt_append;
+static const char *opt_upload_pack;
 static int opt_force;
-static char *opt_tags;
-static char *opt_prune;
-static char *max_children;
+static const char *opt_tags;
+static const char *opt_prune;
+static const char *max_children;
 static int opt_dry_run;
-static char *opt_keep;
-static char *opt_depth;
-static char *opt_unshallow;
-static char *opt_update_shallow;
-static char *opt_refmap;
-static char *opt_ipv4;
-static char *opt_ipv6;
+static const char *opt_keep;
+static const char *opt_depth;
+static const char *opt_unshallow;
+static const char *opt_update_shallow;
+static const char *opt_refmap;
+static const char *opt_ipv4;
+static const char *opt_ipv6;
 static int opt_show_forced_updates = -1;
-static char *set_upstream;
+static const char *set_upstream;
 static struct strvec opt_fetch = STRVEC_INIT;
 
 static struct option pull_options[] = {
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 01c1f04ece..c8d12ee0a7 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1249,7 +1249,7 @@ static int run_proc_receive_hook(struct command *commands,
 	return code;
 }
 
-static char *refuse_unconfigured_deny_msg =
+static const char *refuse_unconfigured_deny_msg =
 	N_("By default, updating the current branch in a non-bare repository\n"
 	   "is denied, because it will make the index and work tree inconsistent\n"
 	   "with what you pushed, and will require 'git reset --hard' to match\n"
@@ -1269,7 +1269,7 @@ static void refuse_unconfigured_deny(void)
 	rp_error("%s", _(refuse_unconfigured_deny_msg));
 }
 
-static char *refuse_unconfigured_deny_delete_current_msg =
+static const char *refuse_unconfigured_deny_delete_current_msg =
 	N_("By default, deleting the current branch is denied, because the next\n"
 	   "'git clone' won't result in any file checked out, causing confusion.\n"
 	   "\n"
diff --git a/builtin/revert.c b/builtin/revert.c
index 53935d2c68..7bf2b4e11d 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -179,7 +179,7 @@ static int run_sequencer(int argc, const char **argv, const char *prefix,
 
 	/* Check for incompatible command line arguments */
 	if (cmd) {
-		char *this_operation;
+		const char *this_operation;
 		if (cmd == 'q')
 			this_operation = "--quit";
 		else if (cmd == 'c')
diff --git a/compat/regex/regcomp.c b/compat/regex/regcomp.c
index 2bc0f1187a..6c5d455e92 100644
--- a/compat/regex/regcomp.c
+++ b/compat/regex/regcomp.c
@@ -848,7 +848,7 @@ init_dfa (re_dfa_t *dfa, size_t pat_len)
 {
   unsigned int table_size;
 #ifndef _LIBC
-  char *codeset_name;
+  const char *codeset_name;
 #endif
 
   memset (dfa, '\0', sizeof (re_dfa_t));
diff --git a/diff.c b/diff.c
index e70301df76..ffd867ef6c 100644
--- a/diff.c
+++ b/diff.c
@@ -3764,7 +3764,7 @@ static void builtin_diff(const char *name_a,
 	return;
 }
 
-static char *get_compact_summary(const struct diff_filepair *p, int is_renamed)
+static const char *get_compact_summary(const struct diff_filepair *p, int is_renamed)
 {
 	if (!is_renamed) {
 		if (p->status == DIFF_STATUS_ADDED) {
@@ -4076,7 +4076,7 @@ static int reuse_worktree_file(struct index_state *istate,
 static int diff_populate_gitlink(struct diff_filespec *s, int size_only)
 {
 	struct strbuf buf = STRBUF_INIT;
-	char *dirty = "";
+	const char *dirty = "";
 
 	/* Are we looking at the work tree? */
 	if (s->dirty_submodule)
diff --git a/diffcore-rename.c b/diffcore-rename.c
index 5a6e2bcac7..0e1adb87df 100644
--- a/diffcore-rename.c
+++ b/diffcore-rename.c
@@ -406,7 +406,7 @@ static const char *get_highest_rename_path(struct strintmap *counts)
 	return highest_destination_dir;
 }
 
-static char *UNKNOWN_DIR = "/";  /* placeholder -- short, illegal directory */
+static const char *UNKNOWN_DIR = "/";  /* placeholder -- short, illegal directory */
 
 static int dir_rename_already_determinable(struct strintmap *counts)
 {
@@ -429,8 +429,8 @@ static int dir_rename_already_determinable(struct strintmap *counts)
 }
 
 static void increment_count(struct dir_rename_info *info,
-			    char *old_dir,
-			    char *new_dir)
+			    const char *old_dir,
+			    const char *new_dir)
 {
 	struct strintmap *counts;
 	struct strmap_entry *e;
diff --git a/fmt-merge-msg.c b/fmt-merge-msg.c
index 7d144b803a..5af63ab5ab 100644
--- a/fmt-merge-msg.c
+++ b/fmt-merge-msg.c
@@ -447,7 +447,7 @@ static void fmt_merge_msg_title(struct strbuf *out,
 				const char *current_branch)
 {
 	int i = 0;
-	char *sep = "";
+	const char *sep = "";
 
 	strbuf_addstr(out, "Merge ");
 	for (i = 0; i < srcs.nr; i++) {
diff --git a/fsck.c b/fsck.c
index 7dff41413e..61cd48aa25 100644
--- a/fsck.c
+++ b/fsck.c
@@ -1231,7 +1231,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size,
 }
 
 int fsck_buffer(const struct object_id *oid, enum object_type type,
-		void *data, unsigned long size,
+		const void *data, unsigned long size,
 		struct fsck_options *options)
 {
 	if (type == OBJ_BLOB)
diff --git a/fsck.h b/fsck.h
index 17fa2dda5d..4f0c4e6479 100644
--- a/fsck.h
+++ b/fsck.h
@@ -202,7 +202,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size,
  * struct.
  */
 int fsck_buffer(const struct object_id *oid, enum object_type,
-		void *data, unsigned long size,
+		const void *data, unsigned long size,
 		struct fsck_options *options);
 
 /*
diff --git a/gpg-interface.c b/gpg-interface.c
index 5193223714..71a9382a61 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -727,7 +727,7 @@ static int git_gpg_config(const char *var, const char *value,
 			  void *cb UNUSED)
 {
 	struct gpg_format *fmt = NULL;
-	char *fmtname = NULL;
+	const char *fmtname = NULL;
 	char *trust;
 	int ret;
 
diff --git a/http-backend.c b/http-backend.c
index 5b65287ac9..5b4dca65ed 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -753,7 +753,7 @@ static int bad_request(struct strbuf *hdr, const struct service_cmd *c)
 
 int cmd_main(int argc UNUSED, const char **argv UNUSED)
 {
-	char *method = getenv("REQUEST_METHOD");
+	const char *method = getenv("REQUEST_METHOD");
 	const char *proto_header;
 	char *dir;
 	struct service_cmd *cmd = NULL;
diff --git a/imap-send.c b/imap-send.c
index a5d1510180..8b723b34a5 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -1215,9 +1215,9 @@ static int imap_store_msg(struct imap_store *ctx, struct strbuf *msg)
 static void wrap_in_html(struct strbuf *msg)
 {
 	struct strbuf buf = STRBUF_INIT;
-	static char *content_type = "Content-Type: text/html;\n";
-	static char *pre_open = "<pre>\n";
-	static char *pre_close = "</pre>\n";
+	static const char *content_type = "Content-Type: text/html;\n";
+	static const char *pre_open = "<pre>\n";
+	static const char *pre_close = "</pre>\n";
 	const char *body = strstr(msg->buf, "\n\n");
 
 	if (!body)
diff --git a/pretty.c b/pretty.c
index 22a81506b7..ec05db5655 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1325,7 +1325,7 @@ int format_set_trailers_options(struct process_trailer_options *opts,
 static size_t parse_describe_args(const char *start, struct strvec *args)
 {
 	struct {
-		char *name;
+		const char *name;
 		enum {
 			DESCRIBE_ARG_BOOL,
 			DESCRIBE_ARG_INTEGER,
diff --git a/refs.c b/refs.c
index 8260c27cde..292e8d947e 100644
--- a/refs.c
+++ b/refs.c
@@ -159,7 +159,7 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref)
 {
 	struct ref_namespace_info *info = &ref_namespace[namespace];
 	if (info->ref_updated)
-		free(info->ref);
+		free((char *)info->ref);
 	info->ref = ref;
 	info->ref_updated = 1;
 }
diff --git a/refs.h b/refs.h
index 34568ee1fb..923f751d18 100644
--- a/refs.h
+++ b/refs.h
@@ -975,7 +975,7 @@ struct ref_store *get_worktree_ref_store(const struct worktree *wt);
  */
 
 struct ref_namespace_info {
-	char *ref;
+	const char *ref;
 	enum decoration_type decoration;
 
 	/*
diff --git a/reftable/basics.c b/reftable/basics.c
index fea711db7e..0058619ca6 100644
--- a/reftable/basics.c
+++ b/reftable/basics.c
@@ -67,9 +67,9 @@ void free_names(char **a)
 	reftable_free(a);
 }
 
-size_t names_length(char **names)
+size_t names_length(const char **names)
 {
-	char **p = names;
+	const char **p = names;
 	while (*p)
 		p++;
 	return p - names;
@@ -102,15 +102,12 @@ void parse_names(char *buf, int size, char ***namesp)
 	*namesp = names;
 }
 
-int names_equal(char **a, char **b)
+int names_equal(const char **a, const char **b)
 {
-	int i = 0;
-	for (; a[i] && b[i]; i++) {
-		if (strcmp(a[i], b[i])) {
+	size_t i = 0;
+	for (; a[i] && b[i]; i++)
+		if (strcmp(a[i], b[i]))
 			return 0;
-		}
-	}
-
 	return a[i] == b[i];
 }
 
diff --git a/reftable/basics.h b/reftable/basics.h
index 523ecd5307..c8fec68d4e 100644
--- a/reftable/basics.h
+++ b/reftable/basics.h
@@ -42,10 +42,10 @@ void free_names(char **a);
 void parse_names(char *buf, int size, char ***namesp);
 
 /* compares two NULL-terminated arrays of strings. */
-int names_equal(char **a, char **b);
+int names_equal(const char **a, const char **b);
 
 /* returns the array size of a NULL-terminated array of strings. */
-size_t names_length(char **names);
+size_t names_length(const char **names);
 
 /* Allocation routines; they invoke the functions set through
  * reftable_set_alloc() */
diff --git a/reftable/basics_test.c b/reftable/basics_test.c
index 997c4d9e01..13bc761817 100644
--- a/reftable/basics_test.c
+++ b/reftable/basics_test.c
@@ -58,8 +58,8 @@ static void test_binsearch(void)
 
 static void test_names_length(void)
 {
-	char *a[] = { "a", "b", NULL };
-	EXPECT(names_length(a) == 2);
+	const char *names[] = { "a", "b", NULL };
+	EXPECT(names_length(names) == 2);
 }
 
 static void test_parse_names_normal(void)
diff --git a/reftable/record.c b/reftable/record.c
index 5506f3e913..a2cba5ef74 100644
--- a/reftable/record.c
+++ b/reftable/record.c
@@ -116,7 +116,7 @@ static int decode_string(struct strbuf *dest, struct string_view in)
 	return start_len - in.len;
 }
 
-static int encode_string(char *str, struct string_view s)
+static int encode_string(const char *str, struct string_view s)
 {
 	struct string_view start = s;
 	int l = strlen(str);
@@ -969,9 +969,9 @@ static int reftable_log_record_decode(void *rec, struct strbuf key,
 	return REFTABLE_FORMAT_ERROR;
 }
 
-static int null_streq(char *a, char *b)
+static int null_streq(const char *a, const char *b)
 {
-	char *empty = "";
+	const char *empty = "";
 	if (!a)
 		a = empty;
 
diff --git a/reftable/stack.c b/reftable/stack.c
index a59ebe038d..09549c51c9 100644
--- a/reftable/stack.c
+++ b/reftable/stack.c
@@ -204,7 +204,8 @@ static struct reftable_reader **stack_copy_readers(struct reftable_stack *st,
 	return cur;
 }
 
-static int reftable_stack_reload_once(struct reftable_stack *st, char **names,
+static int reftable_stack_reload_once(struct reftable_stack *st,
+				      const char **names,
 				      int reuse_open)
 {
 	size_t cur_len = !st->merged ? 0 : st->merged->stack_len;
@@ -222,7 +223,7 @@ static int reftable_stack_reload_once(struct reftable_stack *st, char **names,
 
 	while (*names) {
 		struct reftable_reader *rd = NULL;
-		char *name = *names++;
+		const char *name = *names++;
 
 		/* this is linear; we assume compaction keeps the number of
 		   tables under control so this is not quadratic. */
@@ -354,7 +355,7 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
 				goto out;
 		}
 
-		err = reftable_stack_reload_once(st, names, reuse_open);
+		err = reftable_stack_reload_once(st, (const char **) names, reuse_open);
 		if (!err)
 			break;
 		if (err != REFTABLE_NOT_EXIST_ERROR)
@@ -368,7 +369,8 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
 		err = read_lines(st->list_file, &names_after);
 		if (err < 0)
 			goto out;
-		if (names_equal(names_after, names)) {
+		if (names_equal((const char **) names_after,
+				(const char **) names)) {
 			err = REFTABLE_NOT_EXIST_ERROR;
 			goto out;
 		}
diff --git a/reftable/stack_test.c b/reftable/stack_test.c
index 7889f818d1..07d89b45da 100644
--- a/reftable/stack_test.c
+++ b/reftable/stack_test.c
@@ -83,7 +83,7 @@ static void test_read_file(void)
 	char out[1024] = "line1\n\nline2\nline3";
 	int n, err;
 	char **names = NULL;
-	char *want[] = { "line1", "line2", "line3" };
+	const char *want[] = { "line1", "line2", "line3" };
 	int i = 0;
 
 	EXPECT(fd > 0);
@@ -116,9 +116,9 @@ static void test_parse_names(void)
 
 static void test_names_equal(void)
 {
-	char *a[] = { "a", "b", "c", NULL };
-	char *b[] = { "a", "b", "d", NULL };
-	char *c[] = { "a", "b", NULL };
+	const char *a[] = { "a", "b", "c", NULL };
+	const char *b[] = { "a", "b", "d", NULL };
+	const char *c[] = { "a", "b", NULL };
 
 	EXPECT(names_equal(a, a));
 	EXPECT(!names_equal(a, b));
diff --git a/run-command.c b/run-command.c
index 1b821042b4..7600531fb6 100644
--- a/run-command.c
+++ b/run-command.c
@@ -663,7 +663,7 @@ int start_command(struct child_process *cmd)
 	int need_in, need_out, need_err;
 	int fdin[2], fdout[2], fderr[2];
 	int failed_errno;
-	char *str;
+	const char *str;
 
 	/*
 	 * In case of errors we must keep the promise to close FDs
diff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c
index 0eb0b3d49c..2912899558 100644
--- a/t/helper/test-hashmap.c
+++ b/t/helper/test-hashmap.c
@@ -36,7 +36,8 @@ static int test_entry_cmp(const void *cmp_data,
 }
 
 static struct test_entry *alloc_test_entry(unsigned int hash,
-					   char *key, char *value)
+					   const char *key,
+					   const char *value)
 {
 	size_t klen = strlen(key);
 	size_t vlen = strlen(value);
diff --git a/t/helper/test-json-writer.c b/t/helper/test-json-writer.c
index afe393f597..ed52eb76bf 100644
--- a/t/helper/test-json-writer.c
+++ b/t/helper/test-json-writer.c
@@ -174,7 +174,7 @@ static void make_arr4(int pretty)
 	jw_end(&arr4);
 }
 
-static char *expect_nest1 =
+static const char *expect_nest1 =
 	"{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true},\"arr1\":[\"abc\",42,true]}";
 
 static struct json_writer nest1 = JSON_WRITER_INIT;
@@ -195,10 +195,10 @@ static void make_nest1(int pretty)
 	jw_release(&arr1);
 }
 
-static char *expect_inline1 =
+static const char *expect_inline1 =
 	"{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true},\"arr1\":[\"abc\",42,true]}";
 
-static char *pretty_inline1 =
+static const char *pretty_inline1 =
 	("{\n"
 	 "  \"obj1\": {\n"
 	 "    \"a\": \"abc\",\n"
@@ -236,10 +236,10 @@ static void make_inline1(int pretty)
 	jw_end(&inline1);
 }
 
-static char *expect_inline2 =
+static const char *expect_inline2 =
 	"[[1,2],[3,4],{\"a\":\"abc\"}]";
 
-static char *pretty_inline2 =
+static const char *pretty_inline2 =
 	("[\n"
 	 "  [\n"
 	 "    1,\n"
diff --git a/t/helper/test-regex.c b/t/helper/test-regex.c
index 80042eafc2..366bd70976 100644
--- a/t/helper/test-regex.c
+++ b/t/helper/test-regex.c
@@ -20,8 +20,8 @@ static struct reg_flag reg_flags[] = {
 
 static int test_regex_bug(void)
 {
-	char *pat = "[^={} \t]+";
-	char *str = "={}\nfred";
+	const char *pat = "[^={} \t]+";
+	const char *str = "={}\nfred";
 	regex_t r;
 	regmatch_t m[1];
 
diff --git a/t/helper/test-rot13-filter.c b/t/helper/test-rot13-filter.c
index f8d564c622..7e1d9e0ee4 100644
--- a/t/helper/test-rot13-filter.c
+++ b/t/helper/test-rot13-filter.c
@@ -136,7 +136,7 @@ static void free_delay_entries(void)
 	strmap_clear(&delay, 0);
 }
 
-static void add_delay_entry(char *pathname, int count, int requested)
+static void add_delay_entry(const char *pathname, int count, int requested)
 {
 	struct delay_entry *entry = xcalloc(1, sizeof(*entry));
 	entry->count = count;
@@ -189,7 +189,8 @@ static void reply_list_available_blobs_cmd(void)
 static void command_loop(void)
 {
 	for (;;) {
-		char *buf, *output;
+		char *buf;
+		const char *output;
 		char *pathname;
 		struct delay_entry *entry;
 		struct strbuf input = STRBUF_INIT;
diff --git a/t/unit-tests/t-strbuf.c b/t/unit-tests/t-strbuf.c
index de434a4441..6027dafef7 100644
--- a/t/unit-tests/t-strbuf.c
+++ b/t/unit-tests/t-strbuf.c
@@ -2,7 +2,8 @@
 #include "strbuf.h"
 
 /* wrapper that supplies tests with an empty, initialized strbuf */
-static void setup(void (*f)(struct strbuf*, void*), void *data)
+static void setup(void (*f)(struct strbuf*, const void*),
+		  const void *data)
 {
 	struct strbuf buf = STRBUF_INIT;
 
@@ -13,7 +14,8 @@ static void setup(void (*f)(struct strbuf*, void*), void *data)
 }
 
 /* wrapper that supplies tests with a populated, initialized strbuf */
-static void setup_populated(void (*f)(struct strbuf*, void*), char *init_str, void *data)
+static void setup_populated(void (*f)(struct strbuf*, const void*),
+			    const char *init_str, const void *data)
 {
 	struct strbuf buf = STRBUF_INIT;
 
@@ -64,7 +66,7 @@ static void t_dynamic_init(void)
 	strbuf_release(&buf);
 }
 
-static void t_addch(struct strbuf *buf, void *data)
+static void t_addch(struct strbuf *buf, const void *data)
 {
 	const char *p_ch = data;
 	const char ch = *p_ch;
@@ -83,7 +85,7 @@ static void t_addch(struct strbuf *buf, void *data)
 	check_char(buf->buf[buf->len], ==, '\0');
 }
 
-static void t_addstr(struct strbuf *buf, void *data)
+static void t_addstr(struct strbuf *buf, const void *data)
 {
 	const char *text = data;
 	size_t len = strlen(text);
diff --git a/trailer.c b/trailer.c
index 2bcb9ba8f7..72e5136c73 100644
--- a/trailer.c
+++ b/trailer.c
@@ -63,7 +63,7 @@ struct arg_item {
 
 static LIST_HEAD(conf_head);
 
-static char *separators = ":";
+static const char *separators = ":";
 
 static int configured;
 
diff --git a/wt-status.c b/wt-status.c
index ff4be071ca..7912545e4e 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -2408,7 +2408,7 @@ static void wt_porcelain_v2_print_unmerged_entry(
 		int mode;
 		struct object_id oid;
 	} stages[3];
-	char *key;
+	const char *key;
 	char submodule_token[5];
 	char unmerged_prefix = 'u';
 	char eol_char = s->null_termination ? '\0' : '\n';
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 3, 2024, 09:39 UTC · re: Patrick Steinhardt · lore

[PATCH v3 02/27] global: convert intentionally-leaking config strings to consts

There are multiple cases where we intentionally leak config strings:
  - `struct gpg_format` is used to track programs that can be used for
    signing commits, either via gpg(1), gpgsm(1) or ssh-keygen(1). The
    user can override the commands via several config variables. As the
    array is populated once, only, and the struct memers are never
    written to or free'd.
  - `struct ll_merge_driver` is used to track merge drivers. Same as
    with the GPG format, these drivers are populated once and then
    reused. Its data is never written to or free'd, either.
  - `struct userdiff_funcname` and `struct userdiff_driver` can be
    configured via `diff.<driver>.*` to add additional drivers. Again,
    these have a global lifetime and are never written to or free'd.

All of these are intentionally kept alive and are never written to. Furthermore, all of these are being assigned both string constants in some places, and allocated strings in other places. This will cause warnings once we enable `-Wwrite-strings`, so let's mark the respective fields as `const char *` and cast away the constness when assigning those values.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 gpg-interface.c |  4 ++--
 merge-ll.c      | 11 ++++++++---
 userdiff.c      | 10 +++++-----
 userdiff.h      | 12 ++++++------
 4 files changed, 21 insertions(+), 16 deletions(-)
Show changes to 4 files +21 −16

gpg-interface.c, merge-ll.c, userdiff.c, userdiff.h

diff --git a/gpg-interface.c b/gpg-interface.c
index 71a9382a61..5c824aeb25 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -34,7 +34,7 @@ static enum signature_trust_level configured_min_trust_level = TRUST_UNDEFINED;
 
 struct gpg_format {
 	const char *name;
-	char *program;
+	const char *program;
 	const char **verify_args;
 	const char **sigs;
 	int (*verify_signed_buffer)(struct signature_check *sigc,
@@ -783,7 +783,7 @@ static int git_gpg_config(const char *var, const char *value,
 
 	if (fmtname) {
 		fmt = get_format_by_name(fmtname);
-		return git_config_string(&fmt->program, var, value);
+		return git_config_string((char **) &fmt->program, var, value);
 	}
 
 	return 0;
diff --git a/merge-ll.c b/merge-ll.c
index e29b15fa4a..180c19df67 100644
--- a/merge-ll.c
+++ b/merge-ll.c
@@ -27,7 +27,7 @@ typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *,
 
 struct ll_merge_driver {
 	const char *name;
-	char *description;
+	const char *description;
 	ll_merge_fn fn;
 	char *recursive;
 	struct ll_merge_driver *next;
@@ -304,8 +304,13 @@ static int read_merge_config(const char *var, const char *value,
 		ll_user_merge_tail = &(fn->next);
 	}
 
-	if (!strcmp("name", key))
-		return git_config_string(&fn->description, var, value);
+	if (!strcmp("name", key)) {
+		/*
+		 * The description is leaking, but that's okay as we want to
+		 * keep around the merge drivers anyway.
+		 */
+		return git_config_string((char **) &fn->description, var, value);
+	}
 
 	if (!strcmp("driver", key)) {
 		if (!value)
diff --git a/userdiff.c b/userdiff.c
index 82bc76b910..371032a413 100644
--- a/userdiff.c
+++ b/userdiff.c
@@ -399,7 +399,7 @@ static struct userdiff_driver *userdiff_find_by_namelen(const char *name, size_t
 static int parse_funcname(struct userdiff_funcname *f, const char *k,
 		const char *v, int cflags)
 {
-	if (git_config_string(&f->pattern, k, v) < 0)
+	if (git_config_string((char **) &f->pattern, k, v) < 0)
 		return -1;
 	f->cflags = cflags;
 	return 0;
@@ -445,15 +445,15 @@ int userdiff_config(const char *k, const char *v)
 	if (!strcmp(type, "binary"))
 		return parse_tristate(&drv->binary, k, v);
 	if (!strcmp(type, "command"))
-		return git_config_string(&drv->external, k, v);
+		return git_config_string((char **) &drv->external, k, v);
 	if (!strcmp(type, "textconv"))
-		return git_config_string(&drv->textconv, k, v);
+		return git_config_string((char **) &drv->textconv, k, v);
 	if (!strcmp(type, "cachetextconv"))
 		return parse_bool(&drv->textconv_want_cache, k, v);
 	if (!strcmp(type, "wordregex"))
-		return git_config_string(&drv->word_regex, k, v);
+		return git_config_string((char **) &drv->word_regex, k, v);
 	if (!strcmp(type, "algorithm"))
-		return git_config_string(&drv->algorithm, k, v);
+		return git_config_string((char **) &drv->algorithm, k, v);
 
 	return 0;
 }
diff --git a/userdiff.h b/userdiff.h
index cc8e5abfef..d726804c3e 100644
--- a/userdiff.h
+++ b/userdiff.h
@@ -7,19 +7,19 @@ struct index_state;
 struct repository;
 
 struct userdiff_funcname {
-	char *pattern;
+	const char *pattern;
 	int cflags;
 };
 
 struct userdiff_driver {
 	const char *name;
-	char *external;
-	char *algorithm;
+	const char *external;
+	const char *algorithm;
 	int binary;
 	struct userdiff_funcname funcname;
-	char *word_regex;
-	char *word_regex_multi_byte;
-	char *textconv;
+	const char *word_regex;
+	const char *word_regex_multi_byte;
+	const char *textconv;
 	struct notes_cache *textconv_cache;
 	int textconv_want_cache;
 };
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 3, 2024, 09:39 UTC · re: Patrick Steinhardt · lore

[PATCH v3 03/27] refs/reftable: stop micro-optimizing refname allocations on copy

When copying refs, we execute `write_copy_table()` to write the new table. As the names arge given to use via `arg->newname` and `arg->oldname`, respectively, we optimize away some allocations by assigning those fields to the reftable records we are about to write. This requires us to cast the input to `char *` pointers as they are in fact constant strings. Later on, we then unset the refname for all of the records before calling `reftable_log_record_release()` on them.

We also do this when assigning the "HEAD" constant, but here we do not cast because its type is `char[]` by default. It's about to be turned into `const char *` though once we enable `-Wwrite-strings` and will thus cause another warning.

It's quite dubious whether this micro-optimization really helps. We're about to write to disk anyway, which is going to be way slower than a small handful of allocations. Let's drop the optimization altogther and instead copy arguments to simplify the code and avoid the future warning with `-Wwrite-strings`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 refs/reftable-backend.c | 28 ++++++++++++++++------------
 1 file changed, 16 insertions(+), 12 deletions(-)
Show changes to refs/reftable-backend.c +16 −12
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 1af86bbdec..e77faa2b9d 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1340,10 +1340,10 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 	 * old reference.
 	 */
 	refs[0] = old_ref;
-	refs[0].refname = (char *)arg->newname;
+	refs[0].refname = xstrdup(arg->newname);
 	refs[0].update_index = creation_ts;
 	if (arg->delete_old) {
-		refs[1].refname = (char *)arg->oldname;
+		refs[1].refname = xstrdup(arg->oldname);
 		refs[1].value_type = REFTABLE_REF_DELETION;
 		refs[1].update_index = deletion_ts;
 	}
@@ -1366,7 +1366,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 		ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 		memset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));
 		fill_reftable_log_record(&logs[logs_nr], &committer_ident);
-		logs[logs_nr].refname = (char *)arg->newname;
+		logs[logs_nr].refname = xstrdup(arg->newname);
 		logs[logs_nr].update_index = deletion_ts;
 		logs[logs_nr].value.update.message =
 			xstrndup(arg->logmsg, arg->refs->write_options.block_size / 2);
@@ -1387,7 +1387,13 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 		if (append_head_reflog) {
 			ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 			logs[logs_nr] = logs[logs_nr - 1];
-			logs[logs_nr].refname = "HEAD";
+			logs[logs_nr].refname = xstrdup("HEAD");
+			logs[logs_nr].value.update.name =
+				xstrdup(logs[logs_nr].value.update.name);
+			logs[logs_nr].value.update.email =
+				xstrdup(logs[logs_nr].value.update.email);
+			logs[logs_nr].value.update.message =
+				xstrdup(logs[logs_nr].value.update.message);
 			logs_nr++;
 		}
 	}
@@ -1398,7 +1404,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 	ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 	memset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));
 	fill_reftable_log_record(&logs[logs_nr], &committer_ident);
-	logs[logs_nr].refname = (char *)arg->newname;
+	logs[logs_nr].refname = xstrdup(arg->newname);
 	logs[logs_nr].update_index = creation_ts;
 	logs[logs_nr].value.update.message =
 		xstrndup(arg->logmsg, arg->refs->write_options.block_size / 2);
@@ -1430,7 +1436,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 		 */
 		ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 		logs[logs_nr] = old_log;
-		logs[logs_nr].refname = (char *)arg->newname;
+		logs[logs_nr].refname = xstrdup(arg->newname);
 		logs_nr++;
 
 		/*
@@ -1439,7 +1445,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 		if (arg->delete_old) {
 			ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 			memset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));
-			logs[logs_nr].refname = (char *)arg->oldname;
+			logs[logs_nr].refname = xstrdup(arg->oldname);
 			logs[logs_nr].value_type = REFTABLE_LOG_DELETION;
 			logs[logs_nr].update_index = old_log.update_index;
 			logs_nr++;
@@ -1462,13 +1468,11 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 	reftable_iterator_destroy(&it);
 	string_list_clear(&skip, 0);
 	strbuf_release(&errbuf);
-	for (i = 0; i < logs_nr; i++) {
-		if (!strcmp(logs[i].refname, "HEAD"))
-			continue;
-		logs[i].refname = NULL;
+	for (i = 0; i < logs_nr; i++)
 		reftable_log_record_release(&logs[i]);
-	}
 	free(logs);
+	for (i = 0; i < ARRAY_SIZE(refs); i++)
+		reftable_ref_record_release(&refs[i]);
 	reftable_ref_record_release(&old_ref);
 	reftable_log_record_release(&old_log);
 	return ret;
-- 
2.45.1.410.g58bac47f8e.dirty
Junio C Hamano· Jun 3, 2024, 18:08 UTC · re: Patrick Steinhardt · lore

Re: [PATCH v3 03/27] refs/reftable: stop micro-optimizing refname allocations on copy

Patrick Steinhardt <ps@pks.im> writes:
> When copying refs, we execute `write_copy_table()` to write the new
> table. As the names arge given to use via `arg->newname` and
arge???
Show 16 quoted lines
> `arg->oldname`, respectively, we optimize away some allocations by
> assigning those fields to the reftable records we are about to write.
> This requires us to cast the input to `char *` pointers as they are in
> fact constant strings. Later on, we then unset the refname for all of
> the records before calling `reftable_log_record_release()` on them.
>
> We also do this when assigning the "HEAD" constant, but here we do not
> cast because its type is `char[]` by default. It's about to be turned
> into `const char *` though once we enable `-Wwrite-strings` and will
> thus cause another warning.
>
> It's quite dubious whether this micro-optimization really helps. We're
> about to write to disk anyway, which is going to be way slower than a
> small handful of allocations. Let's drop the optimization altogther and
> instead copy arguments to simplify the code and avoid the future warning
> with `-Wwrite-strings`.

It certainly makes the final clean-up part simpler, which is a good sign.

Patrick Steinhardt· Jun 3, 2024, 09:39 UTC · re: Patrick Steinhardt · lore

[PATCH v3 04/27] reftable: cast away constness when assigning constants to records

The reftable records are used in multiple ways throughout the reftable library. In many of those cases they merely act as input to a function without getting modified by it at all. Most importantly, this happens when writing records and when querying for records.

We rely on this in our tests and thus assign string constants to those fields, which is about to generate warnings as those fields are of type `char *`. While we could go through the process and instead allocate those strings in all of our tests, this feels quite unnecessary.

Instead, add casts to `char *` for all of those strings. As this is part of our tests, this also nicely serves as a demonstration that nothing writes or frees those string constants, which would otherwise lead to segfaults.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 reftable/block_test.c     |  2 +-
 reftable/merged_test.c    | 44 +++++++++++++++++------------------
 reftable/readwrite_test.c | 32 +++++++++++++-------------
 reftable/stack_test.c     | 48 +++++++++++++++++++--------------------
 4 files changed, 63 insertions(+), 63 deletions(-)
Show changes to 4 files +63 −63

reftable/block_test.c, reftable/merged_test.c, reftable/readwrite_test.c, reftable/stack_test.c

diff --git a/reftable/block_test.c b/reftable/block_test.c
index 26a9cfbc83..90aecd5a7c 100644
--- a/reftable/block_test.c
+++ b/reftable/block_test.c
@@ -42,7 +42,7 @@ static void test_block_read_write(void)
 	block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,
 			  header_off, hash_size(GIT_SHA1_FORMAT_ID));
 
-	rec.u.ref.refname = "";
+	rec.u.ref.refname = (char *) "";
 	rec.u.ref.value_type = REFTABLE_REF_DELETION;
 	n = block_writer_add(&bw, &rec);
 	EXPECT(n == REFTABLE_API_ERROR);
diff --git a/reftable/merged_test.c b/reftable/merged_test.c
index 530fc82d1c..6d1159d12d 100644
--- a/reftable/merged_test.c
+++ b/reftable/merged_test.c
@@ -124,13 +124,13 @@ static void readers_destroy(struct reftable_reader **readers, size_t n)
 static void test_merged_between(void)
 {
 	struct reftable_ref_record r1[] = { {
-		.refname = "b",
+		.refname = (char *) "b",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_VAL1,
 		.value.val1 = { 1, 2, 3, 0 },
 	} };
 	struct reftable_ref_record r2[] = { {
-		.refname = "a",
+		.refname = (char *) "a",
 		.update_index = 2,
 		.value_type = REFTABLE_REF_DELETION,
 	} };
@@ -165,38 +165,38 @@ static void test_merged(void)
 {
 	struct reftable_ref_record r1[] = {
 		{
-			.refname = "a",
+			.refname = (char *) "a",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
 		},
 		{
-			.refname = "b",
+			.refname = (char *) "b",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
 		},
 		{
-			.refname = "c",
+			.refname = (char *) "c",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
 		}
 	};
 	struct reftable_ref_record r2[] = { {
-		.refname = "a",
+		.refname = (char *) "a",
 		.update_index = 2,
 		.value_type = REFTABLE_REF_DELETION,
 	} };
 	struct reftable_ref_record r3[] = {
 		{
-			.refname = "c",
+			.refname = (char *) "c",
 			.update_index = 3,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 2 },
 		},
 		{
-			.refname = "d",
+			.refname = (char *) "d",
 			.update_index = 3,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
@@ -291,46 +291,46 @@ static void test_merged_logs(void)
 {
 	struct reftable_log_record r1[] = {
 		{
-			.refname = "a",
+			.refname = (char *) "a",
 			.update_index = 2,
 			.value_type = REFTABLE_LOG_UPDATE,
 			.value.update = {
 				.old_hash = { 2 },
 				/* deletion */
-				.name = "jane doe",
-				.email = "jane@invalid",
-				.message = "message2",
+				.name = (char *) "jane doe",
+				.email = (char *) "jane@invalid",
+				.message = (char *) "message2",
 			}
 		},
 		{
-			.refname = "a",
+			.refname = (char *) "a",
 			.update_index = 1,
 			.value_type = REFTABLE_LOG_UPDATE,
 			.value.update = {
 				.old_hash = { 1 },
 				.new_hash = { 2 },
-				.name = "jane doe",
-				.email = "jane@invalid",
-				.message = "message1",
+				.name = (char *) "jane doe",
+				.email = (char *) "jane@invalid",
+				.message = (char *) "message1",
 			}
 		},
 	};
 	struct reftable_log_record r2[] = {
 		{
-			.refname = "a",
+			.refname = (char *) "a",
 			.update_index = 3,
 			.value_type = REFTABLE_LOG_UPDATE,
 			.value.update = {
 				.new_hash = { 3 },
-				.name = "jane doe",
-				.email = "jane@invalid",
-				.message = "message3",
+				.name = (char *) "jane doe",
+				.email = (char *) "jane@invalid",
+				.message = (char *) "message3",
 			}
 		},
 	};
 	struct reftable_log_record r3[] = {
 		{
-			.refname = "a",
+			.refname = (char *) "a",
 			.update_index = 2,
 			.value_type = REFTABLE_LOG_DELETION,
 		},
@@ -406,7 +406,7 @@ static void test_default_write_opts(void)
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
 
 	struct reftable_ref_record rec = {
-		.refname = "master",
+		.refname = (char *) "master",
 		.update_index = 1,
 	};
 	int err;
diff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c
index a6dbd214c5..c55019232b 100644
--- a/reftable/readwrite_test.c
+++ b/reftable/readwrite_test.c
@@ -86,7 +86,7 @@ static void write_table(char ***names, struct strbuf *buf, int N,
 		log.update_index = update_index;
 		log.value_type = REFTABLE_LOG_UPDATE;
 		set_test_hash(log.value.update.new_hash, i);
-		log.value.update.message = "message";
+		log.value.update.message = (char *) "message";
 
 		n = reftable_writer_add_log(w, &log);
 		EXPECT(n == 0);
@@ -118,15 +118,15 @@ static void test_log_buffer_size(void)
 	int err;
 	int i;
 	struct reftable_log_record
-		log = { .refname = "refs/heads/master",
+		log = { .refname = (char *) "refs/heads/master",
 			.update_index = 0xa,
 			.value_type = REFTABLE_LOG_UPDATE,
 			.value = { .update = {
-					   .name = "Han-Wen Nienhuys",
-					   .email = "hanwen@google.com",
+					   .name = (char *) "Han-Wen Nienhuys",
+					   .email = (char *) "hanwen@google.com",
 					   .tz_offset = 100,
 					   .time = 0x5e430672,
-					   .message = "commit: 9\n",
+					   .message = (char *) "commit: 9\n",
 				   } } };
 	struct reftable_writer *w =
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
@@ -156,15 +156,15 @@ static void test_log_overflow(void)
 	};
 	int err;
 	struct reftable_log_record log = {
-		.refname = "refs/heads/master",
+		.refname = (char *) "refs/heads/master",
 		.update_index = 0xa,
 		.value_type = REFTABLE_LOG_UPDATE,
 		.value = {
 			.update = {
 				.old_hash = { 1 },
 				.new_hash = { 2 },
-				.name = "Han-Wen Nienhuys",
-				.email = "hanwen@google.com",
+				.name = (char *) "Han-Wen Nienhuys",
+				.email = (char *) "hanwen@google.com",
 				.tz_offset = 100,
 				.time = 0x5e430672,
 				.message = msg,
@@ -293,14 +293,14 @@ static void test_log_zlib_corruption(void)
 	char message[100] = { 0 };
 	int err, i, n;
 	struct reftable_log_record log = {
-		.refname = "refname",
+		.refname = (char *) "refname",
 		.value_type = REFTABLE_LOG_UPDATE,
 		.value = {
 			.update = {
 				.new_hash = { 1 },
 				.old_hash = { 2 },
-				.name = "My Name",
-				.email = "myname@invalid",
+				.name = (char *) "My Name",
+				.email = (char *) "myname@invalid",
 				.message = message,
 			},
 		},
@@ -728,7 +728,7 @@ static void test_write_empty_key(void)
 	struct reftable_writer *w =
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
 	struct reftable_ref_record ref = {
-		.refname = "",
+		.refname = (char *) "",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_DELETION,
 	};
@@ -752,18 +752,18 @@ static void test_write_key_order(void)
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
 	struct reftable_ref_record refs[2] = {
 		{
-			.refname = "b",
+			.refname = (char *) "b",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_SYMREF,
 			.value = {
-				.symref = "target",
+				.symref = (char *) "target",
 			},
 		}, {
-			.refname = "a",
+			.refname = (char *) "a",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_SYMREF,
 			.value = {
-				.symref = "target",
+				.symref = (char *) "target",
 			},
 		}
 	};
diff --git a/reftable/stack_test.c b/reftable/stack_test.c
index 07d89b45da..4abf92636d 100644
--- a/reftable/stack_test.c
+++ b/reftable/stack_test.c
@@ -156,10 +156,10 @@ static void test_reftable_stack_add_one(void)
 	struct reftable_stack *st = NULL;
 	int err;
 	struct reftable_ref_record ref = {
-		.refname = "HEAD",
+		.refname = (char *) "HEAD",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *) "master",
 	};
 	struct reftable_ref_record dest = { NULL };
 	struct stat stat_result = { 0 };
@@ -216,16 +216,16 @@ static void test_reftable_stack_uptodate(void)
 
 	int err;
 	struct reftable_ref_record ref1 = {
-		.refname = "HEAD",
+		.refname = (char *) "HEAD",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *) "master",
 	};
 	struct reftable_ref_record ref2 = {
-		.refname = "branch2",
+		.refname = (char *) "branch2",
 		.update_index = 2,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *) "master",
 	};
 
 
@@ -264,10 +264,10 @@ static void test_reftable_stack_transaction_api(void)
 	struct reftable_addition *add = NULL;
 
 	struct reftable_ref_record ref = {
-		.refname = "HEAD",
+		.refname = (char *) "HEAD",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *) "master",
 	};
 	struct reftable_ref_record dest = { NULL };
 
@@ -313,7 +313,7 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)
 		struct reftable_ref_record ref = {
 			.update_index = reftable_stack_next_update_index(st),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = (char *) "master",
 		};
 		char name[100];
 
@@ -356,7 +356,7 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)
 static void test_reftable_stack_auto_compaction_fails_gracefully(void)
 {
 	struct reftable_ref_record ref = {
-		.refname = "refs/heads/master",
+		.refname = (char *) "refs/heads/master",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_VAL1,
 		.value.val1 = {0x01},
@@ -409,16 +409,16 @@ static void test_reftable_stack_update_index_check(void)
 	struct reftable_stack *st = NULL;
 	int err;
 	struct reftable_ref_record ref1 = {
-		.refname = "name1",
+		.refname = (char *) "name1",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *) "master",
 	};
 	struct reftable_ref_record ref2 = {
-		.refname = "name2",
+		.refname = (char *) "name2",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *) "master",
 	};
 
 	err = reftable_new_stack(&st, dir, cfg);
@@ -561,7 +561,7 @@ static void test_reftable_stack_log_normalize(void)
 	struct reftable_stack *st = NULL;
 	char *dir = get_tmp_dir(__LINE__);
 	struct reftable_log_record input = {
-		.refname = "branch",
+		.refname = (char *) "branch",
 		.update_index = 1,
 		.value_type = REFTABLE_LOG_UPDATE,
 		.value = {
@@ -582,11 +582,11 @@ static void test_reftable_stack_log_normalize(void)
 	err = reftable_new_stack(&st, dir, cfg);
 	EXPECT_ERR(err);
 
-	input.value.update.message = "one\ntwo";
+	input.value.update.message = (char *) "one\ntwo";
 	err = reftable_stack_add(st, &write_test_log, &arg);
 	EXPECT(err == REFTABLE_API_ERROR);
 
-	input.value.update.message = "one";
+	input.value.update.message = (char *) "one";
 	err = reftable_stack_add(st, &write_test_log, &arg);
 	EXPECT_ERR(err);
 
@@ -594,7 +594,7 @@ static void test_reftable_stack_log_normalize(void)
 	EXPECT_ERR(err);
 	EXPECT(0 == strcmp(dest.value.update.message, "one\n"));
 
-	input.value.update.message = "two\n";
+	input.value.update.message = (char *) "two\n";
 	arg.update_index = 2;
 	err = reftable_stack_add(st, &write_test_log, &arg);
 	EXPECT_ERR(err);
@@ -697,9 +697,9 @@ static void test_reftable_stack_hash_id(void)
 	int err;
 
 	struct reftable_ref_record ref = {
-		.refname = "master",
+		.refname = (char *) "master",
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "target",
+		.value.symref = (char *) "target",
 		.update_index = 1,
 	};
 	struct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID };
@@ -879,7 +879,7 @@ static void test_reftable_stack_auto_compaction(void)
 			.refname = name,
 			.update_index = reftable_stack_next_update_index(st),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = (char *) "master",
 		};
 		snprintf(name, sizeof(name), "branch%04d", i);
 
@@ -913,7 +913,7 @@ static void test_reftable_stack_add_performs_auto_compaction(void)
 		struct reftable_ref_record ref = {
 			.update_index = reftable_stack_next_update_index(st),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = (char *) "master",
 		};
 
 		/*
@@ -964,7 +964,7 @@ static void test_reftable_stack_compaction_concurrent(void)
 			.refname = name,
 			.update_index = reftable_stack_next_update_index(st1),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = (char *) "master",
 		};
 		snprintf(name, sizeof(name), "branch%04d", i);
 
@@ -1014,7 +1014,7 @@ static void test_reftable_stack_compaction_concurrent_clean(void)
 			.refname = name,
 			.update_index = reftable_stack_next_update_index(st1),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = (char *) "master",
 		};
 		snprintf(name, sizeof(name), "branch%04d", i);
 
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 3, 2024, 09:39 UTC · re: Patrick Steinhardt · lore

[PATCH v3 05/27] refspec: remove global tag refspec structure

We have a global tag refspec structure that is used by both git-clone(1) and git-fetch(1). Initialization fo the structure will break once we enable `-Wwrite-strings`, even though the breakage is harmless. While we could just add casts, the structure isn't really required in the first place as we can simply initialize the structures at the respective callsites.

Refactor the code accordingly.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c |  8 ++++++--
 builtin/fetch.c | 11 ++++++++---
 refspec.c       | 13 -------------
 refspec.h       |  1 -
 4 files changed, 14 insertions(+), 19 deletions(-)
Show changes to 4 files +14 −19

builtin/clone.c, builtin/fetch.c, refspec.c, refspec.h

diff --git a/builtin/clone.c b/builtin/clone.c
index 92ab7d7165..bde1d284a2 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -523,6 +523,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
 	struct ref *head = copy_ref(find_ref_by_name(refs, "HEAD"));
 	struct ref *local_refs = head;
 	struct ref **tail = head ? &head->next : &local_refs;
+	struct refspec_item tag_refspec;
+
+	refspec_item_init(&tag_refspec, TAG_REFSPEC, 0);
 
 	if (option_single_branch) {
 		struct ref *remote_head = NULL;
@@ -545,7 +548,7 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
 					      &tail, 0);
 
 			/* if --branch=tag, pull the requested tag explicitly */
-			get_fetch_map(remote_head, tag_refspec, &tail, 0);
+			get_fetch_map(remote_head, &tag_refspec, &tail, 0);
 		}
 		free_refs(remote_head);
 	} else {
@@ -555,8 +558,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
 	}
 
 	if (!option_mirror && !option_single_branch && !option_no_tags)
-		get_fetch_map(refs, tag_refspec, &tail, 0);
+		get_fetch_map(refs, &tag_refspec, &tail, 0);
 
+	refspec_item_clear(&tag_refspec);
 	return local_refs;
 }
 
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 75255dc600..06b60867f5 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -582,11 +582,16 @@ static struct ref *get_ref_map(struct remote *remote,
 		}
 	}
 
-	if (tags == TAGS_SET)
+	if (tags == TAGS_SET) {
+		struct refspec_item tag_refspec;
+
 		/* also fetch all tags */
-		get_fetch_map(remote_refs, tag_refspec, &tail, 0);
-	else if (tags == TAGS_DEFAULT && *autotags)
+		refspec_item_init(&tag_refspec, TAG_REFSPEC, 0);
+		get_fetch_map(remote_refs, &tag_refspec, &tail, 0);
+		refspec_item_clear(&tag_refspec);
+	} else if (tags == TAGS_DEFAULT && *autotags) {
 		find_non_local_tags(remote_refs, NULL, &ref_map, &tail);
+	}
 
 	/* Now append any refs to be updated opportunistically: */
 	*tail = orefs;
diff --git a/refspec.c b/refspec.c
index d60932f4de..1df5de6c2f 100644
--- a/refspec.c
+++ b/refspec.c
@@ -7,19 +7,6 @@
 #include "refspec.h"
 #include "strbuf.h"
 
-static struct refspec_item s_tag_refspec = {
-	.force = 0,
-	.pattern = 1,
-	.matching = 0,
-	.exact_sha1 = 0,
-	.negative = 0,
-	.src = "refs/tags/*",
-	.dst = "refs/tags/*",
-};
-
-/* See TAG_REFSPEC for the string version */
-const struct refspec_item *tag_refspec = &s_tag_refspec;
-
 /*
  * Parses the provided refspec 'refspec' and populates the refspec_item 'item'.
  * Returns 1 if successful and 0 if the refspec is invalid.
diff --git a/refspec.h b/refspec.h
index 8c0c446993..754be45cee 100644
--- a/refspec.h
+++ b/refspec.h
@@ -2,7 +2,6 @@
 #define REFSPEC_H
 
 #define TAG_REFSPEC "refs/tags/*:refs/tags/*"
-extern const struct refspec_item *tag_refspec;
 
 /**
  * A struct refspec_item holds the parsed interpretation of a refspec.  If it
-- 
2.45.1.410.g58bac47f8e.dirty
Junio C Hamano· Jun 3, 2024, 18:11 UTC · re: Patrick Steinhardt · lore

Re: [PATCH v3 05/27] refspec: remove global tag refspec structure

Patrick Steinhardt <ps@pks.im> writes:
> We have a global tag refspec structure that is used by both git-clone(1)
> and git-fetch(1). Initialization fo the structure will break once we
"fo" -> "of".
Show 109 quoted lines
> enable `-Wwrite-strings`, even though the breakage is harmless. While we
> could just add casts, the structure isn't really required in the first
> place as we can simply initialize the structures at the respective
> callsites.
>
> Refactor the code accordingly.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  builtin/clone.c |  8 ++++++--
>  builtin/fetch.c | 11 ++++++++---
>  refspec.c       | 13 -------------
>  refspec.h       |  1 -
>  4 files changed, 14 insertions(+), 19 deletions(-)
>
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 92ab7d7165..bde1d284a2 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -523,6 +523,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
>  	struct ref *head = copy_ref(find_ref_by_name(refs, "HEAD"));
>  	struct ref *local_refs = head;
>  	struct ref **tail = head ? &head->next : &local_refs;
> +	struct refspec_item tag_refspec;
> +
> +	refspec_item_init(&tag_refspec, TAG_REFSPEC, 0);
>  
>  	if (option_single_branch) {
>  		struct ref *remote_head = NULL;
> @@ -545,7 +548,7 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
>  					      &tail, 0);
>  
>  			/* if --branch=tag, pull the requested tag explicitly */
> -			get_fetch_map(remote_head, tag_refspec, &tail, 0);
> +			get_fetch_map(remote_head, &tag_refspec, &tail, 0);
>  		}
>  		free_refs(remote_head);
>  	} else {
> @@ -555,8 +558,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
>  	}
>  
>  	if (!option_mirror && !option_single_branch && !option_no_tags)
> -		get_fetch_map(refs, tag_refspec, &tail, 0);
> +		get_fetch_map(refs, &tag_refspec, &tail, 0);
>  
> +	refspec_item_clear(&tag_refspec);
>  	return local_refs;
>  }
>  
> diff --git a/builtin/fetch.c b/builtin/fetch.c
> index 75255dc600..06b60867f5 100644
> --- a/builtin/fetch.c
> +++ b/builtin/fetch.c
> @@ -582,11 +582,16 @@ static struct ref *get_ref_map(struct remote *remote,
>  		}
>  	}
>  
> -	if (tags == TAGS_SET)
> +	if (tags == TAGS_SET) {
> +		struct refspec_item tag_refspec;
> +
>  		/* also fetch all tags */
> -		get_fetch_map(remote_refs, tag_refspec, &tail, 0);
> -	else if (tags == TAGS_DEFAULT && *autotags)
> +		refspec_item_init(&tag_refspec, TAG_REFSPEC, 0);
> +		get_fetch_map(remote_refs, &tag_refspec, &tail, 0);
> +		refspec_item_clear(&tag_refspec);
> +	} else if (tags == TAGS_DEFAULT && *autotags) {
>  		find_non_local_tags(remote_refs, NULL, &ref_map, &tail);
> +	}
>  
>  	/* Now append any refs to be updated opportunistically: */
>  	*tail = orefs;
> diff --git a/refspec.c b/refspec.c
> index d60932f4de..1df5de6c2f 100644
> --- a/refspec.c
> +++ b/refspec.c
> @@ -7,19 +7,6 @@
>  #include "refspec.h"
>  #include "strbuf.h"
>  
> -static struct refspec_item s_tag_refspec = {
> -	.force = 0,
> -	.pattern = 1,
> -	.matching = 0,
> -	.exact_sha1 = 0,
> -	.negative = 0,
> -	.src = "refs/tags/*",
> -	.dst = "refs/tags/*",
> -};
> -
> -/* See TAG_REFSPEC for the string version */
> -const struct refspec_item *tag_refspec = &s_tag_refspec;
> -
>  /*
>   * Parses the provided refspec 'refspec' and populates the refspec_item 'item'.
>   * Returns 1 if successful and 0 if the refspec is invalid.
> diff --git a/refspec.h b/refspec.h
> index 8c0c446993..754be45cee 100644
> --- a/refspec.h
> +++ b/refspec.h
> @@ -2,7 +2,6 @@
>  #define REFSPEC_H
>  
>  #define TAG_REFSPEC "refs/tags/*:refs/tags/*"
> -extern const struct refspec_item *tag_refspec;
>  
>  /**
>   * A struct refspec_item holds the parsed interpretation of a refspec.  If it
Patrick Steinhardt· Jun 3, 2024, 09:39 UTC · re: Patrick Steinhardt · lore

[PATCH v3 06/27] builtin/remote: cast away constness in `get_head_names()`

In `get_head_names()`, we assign the "refs/heads/*" string constant to `struct refspec_item::{src,dst}`, which are both non-constant pointers. Ideally, we'd refactor the code such that both of these fields were constant. But `struct refspec_item` is used for two different usecases with conflicting requirements:

  - To query for a source or destination based on the given refspec. The
    caller either sets `src` or `dst` as the branch that we want to
    search for, and the respective other field gets populated. The
    fields should be constant when being used as a query parameter,
    which is owned by the caller, and non-constant when being used as an
    out parameter, which is owned by the refspec item. This is is
    contradictory in itself already.
  - To store refspec items with their respective source and destination
    branches, in which case both fields should be owned by the struct.

Ideally, we'd split up this interface to clearly separate between querying and storing, which would enable us to clarify lifetimes of the strings. This would be a much bigger undertaking though.

Instead, accept the status quo for now and cast away the constness of the source and destination patterns. We know that those are not being written to or freed, so while this is ugly it certainly is fine for now.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/remote.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)
Show changes to builtin/remote.c +6 −6
diff --git a/builtin/remote.c b/builtin/remote.c
index d52b1c0e10..b44f580b8c 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -493,12 +493,13 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat
 {
 	struct ref *ref, *matches;
 	struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;
-	struct refspec_item refspec;
+	struct refspec_item refspec = {
+		.force = 0,
+		.pattern = 1,
+		.src = (char *) "refs/heads/*",
+		.dst = (char *) "refs/heads/*",
+	};
 
-	memset(&refspec, 0, sizeof(refspec));
-	refspec.force = 0;
-	refspec.pattern = 1;
-	refspec.src = refspec.dst = "refs/heads/*";
 	get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
 	matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
 				    fetch_map, 1);
@@ -507,7 +508,6 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat
 
 	free_refs(fetch_map);
 	free_refs(matches);
-
 	return 0;
 }
 
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 3, 2024, 09:39 UTC · re: Patrick Steinhardt · lore

[PATCH v3 07/27] diff: cast string constant in `fill_textconv()`

The `fill_textconv()` function is responsible for converting an input file with a textconv driver, which is then passed to the caller. Weirdly though, the function also handles the case where there is no textconv driver at all. In that case, it will return either the contents of the populated filespec, or an empty string if the filespec is invalid.

These two cases have differing memory ownership semantics. When there is a textconv driver, then the result is an allocated string. Otherwise, the result is either a string constant or owned by the filespec struct. All callers are in fact aware of this weirdness and only end up freeing the output buffer when they had a textconv driver.

Ideally, we'd split up this interface to only perform the conversion via the textconv driver, and BUG in case the caller didn't provide one. This would make memory ownership semantics much more straight forward. For now though, let's simply cast the empty string constant to `char *` to avoid a warning with `-Wwrite-strings`. This is equivalent to the same cast that we already have in `fill_mmfile()`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 diff.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to diff.c +1 −1
diff --git a/diff.c b/diff.c
index ffd867ef6c..cecda216cf 100644
--- a/diff.c
+++ b/diff.c
@@ -7235,7 +7235,7 @@ size_t fill_textconv(struct repository *r,
 
 	if (!driver) {
 		if (!DIFF_FILE_VALID(df)) {
-			*outbuf = "";
+			*outbuf = (char *) "";
 			return 0;
 		}
 		if (diff_populate_filespec(r, df, NULL))
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 3, 2024, 09:39 UTC · re: Patrick Steinhardt · lore

[PATCH v3 08/27] line-log: stop assigning string constant to file parent buffer

Stop assigning a string constant to the file parent buffer and instead assign an allocated string. While the code is fine in practice, it will break once we compile with `-Wwrite-strings`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 line-log.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
Show changes to line-log.c +3 −1
diff --git a/line-log.c b/line-log.c
index 8ff6ccb772..bd3e663c24 100644
--- a/line-log.c
+++ b/line-log.c
@@ -1032,6 +1032,7 @@ static int process_diff_filepair(struct rev_info *rev,
 	struct range_set tmp;
 	struct diff_ranges diff;
 	mmfile_t file_parent, file_target;
+	char *parent_data_to_free = NULL;
 
 	assert(pair->two->path);
 	while (rg) {
@@ -1056,7 +1057,7 @@ static int process_diff_filepair(struct rev_info *rev,
 		file_parent.ptr = pair->one->data;
 		file_parent.size = pair->one->size;
 	} else {
-		file_parent.ptr = "";
+		file_parent.ptr = parent_data_to_free = xstrdup("");
 		file_parent.size = 0;
 	}
 
@@ -1075,6 +1076,7 @@ static int process_diff_filepair(struct rev_info *rev,
 
 	diff_ranges_release(&diff);
 
+	free(parent_data_to_free);
 	return ((*diff_out)->parent.nr > 0);
 }
 
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 3, 2024, 09:39 UTC · re: Patrick Steinhardt · lore

[PATCH v3 09/27] line-log: always allocate the output prefix

The returned string by `output_prefix()` is sometimes a string constant and sometimes an allocated string. This has been fine until now because we always leak the allocated strings, and thus we never tried to free the string constant.

Fix the code to always return an allocated string and free the returned value at all callsites.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 line-log.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)
Show changes to line-log.c +11 −7
diff --git a/line-log.c b/line-log.c
index bd3e663c24..67c80b39a0 100644
--- a/line-log.c
+++ b/line-log.c
@@ -899,14 +899,12 @@ static void print_line(const char *prefix, char first,
 
 static char *output_prefix(struct diff_options *opt)
 {
-	char *prefix = "";
-
 	if (opt->output_prefix) {
 		struct strbuf *sb = opt->output_prefix(opt, opt->output_prefix_data);
-		prefix = sb->buf;
+		return sb->buf;
+	} else {
+		return xstrdup("");
 	}
-
-	return prefix;
 }
 
 static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *range)
@@ -927,7 +925,7 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang
 	const char *c_context = diff_get_color(opt->use_color, DIFF_CONTEXT);
 
 	if (!pair || !diff)
-		return;
+		goto out;
 
 	if (pair->one->oid_valid)
 		fill_line_ends(rev->diffopt.repo, pair->one, &p_lines, &p_ends);
@@ -1002,8 +1000,10 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang
 				   c_context, c_reset, opt->file);
 	}
 
+out:
 	free(p_ends);
 	free(t_ends);
+	free(prefix);
 }
 
 /*
@@ -1012,7 +1012,11 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang
  */
 static void dump_diff_hacky(struct rev_info *rev, struct line_log_data *range)
 {
-	fprintf(rev->diffopt.file, "%s\n", output_prefix(&rev->diffopt));
+	char *prefix = output_prefix(&rev->diffopt);
+
+	fprintf(rev->diffopt.file, "%s\n", prefix);
+	free(prefix);
+
 	while (range) {
 		dump_diff_hacky_one(rev, range);
 		range = range->next;
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 3, 2024, 09:39 UTC · re: Patrick Steinhardt · lore

[PATCH v3 10/27] entry: refactor how we remove items for delayed checkouts

When finalizing a delayed checkout, we sort out several strings from the passed-in string list by first assigning the empty string to those filters and then calling `string_list_remove_empty_items()`. Assigning the empty string will cause compiler warnings though as the string is a `char *` once we enable `-Wwrite-strings`.

Refactor the code to use a `NULL` pointer with `filter_string_list()` instead to avoid this warning.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 entry.c | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)
Show changes to entry.c +10 −4
diff --git a/entry.c b/entry.c
index b8c257f6f9..f291d8eee6 100644
--- a/entry.c
+++ b/entry.c
@@ -167,6 +167,11 @@ static int remove_available_paths(struct string_list_item *item, void *cb_data)
 	return !available;
 }
 
+static int string_is_not_null(struct string_list_item *item, void *data UNUSED)
+{
+	return !!item->string;
+}
+
 int finish_delayed_checkout(struct checkout *state, int show_progress)
 {
 	int errs = 0;
@@ -189,7 +194,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 			if (!async_query_available_blobs(filter->string, &available_paths)) {
 				/* Filter reported an error */
 				errs = 1;
-				filter->string = "";
+				filter->string = NULL;
 				continue;
 			}
 			if (available_paths.nr <= 0) {
@@ -199,7 +204,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 				 * filter from the list (see
 				 * "string_list_remove_empty_items" call below).
 				 */
-				filter->string = "";
+				filter->string = NULL;
 				continue;
 			}
 
@@ -225,7 +230,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 					 * Do not ask the filter for available blobs,
 					 * again, as the filter is likely buggy.
 					 */
-					filter->string = "";
+					filter->string = NULL;
 					continue;
 				}
 				ce = index_file_exists(state->istate, path->string,
@@ -239,7 +244,8 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 					errs = 1;
 			}
 		}
-		string_list_remove_empty_items(&dco->filters, 0);
+
+		filter_string_list(&dco->filters, 0, string_is_not_null, NULL);
 	}
 	stop_progress(&progress);
 	string_list_clear(&dco->filters, 0);
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 3, 2024, 09:39 UTC · re: Patrick Steinhardt · lore

[PATCH v3 11/27] ident: add casts for fallback name and GECOS

In `xgetpwuid_self()`, we return a fallback identity when it was not possible to look up the current identity. This fallback identity needs to be internal and must never be written to by the calles as specified by getpwuid(3P). As both the `pw_name` and `pw_gecos` fields are marked as non-constant though, it will cause a warning to assign constant strings to them once compiling with `-Wwrite-strings`.

Add explicit casts to avoid the warning.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 ident.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
Show changes to ident.c +2 −2
diff --git a/ident.c b/ident.c
index cc7afdbf81..caf41fb2a9 100644
--- a/ident.c
+++ b/ident.c
@@ -46,9 +46,9 @@ static struct passwd *xgetpwuid_self(int *is_bogus)
 	pw = getpwuid(getuid());
 	if (!pw) {
 		static struct passwd fallback;
-		fallback.pw_name = "unknown";
+		fallback.pw_name = (char *) "unknown";
 #ifndef NO_GECOS_IN_PWENT
-		fallback.pw_gecos = "Unknown";
+		fallback.pw_gecos = (char *) "Unknown";
 #endif
 		pw = &fallback;
 		if (is_bogus)
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 3, 2024, 09:39 UTC · re: Patrick Steinhardt · lore

[PATCH v3 12/27] object-file: mark cached object buffers as const

The buffers of cached objects are never modified, but are still stored as a non-constant pointer. This will cause a compiler warning once we enable the `-Wwrite-strings` compiler warning as we assign an empty constant string when initializing the static `empty_tree` cached object.

Convert the field to be constant. This requires us to shuffle around the code a bit because we memcpy(3P) into the allocated buffer in `pretend_object_file()`. This is easily fixed though by allocating the buffer into a temporary variable first.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 object-file.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)
Show changes to object-file.c +6 −3
diff --git a/object-file.c b/object-file.c
index 610b1f465c..3afe9fce06 100644
--- a/object-file.c
+++ b/object-file.c
@@ -277,7 +277,7 @@ int hash_algo_by_length(int len)
 static struct cached_object {
 	struct object_id oid;
 	enum object_type type;
-	void *buf;
+	const void *buf;
 	unsigned long size;
 } *cached_objects;
 static int cached_object_nr, cached_object_alloc;
@@ -1778,6 +1778,10 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,
 			struct object_id *oid)
 {
 	struct cached_object *co;
+	char *co_buf;
+
+	co_buf = xmalloc(len);
+	memcpy(co_buf, buf, len);
 
 	hash_object_file(the_hash_algo, buf, len, type, oid);
 	if (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||
@@ -1787,8 +1791,7 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,
 	co = &cached_objects[cached_object_nr++];
 	co->size = len;
 	co->type = type;
-	co->buf = xmalloc(len);
-	memcpy(co->buf, buf, len);
+	co->buf = co_buf;
 	oidcpy(&co->oid, oid);
 	return 0;
 }
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 3, 2024, 09:39 UTC · re: Patrick Steinhardt · lore

[PATCH v3 13/27] object-file: make `buf` parameter of `index_mem()` a constant

The `buf` parameter of `index_mem()` is a non-constant string. This will break once we enable `-Wwrite-strings` because we also pass constants from at least one callsite.

Adapt the parameter to be a constant. As we cannot free the buffer without casting now, this also requires us to move the lifetime of the nested buffer around.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 object-file.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)
Show changes to object-file.c +7 −7
diff --git a/object-file.c b/object-file.c
index 3afe9fce06..b5b5a59dc6 100644
--- a/object-file.c
+++ b/object-file.c
@@ -2485,12 +2485,13 @@ static int hash_format_check_report(struct fsck_options *opts UNUSED,
 }
 
 static int index_mem(struct index_state *istate,
-		     struct object_id *oid, void *buf, size_t size,
+		     struct object_id *oid,
+		     const void *buf, size_t size,
 		     enum object_type type,
 		     const char *path, unsigned flags)
 {
+	struct strbuf nbuf = STRBUF_INIT;
 	int ret = 0;
-	int re_allocated = 0;
 	int write_object = flags & HASH_WRITE_OBJECT;
 
 	if (!type)
@@ -2500,11 +2501,10 @@ static int index_mem(struct index_state *istate,
 	 * Convert blobs to git internal format
 	 */
 	if ((type == OBJ_BLOB) && path) {
-		struct strbuf nbuf = STRBUF_INIT;
 		if (convert_to_git(istate, path, buf, size, &nbuf,
 				   get_conv_flags(flags))) {
-			buf = strbuf_detach(&nbuf, &size);
-			re_allocated = 1;
+			buf = nbuf.buf;
+			size = nbuf.len;
 		}
 	}
 	if (flags & HASH_FORMAT_CHECK) {
@@ -2521,8 +2521,8 @@ static int index_mem(struct index_state *istate,
 		ret = write_object_file(buf, size, type, oid);
 	else
 		hash_object_file(the_hash_algo, buf, size, type, oid);
-	if (re_allocated)
-		free(buf);
+
+	strbuf_release(&nbuf);
 	return ret;
 }
 
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 3, 2024, 09:40 UTC · re: Patrick Steinhardt · lore

[PATCH v3 14/27] pretty: add casts for decoration option pointers

The `struct decoration_options` have a prefix and suffix field which are both non-constant, but we assign a constant pointer to them. This is safe to do because we pass them to `format_decorations()`, which never modifies these pointers, and then immediately discard the structure. Add explicit casts to avoid compilation warnings with `-Wwrite-strings`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 pretty.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
Show changes to pretty.c +2 −2
diff --git a/pretty.c b/pretty.c
index ec05db5655..1df9d635fb 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1584,8 +1584,8 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
 	case 'D':
 		{
 			const struct decoration_options opts = {
-				.prefix = "",
-				.suffix = ""
+				.prefix = (char *) "",
+				.suffix = (char *) "",
 			};
 
 			format_decorations(sb, commit, c->auto_color, &opts);
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 3, 2024, 09:40 UTC · re: Patrick Steinhardt · lore

[PATCH v3 15/27] compat/win32: fix const-correctness with string constants

Adjust various places in our Win32 compatibility layer where we are not assigning string constants to `const char *` variables.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 compat/basename.c | 18 ++++++++++++++++--
 compat/mingw.c    | 28 ++++++++++++++++------------
 compat/winansi.c  |  2 +-
 3 files changed, 33 insertions(+), 15 deletions(-)
Show changes to 3 files +33 −15

compat/basename.c, compat/mingw.c, compat/winansi.c

diff --git a/compat/basename.c b/compat/basename.c
index 96bd9533b4..5aa320306b 100644
--- a/compat/basename.c
+++ b/compat/basename.c
@@ -1,6 +1,8 @@
 #include "../git-compat-util.h"
 #include "../strbuf.h"
 
+static char current_directory[] = ".";
+
 /* Adapted from libiberty's basename.c.  */
 char *gitbasename (char *path)
 {
@@ -10,7 +12,13 @@ char *gitbasename (char *path)
 		skip_dos_drive_prefix(&path);
 
 	if (!path || !*path)
-		return ".";
+		/*
+		 * basename(3P) is mis-specified because it returns a
+		 * non-constant pointer even though it is specified to return a
+		 * pointer to internal memory at times. The cast is a result of
+		 * that.
+		 */
+		return (char *) "";
 
 	for (base = path; *path; path++) {
 		if (!is_dir_sep(*path))
@@ -34,7 +42,13 @@ char *gitdirname(char *path)
 	int dos_drive_prefix;
 
 	if (!p)
-		return ".";
+		/*
+		 * dirname(3P) is mis-specified because it returns a
+		 * non-constant pointer even though it is specified to return a
+		 * pointer to internal memory at times. The cast is a result of
+		 * that.
+		 */
+		return (char *) "";
 
 	if ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p)
 		goto dot;
diff --git a/compat/mingw.c b/compat/mingw.c
index 6b06ea540f..d378cd04cb 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -2279,7 +2279,11 @@ struct passwd *getpwuid(int uid)
 	p->pw_name = user_name;
 	p->pw_gecos = get_extended_user_info(NameDisplay);
 	if (!p->pw_gecos)
-		p->pw_gecos = "unknown";
+		/*
+		 * Data returned by getpwuid(3P) is treated as internal and
+		 * must never be written to or freed.
+		 */
+		p->pw_gecos = (char *) "unknown";
 	p->pw_dir = NULL;
 
 	initialized = 1;
@@ -2800,16 +2804,16 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)
 			strbuf_addf(report, "'%s' is on a file system that does "
 				    "not record ownership\n", path);
 		} else if (report) {
-			LPSTR str1, str2, str3, str4, to_free1 = NULL,
-			    to_free3 = NULL, to_local_free2 = NULL,
-			    to_local_free4 = NULL;
+			PCSTR str1, str2, str3, str4;
+			LPSTR to_free1 = NULL, to_free3 = NULL,
+			    to_local_free2 = NULL, to_local_free4 = NULL;
 
-			if (user_sid_to_user_name(sid, &str1))
-				to_free1 = str1;
+			if (user_sid_to_user_name(sid, &to_free1))
+				str1 = to_free1;
 			else
 				str1 = "(inconvertible)";
-			if (ConvertSidToStringSidA(sid, &str2))
-				to_local_free2 = str2;
+			if (ConvertSidToStringSidA(sid, &to_local_free2))
+				str2 = to_local_free2;
 			else
 				str2 = "(inconvertible)";
 
@@ -2822,13 +2826,13 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)
 				str4 = "(invalid)";
 			} else {
 				if (user_sid_to_user_name(current_user_sid,
-							  &str3))
-					to_free3 = str3;
+							  &to_free3))
+					str3 = to_free3;
 				else
 					str3 = "(inconvertible)";
 				if (ConvertSidToStringSidA(current_user_sid,
-							   &str4))
-					to_local_free4 = str4;
+							   &to_local_free4))
+					str4 = to_local_free4;
 				else
 					str4 = "(inconvertible)";
 			}
diff --git a/compat/winansi.c b/compat/winansi.c
index f83610f684..575813bde8 100644
--- a/compat/winansi.c
+++ b/compat/winansi.c
@@ -139,7 +139,7 @@ static void write_console(unsigned char *str, size_t len)
 	/* convert utf-8 to utf-16 */
 	int wlen = xutftowcsn(wbuf, (char*) str, ARRAY_SIZE(wbuf), len);
 	if (wlen < 0) {
-		wchar_t *err = L"[invalid]";
+		const wchar_t *err = L"[invalid]";
 		WriteConsoleW(console, err, wcslen(err), &dummy, NULL);
 		return;
 	}
-- 
2.45.1.410.g58bac47f8e.dirty
Eric Sunshine· Jun 3, 2024, 16:57 UTC · re: Patrick Steinhardt · lore

Re: [PATCH v3 15/27] compat/win32: fix const-correctness with string constants

On Mon, Jun 3, 2024 at 5:46 AM Patrick Steinhardt <ps@pks.im> wrote:
Show 18 quoted lines
> Adjust various places in our Win32 compatibility layer where we are not
> assigning string constants to `const char *` variables.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> diff --git a/compat/basename.c b/compat/basename.c
> @@ -1,6 +1,8 @@
> +static char current_directory[] = ".";
> @@ -10,7 +12,13 @@ char *gitbasename (char *path)
>         if (!path || !*path)
> -               return ".";
> +               /*
> +                * basename(3P) is mis-specified because it returns a
> +                * non-constant pointer even though it is specified to return a
> +                * pointer to internal memory at times. The cast is a result of
> +                * that.
> +                */
> +               return (char *) "";

The change from returning "." to returning "" is unexplained by the commit message. Did you mean to return the newly-introduced `current_directory` instead?

Show 10 quoted lines
> @@ -34,7 +42,13 @@ char *gitdirname(char *path)
>         if (!p)
> -               return ".";
> +               /*
> +                * dirname(3P) is mis-specified because it returns a
> +                * non-constant pointer even though it is specified to return a
> +                * pointer to internal memory at times. The cast is a result of
> +                * that.
> +                */
> +               return (char *) "";
Ditto.
Junio C Hamano· Jun 3, 2024, 19:04 UTC · re: Eric Sunshine · lore

Re: [PATCH v3 15/27] compat/win32: fix const-correctness with string constants

Eric Sunshine <sunshine@sunshineco.com> writes:
Show 23 quoted lines
> On Mon, Jun 3, 2024 at 5:46 AM Patrick Steinhardt <ps@pks.im> wrote:
>> Adjust various places in our Win32 compatibility layer where we are not
>> assigning string constants to `const char *` variables.
>>
>> Signed-off-by: Patrick Steinhardt <ps@pks.im>
>> ---
>> diff --git a/compat/basename.c b/compat/basename.c
>> @@ -1,6 +1,8 @@
>> +static char current_directory[] = ".";
>> @@ -10,7 +12,13 @@ char *gitbasename (char *path)
>>         if (!path || !*path)
>> -               return ".";
>> +               /*
>> +                * basename(3P) is mis-specified because it returns a
>> +                * non-constant pointer even though it is specified to return a
>> +                * pointer to internal memory at times. The cast is a result of
>> +                * that.
>> +                */
>> +               return (char *) "";
>
> The change from returning "." to returning "" is unexplained by the
> commit message. Did you mean to return the newly-introduced
> `current_directory` instead?

I suspect that these places wanted to return (char *) "." instead, without introducing a new variable.

Patrick Steinhardt· Jun 4, 2024, 06:42 UTC · re: Junio C Hamano · lore

Re: [PATCH v3 15/27] compat/win32: fix const-correctness with string constants

On Mon, Jun 03, 2024 at 12:04:37PM -0700, Junio C Hamano wrote:
Show 28 quoted lines
> Eric Sunshine <sunshine@sunshineco.com> writes:
> 
> > On Mon, Jun 3, 2024 at 5:46 AM Patrick Steinhardt <ps@pks.im> wrote:
> >> Adjust various places in our Win32 compatibility layer where we are not
> >> assigning string constants to `const char *` variables.
> >>
> >> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> >> ---
> >> diff --git a/compat/basename.c b/compat/basename.c
> >> @@ -1,6 +1,8 @@
> >> +static char current_directory[] = ".";
> >> @@ -10,7 +12,13 @@ char *gitbasename (char *path)
> >>         if (!path || !*path)
> >> -               return ".";
> >> +               /*
> >> +                * basename(3P) is mis-specified because it returns a
> >> +                * non-constant pointer even though it is specified to return a
> >> +                * pointer to internal memory at times. The cast is a result of
> >> +                * that.
> >> +                */
> >> +               return (char *) "";
> >
> > The change from returning "." to returning "" is unexplained by the
> > commit message. Did you mean to return the newly-introduced
> > `current_directory` instead?
> 
> I suspect that these places wanted to return (char *) "." instead,
> without introducing a new variable.
Oof, that's bad. Thanks for catching this!
Patrick
Patrick Steinhardt· Jun 3, 2024, 09:40 UTC · re: Patrick Steinhardt · lore

[PATCH v3 16/27] http: do not assign string constant to non-const field

In `write_accept_language()`, we put all acceptable languages into an array. While all entries in that array are allocated strings, the final entry in that array is a string constant. This is fine because we explicitly skip over the last entry when freeing the array, but will cause warnings once we enable `-Wwrite-strings`.

Adapt the code to also allocate the final entry.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 http.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)
Show changes to http.c +2 −3
diff --git a/http.c b/http.c
index 67cc47d28f..2dea2d03da 100644
--- a/http.c
+++ b/http.c
@@ -1974,7 +1974,7 @@ static void write_accept_language(struct strbuf *buf)
 
 		/* add '*' */
 		REALLOC_ARRAY(language_tags, num_langs + 1);
-		language_tags[num_langs++] = "*"; /* it's OK; this won't be freed */
+		language_tags[num_langs++] = xstrdup("*");
 
 		/* compute decimal_places */
 		for (max_q = 1, decimal_places = 0;
@@ -2004,8 +2004,7 @@ static void write_accept_language(struct strbuf *buf)
 		}
 	}
 
-	/* free language tags -- last one is a static '*' */
-	for (i = 0; i < num_langs - 1; i++)
+	for (i = 0; i < num_langs; i++)
 		free(language_tags[i]);
 	free(language_tags);
 }
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 3, 2024, 09:40 UTC · re: Patrick Steinhardt · lore

[PATCH v3 17/27] parse-options: cast long name for OPTION_ALIAS

We assign the long name for OPTION_ALIAS options to a non-constant value field. We know that the variable will never be written to, but this will cause warnings once we enable `-Wwrite-strings`.

Cast away the constness to be prepared for this change.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 parse-options.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to parse-options.h +1 −1
diff --git a/parse-options.h b/parse-options.h
index bd62e20268..ae15342390 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -355,7 +355,7 @@ struct option {
 	.type = OPTION_ALIAS, \
 	.short_name = (s), \
 	.long_name = (l), \
-	.value = (source_long_name), \
+	.value = (char *)(source_long_name), \
 }
 
 #define OPT_SUBCOMMAND_F(l, v, fn, f) { \
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 3, 2024, 09:40 UTC · re: Patrick Steinhardt · lore

[PATCH v3 18/27] send-pack: always allocate receive status

In `receive_status()`, we record the reason why ref updates have been rejected by the remote via the `remote_status`. But while we allocate the assigned string when a reason was given, we assign a string constant when no reason was given.

This has been working fine so far due to two reasons:
  - We don't ever free the refs in git-send-pack(1)'
  - Remotes always give a reason, at least as implemented by Git proper.

Adapt the code to always allocate the receive status string and free the refs.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/send-pack.c | 2 ++
 send-pack.c         | 2 +-
 2 files changed, 3 insertions(+), 1 deletion(-)
Show changes to 2 files +3 −1

builtin/send-pack.c, send-pack.c

diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index 3df9eaad09..17cae6bbbd 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -336,5 +336,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
 		/* stable plumbing output; do not modify or localize */
 		fprintf(stderr, "Everything up-to-date\n");
 
+	free_refs(remote_refs);
+	free_refs(local_refs);
 	return ret;
 }
diff --git a/send-pack.c b/send-pack.c
index 37f59d4f66..88e96d000b 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -259,7 +259,7 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)
 			if (p)
 				hint->remote_status = xstrdup(p);
 			else
-				hint->remote_status = "failed";
+				hint->remote_status = xstrdup("failed");
 		} else {
 			hint->status = REF_STATUS_OK;
 			hint->remote_status = xstrdup_or_null(p);
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 3, 2024, 09:40 UTC · re: Patrick Steinhardt · lore

[PATCH v3 19/27] remote-curl: avoid assigning string constant to non-const variable

When processing remote options, we split the option line into two by searching for a space. If there is one, we replace the space with '\0', otherwise we implicitly assume that the value is "true" and thus assign a string constant.

As the return value of strchr(3P) weirdly enough is a `char *` even though it gets a `const char *` as input, the assigned-to variable also is a non-constant. This is fine though because the argument is in fact an allocated string, and thus we are allowed to modify it. But this will break once we enable `-Wwrite-strings`.

Refactor the code stop splitting the fields with '\0' altogether. Instead, we can pass the length of the option name to `set_option()` and then use strncmp(3P) instead of strcmp(3P).

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 remote-curl.c | 53 ++++++++++++++++++++++++++-------------------------
 1 file changed, 27 insertions(+), 26 deletions(-)
Show changes to remote-curl.c +27 −26
diff --git a/remote-curl.c b/remote-curl.c
index cae98384da..d0f767df8e 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -58,9 +58,9 @@ struct options {
 static struct options options;
 static struct string_list cas_options = STRING_LIST_INIT_DUP;
 
-static int set_option(const char *name, const char *value)
+static int set_option(const char *name, size_t namelen, const char *value)
 {
-	if (!strcmp(name, "verbosity")) {
+	if (!strncmp(name, "verbosity", namelen)) {
 		char *end;
 		int v = strtol(value, &end, 10);
 		if (value == end || *end)
@@ -68,7 +68,7 @@ static int set_option(const char *name, const char *value)
 		options.verbosity = v;
 		return 0;
 	}
-	else if (!strcmp(name, "progress")) {
+	else if (!strncmp(name, "progress", namelen)) {
 		if (!strcmp(value, "true"))
 			options.progress = 1;
 		else if (!strcmp(value, "false"))
@@ -77,7 +77,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "depth")) {
+	else if (!strncmp(name, "depth", namelen)) {
 		char *end;
 		unsigned long v = strtoul(value, &end, 10);
 		if (value == end || *end)
@@ -85,15 +85,15 @@ static int set_option(const char *name, const char *value)
 		options.depth = v;
 		return 0;
 	}
-	else if (!strcmp(name, "deepen-since")) {
+	else if (!strncmp(name, "deepen-since", namelen)) {
 		options.deepen_since = xstrdup(value);
 		return 0;
 	}
-	else if (!strcmp(name, "deepen-not")) {
+	else if (!strncmp(name, "deepen-not", namelen)) {
 		string_list_append(&options.deepen_not, value);
 		return 0;
 	}
-	else if (!strcmp(name, "deepen-relative")) {
+	else if (!strncmp(name, "deepen-relative", namelen)) {
 		if (!strcmp(value, "true"))
 			options.deepen_relative = 1;
 		else if (!strcmp(value, "false"))
@@ -102,7 +102,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "followtags")) {
+	else if (!strncmp(name, "followtags", namelen)) {
 		if (!strcmp(value, "true"))
 			options.followtags = 1;
 		else if (!strcmp(value, "false"))
@@ -111,7 +111,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "dry-run")) {
+	else if (!strncmp(name, "dry-run", namelen)) {
 		if (!strcmp(value, "true"))
 			options.dry_run = 1;
 		else if (!strcmp(value, "false"))
@@ -120,7 +120,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "check-connectivity")) {
+	else if (!strncmp(name, "check-connectivity", namelen)) {
 		if (!strcmp(value, "true"))
 			options.check_self_contained_and_connected = 1;
 		else if (!strcmp(value, "false"))
@@ -129,7 +129,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "cas")) {
+	else if (!strncmp(name, "cas", namelen)) {
 		struct strbuf val = STRBUF_INIT;
 		strbuf_addstr(&val, "--force-with-lease=");
 		if (*value != '"')
@@ -139,7 +139,7 @@ static int set_option(const char *name, const char *value)
 		string_list_append(&cas_options, val.buf);
 		strbuf_release(&val);
 		return 0;
-	} else if (!strcmp(name, TRANS_OPT_FORCE_IF_INCLUDES)) {
+	} else if (!strncmp(name, TRANS_OPT_FORCE_IF_INCLUDES, namelen)) {
 		if (!strcmp(value, "true"))
 			options.force_if_includes = 1;
 		else if (!strcmp(value, "false"))
@@ -147,7 +147,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "cloning")) {
+	} else if (!strncmp(name, "cloning", namelen)) {
 		if (!strcmp(value, "true"))
 			options.cloning = 1;
 		else if (!strcmp(value, "false"))
@@ -155,7 +155,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "update-shallow")) {
+	} else if (!strncmp(name, "update-shallow", namelen)) {
 		if (!strcmp(value, "true"))
 			options.update_shallow = 1;
 		else if (!strcmp(value, "false"))
@@ -163,7 +163,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "pushcert")) {
+	} else if (!strncmp(name, "pushcert", namelen)) {
 		if (!strcmp(value, "true"))
 			options.push_cert = SEND_PACK_PUSH_CERT_ALWAYS;
 		else if (!strcmp(value, "false"))
@@ -173,7 +173,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "atomic")) {
+	} else if (!strncmp(name, "atomic", namelen)) {
 		if (!strcmp(value, "true"))
 			options.atomic = 1;
 		else if (!strcmp(value, "false"))
@@ -181,7 +181,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "push-option")) {
+	} else if (!strncmp(name, "push-option", namelen)) {
 		if (*value != '"')
 			string_list_append(&options.push_options, value);
 		else {
@@ -192,7 +192,7 @@ static int set_option(const char *name, const char *value)
 						 strbuf_detach(&unquoted, NULL));
 		}
 		return 0;
-	} else if (!strcmp(name, "family")) {
+	} else if (!strncmp(name, "family", namelen)) {
 		if (!strcmp(value, "ipv4"))
 			git_curl_ipresolve = CURL_IPRESOLVE_V4;
 		else if (!strcmp(value, "ipv6"))
@@ -202,16 +202,16 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "from-promisor")) {
+	} else if (!strncmp(name, "from-promisor", namelen)) {
 		options.from_promisor = 1;
 		return 0;
-	} else if (!strcmp(name, "refetch")) {
+	} else if (!strncmp(name, "refetch", namelen)) {
 		options.refetch = 1;
 		return 0;
-	} else if (!strcmp(name, "filter")) {
+	} else if (!strncmp(name, "filter", namelen)) {
 		options.filter = xstrdup(value);
 		return 0;
-	} else if (!strcmp(name, "object-format")) {
+	} else if (!strncmp(name, "object-format", namelen)) {
 		options.object_format = 1;
 		if (strcmp(value, "true"))
 			die(_("unknown value for object-format: %s"), value);
@@ -1588,15 +1588,16 @@ int cmd_main(int argc, const char **argv)
 			parse_push(&buf);
 
 		} else if (skip_prefix(buf.buf, "option ", &arg)) {
-			char *value = strchr(arg, ' ');
+			const char *value = strchrnul(arg, ' ');
+			size_t arglen = value - arg;
 			int result;
 
-			if (value)
-				*value++ = '\0';
+			if (*value)
+				value++; /* skip over SP */
 			else
 				value = "true";
 
-			result = set_option(arg, value);
+			result = set_option(arg, arglen, value);
 			if (!result)
 				printf("ok\n");
 			else if (result < 0)
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 3, 2024, 09:40 UTC · re: Patrick Steinhardt · lore

[PATCH v3 20/27] revision: always store allocated strings in output encoding

The `git_log_output_encoding` variable can be set via the `--encoding=` option. When doing so, we conditionally either assign it to the passed value, or if the value is "none" we assign it the empty string. Depending on which of the both code paths we pick though, the variable may end up being assigned either an allocated string or a string constant.

This is somewhat risky and may easily lead to bugs when a different code path may want to reassign a new value to it, freeing the previous value. We already to this when parsing the "i18n.logoutputencoding" config in `git_default_i18n_config()`. But because the config is typically parsed before we parse command line options this has been fine so far.

Regardless of that, safeguard the code such that the variable always contains an allocated string. While at it, also free the old value in case there was any to plug a potential memory leak.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 revision.c             | 3 ++-
 t/t3900-i18n-commit.sh | 1 +
 t/t3901-i18n-patch.sh  | 1 +
 3 files changed, 4 insertions(+), 1 deletion(-)
Show changes to 3 files +4 −1

revision.c, t/t3900-i18n-commit.sh, t/t3901-i18n-patch.sh

diff --git a/revision.c b/revision.c
index 7ddf0f151a..2ee6886078 100644
--- a/revision.c
+++ b/revision.c
@@ -2650,10 +2650,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 	} else if (!strcmp(arg, "--invert-grep")) {
 		revs->grep_filter.no_body_match = 1;
 	} else if ((argcount = parse_long_opt("encoding", argv, &optarg))) {
+		free(git_log_output_encoding);
 		if (strcmp(optarg, "none"))
 			git_log_output_encoding = xstrdup(optarg);
 		else
-			git_log_output_encoding = "";
+			git_log_output_encoding = xstrdup("");
 		return argcount;
 	} else if (!strcmp(arg, "--reverse")) {
 		revs->reverse ^= 1;
diff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh
index f27d09cfd9..db7b403bc1 100755
--- a/t/t3900-i18n-commit.sh
+++ b/t/t3900-i18n-commit.sh
@@ -5,6 +5,7 @@
 
 test_description='commit and log output encodings'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 compare_with () {
diff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh
index 4b37f78829..5f0b9afc3f 100755
--- a/t/t3901-i18n-patch.sh
+++ b/t/t3901-i18n-patch.sh
@@ -8,6 +8,7 @@ test_description='i18n settings and format-patch | am pipe'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 check_encoding () {
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 3, 2024, 09:40 UTC · re: Patrick Steinhardt · lore

[PATCH v3 21/27] mailmap: always store allocated strings in mailmap blob

Same as with the preceding commit, the `git_mailmap_blob` may sometimes contain an allocated string and sometimes it may contain a string constant. This is risky and can easily lead to bugs in case the variable is getting re-assigned, where the code may then try to free the previous value to avoid memory leaks.

Safeguard the code by always storing allocated strings in the variable.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 mailmap.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to mailmap.c +1 −1
diff --git a/mailmap.c b/mailmap.c
index b2efe29b3d..3d1e092fef 100644
--- a/mailmap.c
+++ b/mailmap.c
@@ -216,7 +216,7 @@ int read_mailmap(struct string_list *map)
 	map->cmp = namemap_cmp;
 
 	if (!git_mailmap_blob && is_bare_repository())
-		git_mailmap_blob = "HEAD:.mailmap";
+		git_mailmap_blob = xstrdup("HEAD:.mailmap");
 
 	if (!startup_info->have_repository || !is_bare_repository())
 		err |= read_mailmap_file(map, ".mailmap",
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 3, 2024, 09:40 UTC · re: Patrick Steinhardt · lore

[PATCH v3 22/27] imap-send: drop global `imap_server_conf` variable

In "imap-send.c", we have a global `sturct imap_server_conf` variable that keeps track of the configuration of the IMAP server. This variable is being populated mostly via the Git configuration.

Refactor the code to allocate the structure on the stack instead of having it globally. This change allows us to track its lifetime more closely.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 imap-send.c | 57 ++++++++++++++++++++++++++++-------------------------
 1 file changed, 30 insertions(+), 27 deletions(-)
Show changes to imap-send.c +30 −27
diff --git a/imap-send.c b/imap-send.c
index 8b723b34a5..67a7a6c456 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -82,10 +82,6 @@ struct imap_server_conf {
 	char *auth_method;
 };
 
-static struct imap_server_conf server = {
-	.ssl_verify = 1,
-};
-
 struct imap_socket {
 	int fd[2];
 	SSL *ssl;
@@ -110,6 +106,7 @@ struct imap {
 };
 
 struct imap_store {
+	const struct imap_server_conf *cfg;
 	/* currently open mailbox */
 	const char *name; /* foreign! maybe preset? */
 	int uidvalidity;
@@ -194,8 +191,8 @@ static void socket_perror(const char *func, struct imap_socket *sock, int ret)
 
 #ifdef NO_OPENSSL
 static int ssl_socket_connect(struct imap_socket *sock UNUSED,
-			      int use_tls_only UNUSED,
-			      int verify UNUSED)
+			      const struct imap_server_conf *cfg,
+			      int use_tls_only UNUSED)
 {
 	fprintf(stderr, "SSL requested but SSL support not compiled in\n");
 	return -1;
@@ -250,7 +247,9 @@ static int verify_hostname(X509 *cert, const char *hostname)
 		     cname, hostname);
 }
 
-static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int verify)
+static int ssl_socket_connect(struct imap_socket *sock,
+			      const struct imap_server_conf *cfg,
+			      int use_tls_only)
 {
 #if (OPENSSL_VERSION_NUMBER >= 0x10000000L)
 	const SSL_METHOD *meth;
@@ -279,7 +278,7 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
 	if (use_tls_only)
 		SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
 
-	if (verify)
+	if (cfg->ssl_verify)
 		SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
 
 	if (!SSL_CTX_set_default_verify_paths(ctx)) {
@@ -306,9 +305,9 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
 	 * OpenSSL does not document this function, but the implementation
 	 * returns 1 on success, 0 on failure after calling SSLerr().
 	 */
-	ret = SSL_set_tlsext_host_name(sock->ssl, server.host);
+	ret = SSL_set_tlsext_host_name(sock->ssl, cfg->host);
 	if (ret != 1)
-		warning("SSL_set_tlsext_host_name(%s) failed.", server.host);
+		warning("SSL_set_tlsext_host_name(%s) failed.", cfg->host);
 #endif
 
 	ret = SSL_connect(sock->ssl);
@@ -317,12 +316,12 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
 		return -1;
 	}
 
-	if (verify) {
+	if (cfg->ssl_verify) {
 		/* make sure the hostname matches that of the certificate */
 		cert = SSL_get_peer_certificate(sock->ssl);
 		if (!cert)
 			return error("unable to get peer certificate.");
-		if (verify_hostname(cert, server.host) < 0)
+		if (verify_hostname(cert, cfg->host) < 0)
 			return -1;
 	}
 
@@ -895,7 +894,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)
 	int ret;
 	char *response;
 
-	response = cram(prompt, server.user, server.pass);
+	response = cram(prompt, ctx->cfg->user, ctx->cfg->pass);
 
 	ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));
 	if (ret != strlen(response))
@@ -935,6 +934,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
 
 	CALLOC_ARRAY(ctx, 1);
 
+	ctx->cfg = srvc;
 	ctx->imap = CALLOC_ARRAY(imap, 1);
 	imap->buf.sock.fd[0] = imap->buf.sock.fd[1] = -1;
 	imap->in_progress_append = &imap->in_progress;
@@ -1035,7 +1035,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
 		imap->buf.sock.fd[1] = dup(s);
 
 		if (srvc->use_ssl &&
-		    ssl_socket_connect(&imap->buf.sock, 0, srvc->ssl_verify)) {
+		    ssl_socket_connect(&imap->buf.sock, srvc, 0)) {
 			close(s);
 			goto bail;
 		}
@@ -1068,8 +1068,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
 		if (!srvc->use_ssl && CAP(STARTTLS)) {
 			if (imap_exec(ctx, NULL, "STARTTLS") != RESP_OK)
 				goto bail;
-			if (ssl_socket_connect(&imap->buf.sock, 1,
-					       srvc->ssl_verify))
+			if (ssl_socket_connect(&imap->buf.sock, srvc, 1))
 				goto bail;
 			/* capabilities may have changed, so get the new capabilities */
 			if (imap_exec(ctx, NULL, "CAPABILITY") != RESP_OK)
@@ -1299,23 +1298,24 @@ static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, int *ofs)
 static int git_imap_config(const char *var, const char *val,
 			   const struct config_context *ctx, void *cb)
 {
+	struct imap_server_conf *cfg = cb;
 
 	if (!strcmp("imap.sslverify", var))
-		server.ssl_verify = git_config_bool(var, val);
+		cfg->ssl_verify = git_config_bool(var, val);
 	else if (!strcmp("imap.preformattedhtml", var))
-		server.use_html = git_config_bool(var, val);
+		cfg->use_html = git_config_bool(var, val);
 	else if (!strcmp("imap.folder", var))
-		return git_config_string(&server.folder, var, val);
+		return git_config_string(&cfg->folder, var, val);
 	else if (!strcmp("imap.user", var))
-		return git_config_string(&server.user, var, val);
+		return git_config_string(&cfg->user, var, val);
 	else if (!strcmp("imap.pass", var))
-		return git_config_string(&server.pass, var, val);
+		return git_config_string(&cfg->pass, var, val);
 	else if (!strcmp("imap.tunnel", var))
-		return git_config_string(&server.tunnel, var, val);
+		return git_config_string(&cfg->tunnel, var, val);
 	else if (!strcmp("imap.authmethod", var))
-		return git_config_string(&server.auth_method, var, val);
+		return git_config_string(&cfg->auth_method, var, val);
 	else if (!strcmp("imap.port", var))
-		server.port = git_config_int(var, val, ctx->kvi);
+		cfg->port = git_config_int(var, val, ctx->kvi);
 	else if (!strcmp("imap.host", var)) {
 		if (!val) {
 			return config_error_nonbool(var);
@@ -1324,11 +1324,11 @@ static int git_imap_config(const char *var, const char *val,
 				val += 5;
 			else if (starts_with(val, "imaps:")) {
 				val += 6;
-				server.use_ssl = 1;
+				cfg->use_ssl = 1;
 			}
 			if (starts_with(val, "//"))
 				val += 2;
-			server.host = xstrdup(val);
+			cfg->host = xstrdup(val);
 		}
 	} else
 		return git_default_config(var, val, ctx, cb);
@@ -1497,12 +1497,15 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,
 
 int cmd_main(int argc, const char **argv)
 {
+	struct imap_server_conf server = {
+		.ssl_verify = 1,
+	};
 	struct strbuf all_msgs = STRBUF_INIT;
 	int total;
 	int nongit_ok;
 
 	setup_git_directory_gently(&nongit_ok);
-	git_config(git_imap_config, NULL);
+	git_config(git_imap_config, &server);
 
 	argc = parse_options(argc, (const char **)argv, "", imap_send_options, imap_send_usage, 0);
 
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 3, 2024, 09:40 UTC · re: Patrick Steinhardt · lore

[PATCH v3 23/27] imap-send: fix leaking memory in `imap_server_conf`

We never free any of the config strings that we populate into the `struct imap_server_conf`. Fix this by creating a common exit path where we can free resources.

While at it, drop the unused member `imap_server_conf::name`.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 imap-send.c | 65 ++++++++++++++++++++++++++++++++++-------------------
 1 file changed, 42 insertions(+), 23 deletions(-)
Show changes to imap-send.c +42 −23
diff --git a/imap-send.c b/imap-send.c
index 67a7a6c456..da3e7ec17e 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -69,7 +69,6 @@ static void imap_warn(const char *, ...);
 static char *next_arg(char **);
 
 struct imap_server_conf {
-	const char *name;
 	char *tunnel;
 	char *host;
 	int port;
@@ -1300,23 +1299,28 @@ static int git_imap_config(const char *var, const char *val,
 {
 	struct imap_server_conf *cfg = cb;
 
-	if (!strcmp("imap.sslverify", var))
+	if (!strcmp("imap.sslverify", var)) {
 		cfg->ssl_verify = git_config_bool(var, val);
-	else if (!strcmp("imap.preformattedhtml", var))
+	} else if (!strcmp("imap.preformattedhtml", var)) {
 		cfg->use_html = git_config_bool(var, val);
-	else if (!strcmp("imap.folder", var))
+	} else if (!strcmp("imap.folder", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->folder, var, val);
-	else if (!strcmp("imap.user", var))
+	} else if (!strcmp("imap.user", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->user, var, val);
-	else if (!strcmp("imap.pass", var))
+	} else if (!strcmp("imap.pass", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->pass, var, val);
-	else if (!strcmp("imap.tunnel", var))
+	} else if (!strcmp("imap.tunnel", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->tunnel, var, val);
-	else if (!strcmp("imap.authmethod", var))
+	} else if (!strcmp("imap.authmethod", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->auth_method, var, val);
-	else if (!strcmp("imap.port", var))
+	} else if (!strcmp("imap.port", var)) {
 		cfg->port = git_config_int(var, val, ctx->kvi);
-	else if (!strcmp("imap.host", var)) {
+	} else if (!strcmp("imap.host", var)) {
 		if (!val) {
 			return config_error_nonbool(var);
 		} else {
@@ -1330,8 +1334,9 @@ static int git_imap_config(const char *var, const char *val,
 				val += 2;
 			cfg->host = xstrdup(val);
 		}
-	} else
+	} else {
 		return git_default_config(var, val, ctx, cb);
+	}
 
 	return 0;
 }
@@ -1503,6 +1508,7 @@ int cmd_main(int argc, const char **argv)
 	struct strbuf all_msgs = STRBUF_INIT;
 	int total;
 	int nongit_ok;
+	int ret;
 
 	setup_git_directory_gently(&nongit_ok);
 	git_config(git_imap_config, &server);
@@ -1529,42 +1535,55 @@ int cmd_main(int argc, const char **argv)
 
 	if (!server.folder) {
 		fprintf(stderr, "no imap store specified\n");
-		return 1;
+		ret = 1;
+		goto out;
 	}
 	if (!server.host) {
 		if (!server.tunnel) {
 			fprintf(stderr, "no imap host specified\n");
-			return 1;
+			ret = 1;
+			goto out;
 		}
-		server.host = "tunnel";
+		server.host = xstrdup("tunnel");
 	}
 
 	/* read the messages */
 	if (strbuf_read(&all_msgs, 0, 0) < 0) {
 		error_errno(_("could not read from stdin"));
-		return 1;
+		ret = 1;
+		goto out;
 	}
 
 	if (all_msgs.len == 0) {
 		fprintf(stderr, "nothing to send\n");
-		return 1;
+		ret = 1;
+		goto out;
 	}
 
 	total = count_messages(&all_msgs);
 	if (!total) {
 		fprintf(stderr, "no messages to send\n");
-		return 1;
+		ret = 1;
+		goto out;
 	}
 
 	/* write it to the imap server */
 
 	if (server.tunnel)
-		return append_msgs_to_imap(&server, &all_msgs, total);
-
+		ret = append_msgs_to_imap(&server, &all_msgs, total);
 #ifdef USE_CURL_FOR_IMAP_SEND
-	if (use_curl)
-		return curl_append_msgs_to_imap(&server, &all_msgs, total);
+	else if (use_curl)
+		ret = curl_append_msgs_to_imap(&server, &all_msgs, total);
 #endif
-
-	return append_msgs_to_imap(&server, &all_msgs, total);
+	else
+		ret = append_msgs_to_imap(&server, &all_msgs, total);
+
+out:
+	free(server.tunnel);
+	free(server.host);
+	free(server.folder);
+	free(server.user);
+	free(server.pass);
+	free(server.auth_method);
+	return ret;
 }
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 3, 2024, 09:40 UTC · re: Patrick Steinhardt · lore

[PATCH v3 24/27] builtin/rebase: do not assign default backend to non-constant field

The `struct rebase_options::default_backend` field is a non-constant string, but is being assigned a constant via `REBASE_OPTIONS_INIT`. Refactor the code to initialize and release options via two functions `rebase_options_init()` and `rebase_options_release()`. Like this, we can easily adapt the former funnction to use `xstrdup()` on the default value without hiding it away in a macro.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rebase.c | 67 ++++++++++++++++++++++++++++--------------------
 1 file changed, 39 insertions(+), 28 deletions(-)
Show changes to builtin/rebase.c +39 −28
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 14d4f0a5e6..11f276012c 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -131,25 +131,40 @@ struct rebase_options {
 	int config_update_refs;
 };
 
-#define REBASE_OPTIONS_INIT {			  	\
-		.type = REBASE_UNSPECIFIED,	  	\
-		.empty = EMPTY_UNSPECIFIED,	  	\
-		.keep_empty = 1,			\
-		.default_backend = "merge",	  	\
-		.flags = REBASE_NO_QUIET, 		\
-		.git_am_opts = STRVEC_INIT,		\
-		.exec = STRING_LIST_INIT_NODUP,		\
-		.git_format_patch_opt = STRBUF_INIT,	\
-		.fork_point = -1,			\
-		.reapply_cherry_picks = -1,             \
-		.allow_empty_message = 1,               \
-		.autosquash = -1,                       \
-		.rebase_merges = -1,                    \
-		.config_rebase_merges = -1,             \
-		.update_refs = -1,                      \
-		.config_update_refs = -1,               \
-		.strategy_opts = STRING_LIST_INIT_NODUP,\
-	}
+static void rebase_options_init(struct rebase_options *opts)
+{
+	memset(opts, 0, sizeof(*opts));
+	opts->type = REBASE_UNSPECIFIED;
+	opts->empty = EMPTY_UNSPECIFIED;
+	opts->default_backend = xstrdup("merge");
+	opts->keep_empty = 1;
+	opts->flags = REBASE_NO_QUIET;
+	strvec_init(&opts->git_am_opts);
+	string_list_init_nodup(&opts->exec);
+	strbuf_init(&opts->git_format_patch_opt, 0);
+	opts->fork_point = -1;
+	opts->reapply_cherry_picks = -1;
+	opts->allow_empty_message = 1;
+	opts->autosquash = -1;
+	opts->rebase_merges = -1;
+	opts->config_rebase_merges = -1;
+	opts->update_refs = -1;
+	opts->config_update_refs = -1;
+	string_list_init_nodup(&opts->strategy_opts);
+}
+
+static void rebase_options_release(struct rebase_options *opts)
+{
+	free(opts->default_backend);
+	free(opts->reflog_action);
+	free(opts->head_name);
+	strvec_clear(&opts->git_am_opts);
+	free(opts->gpg_sign_opt);
+	string_list_clear(&opts->exec, 0);
+	free(opts->strategy);
+	string_list_clear(&opts->strategy_opts, 0);
+	strbuf_release(&opts->git_format_patch_opt);
+}
 
 static struct replay_opts get_replay_opts(const struct rebase_options *opts)
 {
@@ -796,6 +811,7 @@ static int rebase_config(const char *var, const char *value,
 	}
 
 	if (!strcmp(var, "rebase.backend")) {
+		FREE_AND_NULL(opts->default_backend);
 		return git_config_string(&opts->default_backend, var, value);
 	}
 
@@ -1045,7 +1061,7 @@ static int check_exec_cmd(const char *cmd)
 
 int cmd_rebase(int argc, const char **argv, const char *prefix)
 {
-	struct rebase_options options = REBASE_OPTIONS_INIT;
+	struct rebase_options options;
 	const char *branch_name;
 	int ret, flags, total_argc, in_progress = 0;
 	int keep_base = 0;
@@ -1178,6 +1194,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	};
 	int i;
 
+	rebase_options_init(&options);
+
 	if (argc == 2 && !strcmp(argv[1], "-h"))
 		usage_with_options(builtin_rebase_usage,
 				   builtin_rebase_options);
@@ -1833,14 +1851,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 cleanup:
 	strbuf_release(&buf);
 	strbuf_release(&revisions);
-	free(options.reflog_action);
-	free(options.head_name);
-	strvec_clear(&options.git_am_opts);
-	free(options.gpg_sign_opt);
-	string_list_clear(&options.exec, 0);
-	free(options.strategy);
-	string_list_clear(&options.strategy_opts, 0);
-	strbuf_release(&options.git_format_patch_opt);
+	rebase_options_release(&options);
 	free(squash_onto_name);
 	free(keep_base_onto_name);
 	return !!ret;
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 3, 2024, 09:40 UTC · re: Patrick Steinhardt · lore

[PATCH v3 25/27] builtin/rebase: always store allocated string in `options.strategy`

The `struct rebase_options::strategy` field is a `char *`, but we do end up assigning string constants to it in two cases:

  - When being passed a `--strategy=` option via the command line.
  - When being passed a strategy option via `--strategy-option=`, but
    not a strategy.
This will cause warnings once we enable `-Wwrite-strings`.

Ideally, we'd just convert the field to be a `const char *`. But we also assign to this field via the GIT_TEST_MERGE_ALGORITHM envvar, which we have to strdup(3P) into it.

Instead, refactor the code to make sure that we only ever assign allocated strings to this field.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rebase.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)
Show changes to builtin/rebase.c +7 −7
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 11f276012c..26068cf542 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1063,6 +1063,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 {
 	struct rebase_options options;
 	const char *branch_name;
+	const char *strategy_opt = NULL;
 	int ret, flags, total_argc, in_progress = 0;
 	int keep_base = 0;
 	int ok_to_skip_pre_rebase = 0;
@@ -1177,7 +1178,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			PARSE_OPT_OPTARG, parse_opt_rebase_merges),
 		OPT_BOOL(0, "fork-point", &options.fork_point,
 			 N_("use 'merge-base --fork-point' to refine upstream")),
-		OPT_STRING('s', "strategy", &options.strategy,
+		OPT_STRING('s', "strategy", &strategy_opt,
 			   N_("strategy"), N_("use the given merge strategy")),
 		OPT_STRING_LIST('X', "strategy-option", &options.strategy_opts,
 				N_("option"),
@@ -1488,13 +1489,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		}
 	}
 
-	if (options.strategy_opts.nr && !options.strategy)
-		options.strategy = "ort";
-
-	if (options.strategy) {
-		options.strategy = xstrdup(options.strategy);
+	if (strategy_opt)
+		options.strategy = xstrdup(strategy_opt);
+	else if (options.strategy_opts.nr && !options.strategy)
+		options.strategy = xstrdup("ort");
+	if (options.strategy)
 		imply_merge(&options, "--strategy");
-	}
 
 	if (options.root && !options.onto_name)
 		imply_merge(&options, "--root without --onto");
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 3, 2024, 09:41 UTC · re: Patrick Steinhardt · lore

[PATCH v3 26/27] builtin/merge: always store allocated strings in `pull_twohead`

The `pull_twohead` configuration may sometimes contain an allocated string, and sometimes it may contain a string constant. Refactor this to instead always store an allocated string such that we can release its resources without risk.

While at it, manage the lifetime of other config strings, as well. Note that we explicitly don't free `cleanup_arg` here. This is because the variable may be assigned a string constant via command line options.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/merge.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)
Show changes to builtin/merge.c +11 −7
diff --git a/builtin/merge.c b/builtin/merge.c
index daed2d4e1e..fb3eb15b89 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -611,17 +611,19 @@ static int git_merge_config(const char *k, const char *v,
 		return 0;
 	}
 
-	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
+	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat")) {
 		show_diffstat = git_config_bool(k, v);
-	else if (!strcmp(k, "merge.verifysignatures"))
+	} else if (!strcmp(k, "merge.verifysignatures")) {
 		verify_signatures = git_config_bool(k, v);
-	else if (!strcmp(k, "pull.twohead"))
+	} else if (!strcmp(k, "pull.twohead")) {
+		FREE_AND_NULL(pull_twohead);
 		return git_config_string(&pull_twohead, k, v);
-	else if (!strcmp(k, "pull.octopus"))
+	} else if (!strcmp(k, "pull.octopus")) {
+		FREE_AND_NULL(pull_octopus);
 		return git_config_string(&pull_octopus, k, v);
-	else if (!strcmp(k, "commit.cleanup"))
+	} else if (!strcmp(k, "commit.cleanup")) {
 		return git_config_string(&cleanup_arg, k, v);
-	else if (!strcmp(k, "merge.ff")) {
+	} else if (!strcmp(k, "merge.ff")) {
 		int boolval = git_parse_maybe_bool(v);
 		if (0 <= boolval) {
 			fast_forward = boolval ? FF_ALLOW : FF_NO;
@@ -1294,7 +1296,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 	if (!pull_twohead) {
 		char *default_strategy = getenv("GIT_TEST_MERGE_ALGORITHM");
 		if (default_strategy && !strcmp(default_strategy, "ort"))
-			pull_twohead = "ort";
+			pull_twohead = xstrdup("ort");
 	}
 
 	init_diff_ui_defaults();
@@ -1793,6 +1795,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 	}
 	strbuf_release(&buf);
 	free(branch_to_free);
+	free(pull_twohead);
+	free(pull_octopus);
 	discard_index(the_repository->index);
 	return ret;
 }
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 3, 2024, 09:41 UTC · re: Patrick Steinhardt · lore

[PATCH v3 27/27] config.mak.dev: enable `-Wwrite-strings` warning

Writing to string constants is undefined behaviour and must be avoided in C. Even so, the compiler does not help us with this by default because those constants are not in fact marked as `const`. This makes it rather easy to accidentally assign a constant to a non-const variable or field and then later on try to either free it or write to it.

Enable `-Wwrite-strings` to catch such mistakes. With this warning enabled, the type of string constants is changed to `const char[]` and will thus cause compiler warnings when being assigned to non-const fields and variables.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 config.mak.dev | 1 +
 1 file changed, 1 insertion(+)
Show changes to config.mak.dev +1 −0
diff --git a/config.mak.dev b/config.mak.dev
index 981304727c..1ce4c70613 100644
--- a/config.mak.dev
+++ b/config.mak.dev
@@ -37,6 +37,7 @@ DEVELOPER_CFLAGS += -Wpointer-arith
 DEVELOPER_CFLAGS += -Wstrict-prototypes
 DEVELOPER_CFLAGS += -Wunused
 DEVELOPER_CFLAGS += -Wvla
+DEVELOPER_CFLAGS += -Wwrite-strings
 DEVELOPER_CFLAGS += -fno-common
 
 ifneq ($(filter clang4,$(COMPILER_FEATURES)),)
-- 
2.45.1.410.g58bac47f8e.dirty
Junio C Hamano· Jun 3, 2024, 16:59 UTC · re: Patrick Steinhardt · lore

Re: [PATCH v3 00/27] Compile with `-Wwrite-strings`

Patrick Steinhardt <ps@pks.im> writes:
Show 5 quoted lines
> Changes compared to v2:
>
>   - I have split up the second patch into multiple patches. This was
>     done so that we can have a deeper look at the respective sites that
>     need adjustment.
We will see how well this helps reviewer.
Show 5 quoted lines
>   - I dropped the local string arrays that I used in v2 and instead now
>     use string constants with casts in some places where we expect that
>     those should never be written to. This has the benefit that we would
>     segfault in case that expectation is violated, instead of silently
>     allowing a write to those local arrays.
Nice.
>   - I adapted multiple commit messages to not only mention freeing of
>     string constants, but also that writing to string constants is
>     illegal.
Nice again.
> Due to the split-up patch the range-diff got quite messy and was barely
> readable anymore. I thus included an interdiff instead, which should be
> way easier to read.
Triply nice.
Thanks.
Patrick Steinhardt· Jun 4, 2024, 12:36 UTC · re: Patrick Steinhardt · lore

[PATCH v4 00/27] Compile with `-Wwrite-strings`

Hi,

this is another minor reroll of my patch series that prepares the code base to compile with `-Wwrite-strings`.

There are only some small changes compared to v3:
  - Fix typos in some commit messages.
  - Fix accidental conversion of "." to "" in basename/dirname
    compatibility code. Also, remove the unused variable.
Thanks for your reviews!
Patrick
Patrick Steinhardt (27):
  global: improve const correctness when assigning string constants
  global: convert intentionally-leaking config strings to consts
  refs/reftable: stop micro-optimizing refname allocations on copy
  reftable: cast away constness when assigning constants to records
  refspec: remove global tag refspec structure
  builtin/remote: cast away constness in `get_head_names()`
  diff: cast string constant in `fill_textconv()`
  line-log: stop assigning string constant to file parent buffer
  line-log: always allocate the output prefix
  entry: refactor how we remove items for delayed checkouts
  ident: add casts for fallback name and GECOS
  object-file: mark cached object buffers as const
  object-file: make `buf` parameter of `index_mem()` a constant
  pretty: add casts for decoration option pointers
  compat/win32: fix const-correctness with string constants
  http: do not assign string constant to non-const field
  parse-options: cast long name for OPTION_ALIAS
  send-pack: always allocate receive status
  remote-curl: avoid assigning string constant to non-const variable
  revision: always store allocated strings in output encoding
  mailmap: always store allocated strings in mailmap blob
  imap-send: drop global `imap_server_conf` variable
  imap-send: fix leaking memory in `imap_server_conf`
  builtin/rebase: do not assign default backend to non-constant field
  builtin/rebase: always store allocated string in `options.strategy`
  builtin/merge: always store allocated strings in `pull_twohead`
  config.mak.dev: enable `-Wwrite-strings` warning
 builtin/bisect.c             |   3 +-
 builtin/blame.c              |   2 +-
 builtin/bugreport.c          |   2 +-
 builtin/check-ignore.c       |   4 +-
 builtin/clone.c              |  14 ++--
 builtin/commit.c             |   6 +-
 builtin/diagnose.c           |   2 +-
 builtin/fetch.c              |  11 ++-
 builtin/log.c                |   2 +-
 builtin/mailsplit.c          |   4 +-
 builtin/merge.c              |  18 +++--
 builtin/pull.c               |  52 +++++++-------
 builtin/rebase.c             |  81 ++++++++++++----------
 builtin/receive-pack.c       |   4 +-
 builtin/remote.c             |  12 ++--
 builtin/revert.c             |   2 +-
 builtin/send-pack.c          |   2 +
 compat/basename.c            |  16 ++++-
 compat/mingw.c               |  28 ++++----
 compat/regex/regcomp.c       |   2 +-
 compat/winansi.c             |   2 +-
 config.mak.dev               |   1 +
 diff.c                       |   6 +-
 diffcore-rename.c            |   6 +-
 entry.c                      |  14 ++--
 fmt-merge-msg.c              |   2 +-
 fsck.c                       |   2 +-
 fsck.h                       |   2 +-
 gpg-interface.c              |   6 +-
 http-backend.c               |   2 +-
 http.c                       |   5 +-
 ident.c                      |   4 +-
 imap-send.c                  | 130 ++++++++++++++++++++---------------
 line-log.c                   |  22 +++---
 mailmap.c                    |   2 +-
 merge-ll.c                   |  11 ++-
 object-file.c                |  23 ++++---
 parse-options.h              |   2 +-
 pretty.c                     |   6 +-
 refs.c                       |   2 +-
 refs.h                       |   2 +-
 refs/reftable-backend.c      |  28 ++++----
 refspec.c                    |  13 ----
 refspec.h                    |   1 -
 reftable/basics.c            |  15 ++--
 reftable/basics.h            |   4 +-
 reftable/basics_test.c       |   4 +-
 reftable/block_test.c        |   2 +-
 reftable/merged_test.c       |  44 ++++++------
 reftable/readwrite_test.c    |  32 ++++-----
 reftable/record.c            |   6 +-
 reftable/stack.c             |  10 +--
 reftable/stack_test.c        |  56 +++++++--------
 remote-curl.c                |  53 +++++++-------
 revision.c                   |   3 +-
 run-command.c                |   2 +-
 send-pack.c                  |   2 +-
 t/helper/test-hashmap.c      |   3 +-
 t/helper/test-json-writer.c  |  10 +--
 t/helper/test-regex.c        |   4 +-
 t/helper/test-rot13-filter.c |   5 +-
 t/t3900-i18n-commit.sh       |   1 +
 t/t3901-i18n-patch.sh        |   1 +
 t/unit-tests/t-strbuf.c      |  10 +--
 trailer.c                    |   2 +-
 userdiff.c                   |  10 +--
 userdiff.h                   |  12 ++--
 wt-status.c                  |   2 +-
 68 files changed, 468 insertions(+), 386 deletions(-)
Range-diff against v3:
 1:  e01fde88fe =  1:  e01fde88fe global: improve const correctness when assigning string constants
 2:  92cb0b28c6 =  2:  92cb0b28c6 global: convert intentionally-leaking config strings to consts
 3:  085d90c8b6 !  3:  379145478c refs/reftable: stop micro-optimizing refname allocations on copy
    @@ Commit message
         refs/reftable: stop micro-optimizing refname allocations on copy
     
         When copying refs, we execute `write_copy_table()` to write the new
    -    table. As the names arge given to use via `arg->newname` and
    +    table. As the names are given to us via `arg->newname` and
         `arg->oldname`, respectively, we optimize away some allocations by
    -    assigning those fields to the reftable records we are about to write.
    -    This requires us to cast the input to `char *` pointers as they are in
    -    fact constant strings. Later on, we then unset the refname for all of
    -    the records before calling `reftable_log_record_release()` on them.
    +    assigning those fields to the reftable records we are about to write
    +    directly, without duplicating them. This requires us to cast the input
    +    to `char *` pointers as they are in fact constant strings. Later on, we
    +    then unset the refname for all of the records before calling
    +    `reftable_log_record_release()` on them.
     
         We also do this when assigning the "HEAD" constant, but here we do not
         cast because its type is `char[]` by default. It's about to be turned
 4:  8692d9d9af =  4:  d0a2a2f6c5 reftable: cast away constness when assigning constants to records
 5:  db4d062014 !  5:  ead27d3d97 refspec: remove global tag refspec structure
    @@ Commit message
         refspec: remove global tag refspec structure
     
         We have a global tag refspec structure that is used by both git-clone(1)
    -    and git-fetch(1). Initialization fo the structure will break once we
    +    and git-fetch(1). Initialization of the structure will break once we
         enable `-Wwrite-strings`, even though the breakage is harmless. While we
         could just add casts, the structure isn't really required in the first
         place as we can simply initialize the structures at the respective
 6:  6a3c8d351e =  6:  7cb5df9182 builtin/remote: cast away constness in `get_head_names()`
 7:  750429472a =  7:  6e631a9ea4 diff: cast string constant in `fill_textconv()`
 8:  cc8fa1896d =  8:  ac164651a3 line-log: stop assigning string constant to file parent buffer
 9:  03dbdd235b =  9:  b717af02f0 line-log: always allocate the output prefix
10:  6fcb7d6685 = 10:  b46dd3210d entry: refactor how we remove items for delayed checkouts
11:  81e20a7bb8 = 11:  030dbd0288 ident: add casts for fallback name and GECOS
12:  384b4c8967 = 12:  ecca8e973d object-file: mark cached object buffers as const
13:  a1e8e77641 = 13:  62f0e47f94 object-file: make `buf` parameter of `index_mem()` a constant
14:  4d95abe9cc = 14:  e057ead2b4 pretty: add casts for decoration option pointers
15:  3d92528125 ! 15:  06b6120d26 compat/win32: fix const-correctness with string constants
    @@ Commit message
         Signed-off-by: Patrick Steinhardt <ps@pks.im>
     
      ## compat/basename.c ##
    -@@
    - #include "../git-compat-util.h"
    - #include "../strbuf.h"
    - 
    -+static char current_directory[] = ".";
    -+
    - /* Adapted from libiberty's basename.c.  */
    - char *gitbasename (char *path)
    - {
     @@ compat/basename.c: char *gitbasename (char *path)
      		skip_dos_drive_prefix(&path);
      
    @@ compat/basename.c: char *gitbasename (char *path)
     +		 * pointer to internal memory at times. The cast is a result of
     +		 * that.
     +		 */
    -+		return (char *) "";
    ++		return (char *) ".";
      
      	for (base = path; *path; path++) {
      		if (!is_dir_sep(*path))
    @@ compat/basename.c: char *gitdirname(char *path)
     +		 * pointer to internal memory at times. The cast is a result of
     +		 * that.
     +		 */
    -+		return (char *) "";
    ++		return (char *) ".";
      
      	if ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p)
      		goto dot;
16:  8a98078439 = 16:  a8ef39d73d http: do not assign string constant to non-const field
17:  26c4c76c86 = 17:  9d596a07c5 parse-options: cast long name for OPTION_ALIAS
18:  e3227bd3f3 = 18:  4019b532f9 send-pack: always allocate receive status
19:  af82e49682 = 19:  f2f1ada143 remote-curl: avoid assigning string constant to non-const variable
20:  077f10d0dc = 20:  27660b908c revision: always store allocated strings in output encoding
21:  fb240598b4 = 21:  ef43c1b18f mailmap: always store allocated strings in mailmap blob
22:  291030faa7 = 22:  0a69ce4b44 imap-send: drop global `imap_server_conf` variable
23:  9b376a313f = 23:  9ccafd286b imap-send: fix leaking memory in `imap_server_conf`
24:  0a84d66e68 = 24:  e19457d20c builtin/rebase: do not assign default backend to non-constant field
25:  5c8bee3695 = 25:  f548241960 builtin/rebase: always store allocated string in `options.strategy`
26:  84c0149c8f = 26:  78ac075644 builtin/merge: always store allocated strings in `pull_twohead`
27:  3427547134 = 27:  0cd4ce07d8 config.mak.dev: enable `-Wwrite-strings` warning
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 4, 2024, 12:36 UTC · re: Patrick Steinhardt · lore

[PATCH v4 01/27] global: improve const correctness when assigning string constants

We're about to enable `-Wwrite-strings`, which changes the type of string constants to `const char[]`. Fix various sites where we assign such constants to non-const variables.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/bisect.c             |  3 ++-
 builtin/blame.c              |  2 +-
 builtin/bugreport.c          |  2 +-
 builtin/check-ignore.c       |  4 +--
 builtin/clone.c              |  6 ++---
 builtin/commit.c             |  6 ++---
 builtin/diagnose.c           |  2 +-
 builtin/log.c                |  2 +-
 builtin/mailsplit.c          |  4 +--
 builtin/pull.c               | 52 ++++++++++++++++++------------------
 builtin/receive-pack.c       |  4 +--
 builtin/revert.c             |  2 +-
 compat/regex/regcomp.c       |  2 +-
 diff.c                       |  4 +--
 diffcore-rename.c            |  6 ++---
 fmt-merge-msg.c              |  2 +-
 fsck.c                       |  2 +-
 fsck.h                       |  2 +-
 gpg-interface.c              |  2 +-
 http-backend.c               |  2 +-
 imap-send.c                  |  6 ++---
 pretty.c                     |  2 +-
 refs.c                       |  2 +-
 refs.h                       |  2 +-
 reftable/basics.c            | 15 +++++------
 reftable/basics.h            |  4 +--
 reftable/basics_test.c       |  4 +--
 reftable/record.c            |  6 ++---
 reftable/stack.c             | 10 ++++---
 reftable/stack_test.c        |  8 +++---
 run-command.c                |  2 +-
 t/helper/test-hashmap.c      |  3 ++-
 t/helper/test-json-writer.c  | 10 +++----
 t/helper/test-regex.c        |  4 +--
 t/helper/test-rot13-filter.c |  5 ++--
 t/unit-tests/t-strbuf.c      | 10 ++++---
 trailer.c                    |  2 +-
 wt-status.c                  |  2 +-
 38 files changed, 106 insertions(+), 102 deletions(-)
Show changes to 38 files +106 −102

builtin/bisect.c, builtin/blame.c, builtin/bugreport.c, builtin/check-ignore.c, builtin/clone.c, builtin/commit.c, builtin/diagnose.c, builtin/log.c, builtin/mailsplit.c, builtin/pull.c, builtin/receive-pack.c, builtin/revert.c, compat/regex/regcomp.c, diff.c, diffcore-rename.c, fmt-merge-msg.c, fsck.c, fsck.h, gpg-interface.c, http-backend.c, imap-send.c, pretty.c, refs.c, refs.h, reftable/basics.c, reftable/basics.h, reftable/basics_test.c, reftable/record.c, reftable/stack.c, reftable/stack_test.c, run-command.c, t/helper/test-hashmap.c, t/helper/test-json-writer.c, t/helper/test-regex.c, t/helper/test-rot13-filter.c, t/unit-tests/t-strbuf.c, trailer.c, wt-status.c

diff --git a/builtin/bisect.c b/builtin/bisect.c
index a58432b9d9..dabce9b542 100644
--- a/builtin/bisect.c
+++ b/builtin/bisect.c
@@ -262,7 +262,8 @@ static int bisect_reset(const char *commit)
 	return bisect_clean_state();
 }
 
-static void log_commit(FILE *fp, char *fmt, const char *state,
+static void log_commit(FILE *fp,
+		       const char *fmt, const char *state,
 		       struct commit *commit)
 {
 	struct pretty_print_context pp = {0};
diff --git a/builtin/blame.c b/builtin/blame.c
index 838cd476be..98c7629b6a 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -134,7 +134,7 @@ static void get_ac_line(const char *inbuf, const char *what,
 {
 	struct ident_split ident;
 	size_t len, maillen, namelen;
-	char *tmp, *endp;
+	const char *tmp, *endp;
 	const char *namebuf, *mailbuf;
 
 	tmp = strstr(inbuf, what);
diff --git a/builtin/bugreport.c b/builtin/bugreport.c
index 25f860a0d9..b3cc77af53 100644
--- a/builtin/bugreport.c
+++ b/builtin/bugreport.c
@@ -107,7 +107,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix)
 	struct tm tm;
 	enum diagnose_mode diagnose = DIAGNOSE_NONE;
 	char *option_output = NULL;
-	char *option_suffix = "%Y-%m-%d-%H%M";
+	const char *option_suffix = "%Y-%m-%d-%H%M";
 	const char *user_relative_path = NULL;
 	char *prefixed_filename;
 	size_t output_path_len;
diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c
index 6c43430ec4..2bda6a1d46 100644
--- a/builtin/check-ignore.c
+++ b/builtin/check-ignore.c
@@ -35,8 +35,8 @@ static const struct option check_ignore_options[] = {
 
 static void output_pattern(const char *path, struct path_pattern *pattern)
 {
-	char *bang  = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE)  ? "!" : "";
-	char *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? "/" : "";
+	const char *bang  = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE)  ? "!" : "";
+	const char *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? "/" : "";
 	if (!nul_term_line) {
 		if (!verbose) {
 			write_name_quoted(path, stdout, '\n');
diff --git a/builtin/clone.c b/builtin/clone.c
index 23993b905b..92ab7d7165 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -71,7 +71,7 @@ static char *option_branch = NULL;
 static struct string_list option_not = STRING_LIST_INIT_NODUP;
 static const char *real_git_dir;
 static const char *ref_format;
-static char *option_upload_pack = "git-upload-pack";
+static const char *option_upload_pack = "git-upload-pack";
 static int option_verbosity;
 static int option_progress = -1;
 static int option_sparse_checkout;
@@ -177,8 +177,8 @@ static struct option builtin_clone_options[] = {
 
 static const char *get_repo_path_1(struct strbuf *path, int *is_bundle)
 {
-	static char *suffix[] = { "/.git", "", ".git/.git", ".git" };
-	static char *bundle_suffix[] = { ".bundle", "" };
+	static const char *suffix[] = { "/.git", "", ".git/.git", ".git" };
+	static const char *bundle_suffix[] = { ".bundle", "" };
 	size_t baselen = path->len;
 	struct stat st;
 	int i;
diff --git a/builtin/commit.c b/builtin/commit.c
index f53e7e86ff..75c741173e 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,7 +113,7 @@ static char *template_file;
  * the commit message and/or authorship.
  */
 static const char *author_message, *author_message_buffer;
-static char *edit_message, *use_message;
+static const char *edit_message, *use_message;
 static char *fixup_message, *fixup_commit, *squash_message;
 static const char *fixup_prefix;
 static int all, also, interactive, patch_interactive, only, amend, signoff;
@@ -121,8 +121,8 @@ static int edit_flag = -1; /* unspecified */
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
-static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
-static char *sign_commit, *pathspec_from_file;
+static const char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
+static const char *sign_commit, *pathspec_from_file;
 static struct strvec trailer_args = STRVEC_INIT;
 
 /*
diff --git a/builtin/diagnose.c b/builtin/diagnose.c
index 4f22eb2b55..4857a4395b 100644
--- a/builtin/diagnose.c
+++ b/builtin/diagnose.c
@@ -18,7 +18,7 @@ int cmd_diagnose(int argc, const char **argv, const char *prefix)
 	struct tm tm;
 	enum diagnose_mode mode = DIAGNOSE_STATS;
 	char *option_output = NULL;
-	char *option_suffix = "%Y-%m-%d-%H%M";
+	const char *option_suffix = "%Y-%m-%d-%H%M";
 	char *prefixed_filename;
 
 	const struct option diagnose_options[] = {
diff --git a/builtin/log.c b/builtin/log.c
index 78a247d8a9..b8846a9458 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1283,7 +1283,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids)
 	o2->flags = flags2;
 }
 
-static void gen_message_id(struct rev_info *info, char *base)
+static void gen_message_id(struct rev_info *info, const char *base)
 {
 	struct strbuf buf = STRBUF_INIT;
 	strbuf_addf(&buf, "%s.%"PRItime".git.%s", base,
diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c
index 3af9ddb8ae..fe6dbc5d05 100644
--- a/builtin/mailsplit.c
+++ b/builtin/mailsplit.c
@@ -113,8 +113,8 @@ static int populate_maildir_list(struct string_list *list, const char *path)
 	DIR *dir;
 	struct dirent *dent;
 	char *name = NULL;
-	char *subs[] = { "cur", "new", NULL };
-	char **sub;
+	const char *subs[] = { "cur", "new", NULL };
+	const char **sub;
 	int ret = -1;
 
 	for (sub = subs; *sub; ++sub) {
diff --git a/builtin/pull.c b/builtin/pull.c
index d622202bce..2d0429f14f 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -71,48 +71,48 @@ static const char * const pull_usage[] = {
 
 /* Shared options */
 static int opt_verbosity;
-static char *opt_progress;
+static const char *opt_progress;
 static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
 static int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT;
 
 /* Options passed to git-merge or git-rebase */
 static enum rebase_type opt_rebase = -1;
-static char *opt_diffstat;
-static char *opt_log;
-static char *opt_signoff;
-static char *opt_squash;
-static char *opt_commit;
-static char *opt_edit;
-static char *cleanup_arg;
-static char *opt_ff;
-static char *opt_verify_signatures;
-static char *opt_verify;
+static const char *opt_diffstat;
+static const char *opt_log;
+static const char *opt_signoff;
+static const char *opt_squash;
+static const char *opt_commit;
+static const char *opt_edit;
+static const char *cleanup_arg;
+static const char *opt_ff;
+static const char *opt_verify_signatures;
+static const char *opt_verify;
 static int opt_autostash = -1;
 static int config_autostash;
 static int check_trust_level = 1;
 static struct strvec opt_strategies = STRVEC_INIT;
 static struct strvec opt_strategy_opts = STRVEC_INIT;
-static char *opt_gpg_sign;
+static const char *opt_gpg_sign;
 static int opt_allow_unrelated_histories;
 
 /* Options passed to git-fetch */
-static char *opt_all;
-static char *opt_append;
-static char *opt_upload_pack;
+static const char *opt_all;
+static const char *opt_append;
+static const char *opt_upload_pack;
 static int opt_force;
-static char *opt_tags;
-static char *opt_prune;
-static char *max_children;
+static const char *opt_tags;
+static const char *opt_prune;
+static const char *max_children;
 static int opt_dry_run;
-static char *opt_keep;
-static char *opt_depth;
-static char *opt_unshallow;
-static char *opt_update_shallow;
-static char *opt_refmap;
-static char *opt_ipv4;
-static char *opt_ipv6;
+static const char *opt_keep;
+static const char *opt_depth;
+static const char *opt_unshallow;
+static const char *opt_update_shallow;
+static const char *opt_refmap;
+static const char *opt_ipv4;
+static const char *opt_ipv6;
 static int opt_show_forced_updates = -1;
-static char *set_upstream;
+static const char *set_upstream;
 static struct strvec opt_fetch = STRVEC_INIT;
 
 static struct option pull_options[] = {
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 01c1f04ece..c8d12ee0a7 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1249,7 +1249,7 @@ static int run_proc_receive_hook(struct command *commands,
 	return code;
 }
 
-static char *refuse_unconfigured_deny_msg =
+static const char *refuse_unconfigured_deny_msg =
 	N_("By default, updating the current branch in a non-bare repository\n"
 	   "is denied, because it will make the index and work tree inconsistent\n"
 	   "with what you pushed, and will require 'git reset --hard' to match\n"
@@ -1269,7 +1269,7 @@ static void refuse_unconfigured_deny(void)
 	rp_error("%s", _(refuse_unconfigured_deny_msg));
 }
 
-static char *refuse_unconfigured_deny_delete_current_msg =
+static const char *refuse_unconfigured_deny_delete_current_msg =
 	N_("By default, deleting the current branch is denied, because the next\n"
 	   "'git clone' won't result in any file checked out, causing confusion.\n"
 	   "\n"
diff --git a/builtin/revert.c b/builtin/revert.c
index 53935d2c68..7bf2b4e11d 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -179,7 +179,7 @@ static int run_sequencer(int argc, const char **argv, const char *prefix,
 
 	/* Check for incompatible command line arguments */
 	if (cmd) {
-		char *this_operation;
+		const char *this_operation;
 		if (cmd == 'q')
 			this_operation = "--quit";
 		else if (cmd == 'c')
diff --git a/compat/regex/regcomp.c b/compat/regex/regcomp.c
index 2bc0f1187a..6c5d455e92 100644
--- a/compat/regex/regcomp.c
+++ b/compat/regex/regcomp.c
@@ -848,7 +848,7 @@ init_dfa (re_dfa_t *dfa, size_t pat_len)
 {
   unsigned int table_size;
 #ifndef _LIBC
-  char *codeset_name;
+  const char *codeset_name;
 #endif
 
   memset (dfa, '\0', sizeof (re_dfa_t));
diff --git a/diff.c b/diff.c
index e70301df76..ffd867ef6c 100644
--- a/diff.c
+++ b/diff.c
@@ -3764,7 +3764,7 @@ static void builtin_diff(const char *name_a,
 	return;
 }
 
-static char *get_compact_summary(const struct diff_filepair *p, int is_renamed)
+static const char *get_compact_summary(const struct diff_filepair *p, int is_renamed)
 {
 	if (!is_renamed) {
 		if (p->status == DIFF_STATUS_ADDED) {
@@ -4076,7 +4076,7 @@ static int reuse_worktree_file(struct index_state *istate,
 static int diff_populate_gitlink(struct diff_filespec *s, int size_only)
 {
 	struct strbuf buf = STRBUF_INIT;
-	char *dirty = "";
+	const char *dirty = "";
 
 	/* Are we looking at the work tree? */
 	if (s->dirty_submodule)
diff --git a/diffcore-rename.c b/diffcore-rename.c
index 5a6e2bcac7..0e1adb87df 100644
--- a/diffcore-rename.c
+++ b/diffcore-rename.c
@@ -406,7 +406,7 @@ static const char *get_highest_rename_path(struct strintmap *counts)
 	return highest_destination_dir;
 }
 
-static char *UNKNOWN_DIR = "/";  /* placeholder -- short, illegal directory */
+static const char *UNKNOWN_DIR = "/";  /* placeholder -- short, illegal directory */
 
 static int dir_rename_already_determinable(struct strintmap *counts)
 {
@@ -429,8 +429,8 @@ static int dir_rename_already_determinable(struct strintmap *counts)
 }
 
 static void increment_count(struct dir_rename_info *info,
-			    char *old_dir,
-			    char *new_dir)
+			    const char *old_dir,
+			    const char *new_dir)
 {
 	struct strintmap *counts;
 	struct strmap_entry *e;
diff --git a/fmt-merge-msg.c b/fmt-merge-msg.c
index 7d144b803a..5af63ab5ab 100644
--- a/fmt-merge-msg.c
+++ b/fmt-merge-msg.c
@@ -447,7 +447,7 @@ static void fmt_merge_msg_title(struct strbuf *out,
 				const char *current_branch)
 {
 	int i = 0;
-	char *sep = "";
+	const char *sep = "";
 
 	strbuf_addstr(out, "Merge ");
 	for (i = 0; i < srcs.nr; i++) {
diff --git a/fsck.c b/fsck.c
index 7dff41413e..61cd48aa25 100644
--- a/fsck.c
+++ b/fsck.c
@@ -1231,7 +1231,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size,
 }
 
 int fsck_buffer(const struct object_id *oid, enum object_type type,
-		void *data, unsigned long size,
+		const void *data, unsigned long size,
 		struct fsck_options *options)
 {
 	if (type == OBJ_BLOB)
diff --git a/fsck.h b/fsck.h
index 17fa2dda5d..4f0c4e6479 100644
--- a/fsck.h
+++ b/fsck.h
@@ -202,7 +202,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size,
  * struct.
  */
 int fsck_buffer(const struct object_id *oid, enum object_type,
-		void *data, unsigned long size,
+		const void *data, unsigned long size,
 		struct fsck_options *options);
 
 /*
diff --git a/gpg-interface.c b/gpg-interface.c
index 5193223714..71a9382a61 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -727,7 +727,7 @@ static int git_gpg_config(const char *var, const char *value,
 			  void *cb UNUSED)
 {
 	struct gpg_format *fmt = NULL;
-	char *fmtname = NULL;
+	const char *fmtname = NULL;
 	char *trust;
 	int ret;
 
diff --git a/http-backend.c b/http-backend.c
index 5b65287ac9..5b4dca65ed 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -753,7 +753,7 @@ static int bad_request(struct strbuf *hdr, const struct service_cmd *c)
 
 int cmd_main(int argc UNUSED, const char **argv UNUSED)
 {
-	char *method = getenv("REQUEST_METHOD");
+	const char *method = getenv("REQUEST_METHOD");
 	const char *proto_header;
 	char *dir;
 	struct service_cmd *cmd = NULL;
diff --git a/imap-send.c b/imap-send.c
index a5d1510180..8b723b34a5 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -1215,9 +1215,9 @@ static int imap_store_msg(struct imap_store *ctx, struct strbuf *msg)
 static void wrap_in_html(struct strbuf *msg)
 {
 	struct strbuf buf = STRBUF_INIT;
-	static char *content_type = "Content-Type: text/html;\n";
-	static char *pre_open = "<pre>\n";
-	static char *pre_close = "</pre>\n";
+	static const char *content_type = "Content-Type: text/html;\n";
+	static const char *pre_open = "<pre>\n";
+	static const char *pre_close = "</pre>\n";
 	const char *body = strstr(msg->buf, "\n\n");
 
 	if (!body)
diff --git a/pretty.c b/pretty.c
index 22a81506b7..ec05db5655 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1325,7 +1325,7 @@ int format_set_trailers_options(struct process_trailer_options *opts,
 static size_t parse_describe_args(const char *start, struct strvec *args)
 {
 	struct {
-		char *name;
+		const char *name;
 		enum {
 			DESCRIBE_ARG_BOOL,
 			DESCRIBE_ARG_INTEGER,
diff --git a/refs.c b/refs.c
index 8260c27cde..292e8d947e 100644
--- a/refs.c
+++ b/refs.c
@@ -159,7 +159,7 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref)
 {
 	struct ref_namespace_info *info = &ref_namespace[namespace];
 	if (info->ref_updated)
-		free(info->ref);
+		free((char *)info->ref);
 	info->ref = ref;
 	info->ref_updated = 1;
 }
diff --git a/refs.h b/refs.h
index 34568ee1fb..923f751d18 100644
--- a/refs.h
+++ b/refs.h
@@ -975,7 +975,7 @@ struct ref_store *get_worktree_ref_store(const struct worktree *wt);
  */
 
 struct ref_namespace_info {
-	char *ref;
+	const char *ref;
 	enum decoration_type decoration;
 
 	/*
diff --git a/reftable/basics.c b/reftable/basics.c
index fea711db7e..0058619ca6 100644
--- a/reftable/basics.c
+++ b/reftable/basics.c
@@ -67,9 +67,9 @@ void free_names(char **a)
 	reftable_free(a);
 }
 
-size_t names_length(char **names)
+size_t names_length(const char **names)
 {
-	char **p = names;
+	const char **p = names;
 	while (*p)
 		p++;
 	return p - names;
@@ -102,15 +102,12 @@ void parse_names(char *buf, int size, char ***namesp)
 	*namesp = names;
 }
 
-int names_equal(char **a, char **b)
+int names_equal(const char **a, const char **b)
 {
-	int i = 0;
-	for (; a[i] && b[i]; i++) {
-		if (strcmp(a[i], b[i])) {
+	size_t i = 0;
+	for (; a[i] && b[i]; i++)
+		if (strcmp(a[i], b[i]))
 			return 0;
-		}
-	}
-
 	return a[i] == b[i];
 }
 
diff --git a/reftable/basics.h b/reftable/basics.h
index 523ecd5307..c8fec68d4e 100644
--- a/reftable/basics.h
+++ b/reftable/basics.h
@@ -42,10 +42,10 @@ void free_names(char **a);
 void parse_names(char *buf, int size, char ***namesp);
 
 /* compares two NULL-terminated arrays of strings. */
-int names_equal(char **a, char **b);
+int names_equal(const char **a, const char **b);
 
 /* returns the array size of a NULL-terminated array of strings. */
-size_t names_length(char **names);
+size_t names_length(const char **names);
 
 /* Allocation routines; they invoke the functions set through
  * reftable_set_alloc() */
diff --git a/reftable/basics_test.c b/reftable/basics_test.c
index 997c4d9e01..13bc761817 100644
--- a/reftable/basics_test.c
+++ b/reftable/basics_test.c
@@ -58,8 +58,8 @@ static void test_binsearch(void)
 
 static void test_names_length(void)
 {
-	char *a[] = { "a", "b", NULL };
-	EXPECT(names_length(a) == 2);
+	const char *names[] = { "a", "b", NULL };
+	EXPECT(names_length(names) == 2);
 }
 
 static void test_parse_names_normal(void)
diff --git a/reftable/record.c b/reftable/record.c
index 5506f3e913..a2cba5ef74 100644
--- a/reftable/record.c
+++ b/reftable/record.c
@@ -116,7 +116,7 @@ static int decode_string(struct strbuf *dest, struct string_view in)
 	return start_len - in.len;
 }
 
-static int encode_string(char *str, struct string_view s)
+static int encode_string(const char *str, struct string_view s)
 {
 	struct string_view start = s;
 	int l = strlen(str);
@@ -969,9 +969,9 @@ static int reftable_log_record_decode(void *rec, struct strbuf key,
 	return REFTABLE_FORMAT_ERROR;
 }
 
-static int null_streq(char *a, char *b)
+static int null_streq(const char *a, const char *b)
 {
-	char *empty = "";
+	const char *empty = "";
 	if (!a)
 		a = empty;
 
diff --git a/reftable/stack.c b/reftable/stack.c
index a59ebe038d..09549c51c9 100644
--- a/reftable/stack.c
+++ b/reftable/stack.c
@@ -204,7 +204,8 @@ static struct reftable_reader **stack_copy_readers(struct reftable_stack *st,
 	return cur;
 }
 
-static int reftable_stack_reload_once(struct reftable_stack *st, char **names,
+static int reftable_stack_reload_once(struct reftable_stack *st,
+				      const char **names,
 				      int reuse_open)
 {
 	size_t cur_len = !st->merged ? 0 : st->merged->stack_len;
@@ -222,7 +223,7 @@ static int reftable_stack_reload_once(struct reftable_stack *st, char **names,
 
 	while (*names) {
 		struct reftable_reader *rd = NULL;
-		char *name = *names++;
+		const char *name = *names++;
 
 		/* this is linear; we assume compaction keeps the number of
 		   tables under control so this is not quadratic. */
@@ -354,7 +355,7 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
 				goto out;
 		}
 
-		err = reftable_stack_reload_once(st, names, reuse_open);
+		err = reftable_stack_reload_once(st, (const char **) names, reuse_open);
 		if (!err)
 			break;
 		if (err != REFTABLE_NOT_EXIST_ERROR)
@@ -368,7 +369,8 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
 		err = read_lines(st->list_file, &names_after);
 		if (err < 0)
 			goto out;
-		if (names_equal(names_after, names)) {
+		if (names_equal((const char **) names_after,
+				(const char **) names)) {
 			err = REFTABLE_NOT_EXIST_ERROR;
 			goto out;
 		}
diff --git a/reftable/stack_test.c b/reftable/stack_test.c
index 7889f818d1..07d89b45da 100644
--- a/reftable/stack_test.c
+++ b/reftable/stack_test.c
@@ -83,7 +83,7 @@ static void test_read_file(void)
 	char out[1024] = "line1\n\nline2\nline3";
 	int n, err;
 	char **names = NULL;
-	char *want[] = { "line1", "line2", "line3" };
+	const char *want[] = { "line1", "line2", "line3" };
 	int i = 0;
 
 	EXPECT(fd > 0);
@@ -116,9 +116,9 @@ static void test_parse_names(void)
 
 static void test_names_equal(void)
 {
-	char *a[] = { "a", "b", "c", NULL };
-	char *b[] = { "a", "b", "d", NULL };
-	char *c[] = { "a", "b", NULL };
+	const char *a[] = { "a", "b", "c", NULL };
+	const char *b[] = { "a", "b", "d", NULL };
+	const char *c[] = { "a", "b", NULL };
 
 	EXPECT(names_equal(a, a));
 	EXPECT(!names_equal(a, b));
diff --git a/run-command.c b/run-command.c
index 1b821042b4..7600531fb6 100644
--- a/run-command.c
+++ b/run-command.c
@@ -663,7 +663,7 @@ int start_command(struct child_process *cmd)
 	int need_in, need_out, need_err;
 	int fdin[2], fdout[2], fderr[2];
 	int failed_errno;
-	char *str;
+	const char *str;
 
 	/*
 	 * In case of errors we must keep the promise to close FDs
diff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c
index 0eb0b3d49c..2912899558 100644
--- a/t/helper/test-hashmap.c
+++ b/t/helper/test-hashmap.c
@@ -36,7 +36,8 @@ static int test_entry_cmp(const void *cmp_data,
 }
 
 static struct test_entry *alloc_test_entry(unsigned int hash,
-					   char *key, char *value)
+					   const char *key,
+					   const char *value)
 {
 	size_t klen = strlen(key);
 	size_t vlen = strlen(value);
diff --git a/t/helper/test-json-writer.c b/t/helper/test-json-writer.c
index afe393f597..ed52eb76bf 100644
--- a/t/helper/test-json-writer.c
+++ b/t/helper/test-json-writer.c
@@ -174,7 +174,7 @@ static void make_arr4(int pretty)
 	jw_end(&arr4);
 }
 
-static char *expect_nest1 =
+static const char *expect_nest1 =
 	"{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true},\"arr1\":[\"abc\",42,true]}";
 
 static struct json_writer nest1 = JSON_WRITER_INIT;
@@ -195,10 +195,10 @@ static void make_nest1(int pretty)
 	jw_release(&arr1);
 }
 
-static char *expect_inline1 =
+static const char *expect_inline1 =
 	"{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true},\"arr1\":[\"abc\",42,true]}";
 
-static char *pretty_inline1 =
+static const char *pretty_inline1 =
 	("{\n"
 	 "  \"obj1\": {\n"
 	 "    \"a\": \"abc\",\n"
@@ -236,10 +236,10 @@ static void make_inline1(int pretty)
 	jw_end(&inline1);
 }
 
-static char *expect_inline2 =
+static const char *expect_inline2 =
 	"[[1,2],[3,4],{\"a\":\"abc\"}]";
 
-static char *pretty_inline2 =
+static const char *pretty_inline2 =
 	("[\n"
 	 "  [\n"
 	 "    1,\n"
diff --git a/t/helper/test-regex.c b/t/helper/test-regex.c
index 80042eafc2..366bd70976 100644
--- a/t/helper/test-regex.c
+++ b/t/helper/test-regex.c
@@ -20,8 +20,8 @@ static struct reg_flag reg_flags[] = {
 
 static int test_regex_bug(void)
 {
-	char *pat = "[^={} \t]+";
-	char *str = "={}\nfred";
+	const char *pat = "[^={} \t]+";
+	const char *str = "={}\nfred";
 	regex_t r;
 	regmatch_t m[1];
 
diff --git a/t/helper/test-rot13-filter.c b/t/helper/test-rot13-filter.c
index f8d564c622..7e1d9e0ee4 100644
--- a/t/helper/test-rot13-filter.c
+++ b/t/helper/test-rot13-filter.c
@@ -136,7 +136,7 @@ static void free_delay_entries(void)
 	strmap_clear(&delay, 0);
 }
 
-static void add_delay_entry(char *pathname, int count, int requested)
+static void add_delay_entry(const char *pathname, int count, int requested)
 {
 	struct delay_entry *entry = xcalloc(1, sizeof(*entry));
 	entry->count = count;
@@ -189,7 +189,8 @@ static void reply_list_available_blobs_cmd(void)
 static void command_loop(void)
 {
 	for (;;) {
-		char *buf, *output;
+		char *buf;
+		const char *output;
 		char *pathname;
 		struct delay_entry *entry;
 		struct strbuf input = STRBUF_INIT;
diff --git a/t/unit-tests/t-strbuf.c b/t/unit-tests/t-strbuf.c
index de434a4441..6027dafef7 100644
--- a/t/unit-tests/t-strbuf.c
+++ b/t/unit-tests/t-strbuf.c
@@ -2,7 +2,8 @@
 #include "strbuf.h"
 
 /* wrapper that supplies tests with an empty, initialized strbuf */
-static void setup(void (*f)(struct strbuf*, void*), void *data)
+static void setup(void (*f)(struct strbuf*, const void*),
+		  const void *data)
 {
 	struct strbuf buf = STRBUF_INIT;
 
@@ -13,7 +14,8 @@ static void setup(void (*f)(struct strbuf*, void*), void *data)
 }
 
 /* wrapper that supplies tests with a populated, initialized strbuf */
-static void setup_populated(void (*f)(struct strbuf*, void*), char *init_str, void *data)
+static void setup_populated(void (*f)(struct strbuf*, const void*),
+			    const char *init_str, const void *data)
 {
 	struct strbuf buf = STRBUF_INIT;
 
@@ -64,7 +66,7 @@ static void t_dynamic_init(void)
 	strbuf_release(&buf);
 }
 
-static void t_addch(struct strbuf *buf, void *data)
+static void t_addch(struct strbuf *buf, const void *data)
 {
 	const char *p_ch = data;
 	const char ch = *p_ch;
@@ -83,7 +85,7 @@ static void t_addch(struct strbuf *buf, void *data)
 	check_char(buf->buf[buf->len], ==, '\0');
 }
 
-static void t_addstr(struct strbuf *buf, void *data)
+static void t_addstr(struct strbuf *buf, const void *data)
 {
 	const char *text = data;
 	size_t len = strlen(text);
diff --git a/trailer.c b/trailer.c
index 2bcb9ba8f7..72e5136c73 100644
--- a/trailer.c
+++ b/trailer.c
@@ -63,7 +63,7 @@ struct arg_item {
 
 static LIST_HEAD(conf_head);
 
-static char *separators = ":";
+static const char *separators = ":";
 
 static int configured;
 
diff --git a/wt-status.c b/wt-status.c
index ff4be071ca..7912545e4e 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -2408,7 +2408,7 @@ static void wt_porcelain_v2_print_unmerged_entry(
 		int mode;
 		struct object_id oid;
 	} stages[3];
-	char *key;
+	const char *key;
 	char submodule_token[5];
 	char unmerged_prefix = 'u';
 	char eol_char = s->null_termination ? '\0' : '\n';
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 4, 2024, 12:37 UTC · re: Patrick Steinhardt · lore

[PATCH v4 02/27] global: convert intentionally-leaking config strings to consts

There are multiple cases where we intentionally leak config strings:
  - `struct gpg_format` is used to track programs that can be used for
    signing commits, either via gpg(1), gpgsm(1) or ssh-keygen(1). The
    user can override the commands via several config variables. As the
    array is populated once, only, and the struct memers are never
    written to or free'd.
  - `struct ll_merge_driver` is used to track merge drivers. Same as
    with the GPG format, these drivers are populated once and then
    reused. Its data is never written to or free'd, either.
  - `struct userdiff_funcname` and `struct userdiff_driver` can be
    configured via `diff.<driver>.*` to add additional drivers. Again,
    these have a global lifetime and are never written to or free'd.

All of these are intentionally kept alive and are never written to. Furthermore, all of these are being assigned both string constants in some places, and allocated strings in other places. This will cause warnings once we enable `-Wwrite-strings`, so let's mark the respective fields as `const char *` and cast away the constness when assigning those values.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 gpg-interface.c |  4 ++--
 merge-ll.c      | 11 ++++++++---
 userdiff.c      | 10 +++++-----
 userdiff.h      | 12 ++++++------
 4 files changed, 21 insertions(+), 16 deletions(-)
Show changes to 4 files +21 −16

gpg-interface.c, merge-ll.c, userdiff.c, userdiff.h

diff --git a/gpg-interface.c b/gpg-interface.c
index 71a9382a61..5c824aeb25 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -34,7 +34,7 @@ static enum signature_trust_level configured_min_trust_level = TRUST_UNDEFINED;
 
 struct gpg_format {
 	const char *name;
-	char *program;
+	const char *program;
 	const char **verify_args;
 	const char **sigs;
 	int (*verify_signed_buffer)(struct signature_check *sigc,
@@ -783,7 +783,7 @@ static int git_gpg_config(const char *var, const char *value,
 
 	if (fmtname) {
 		fmt = get_format_by_name(fmtname);
-		return git_config_string(&fmt->program, var, value);
+		return git_config_string((char **) &fmt->program, var, value);
 	}
 
 	return 0;
diff --git a/merge-ll.c b/merge-ll.c
index e29b15fa4a..180c19df67 100644
--- a/merge-ll.c
+++ b/merge-ll.c
@@ -27,7 +27,7 @@ typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *,
 
 struct ll_merge_driver {
 	const char *name;
-	char *description;
+	const char *description;
 	ll_merge_fn fn;
 	char *recursive;
 	struct ll_merge_driver *next;
@@ -304,8 +304,13 @@ static int read_merge_config(const char *var, const char *value,
 		ll_user_merge_tail = &(fn->next);
 	}
 
-	if (!strcmp("name", key))
-		return git_config_string(&fn->description, var, value);
+	if (!strcmp("name", key)) {
+		/*
+		 * The description is leaking, but that's okay as we want to
+		 * keep around the merge drivers anyway.
+		 */
+		return git_config_string((char **) &fn->description, var, value);
+	}
 
 	if (!strcmp("driver", key)) {
 		if (!value)
diff --git a/userdiff.c b/userdiff.c
index 82bc76b910..371032a413 100644
--- a/userdiff.c
+++ b/userdiff.c
@@ -399,7 +399,7 @@ static struct userdiff_driver *userdiff_find_by_namelen(const char *name, size_t
 static int parse_funcname(struct userdiff_funcname *f, const char *k,
 		const char *v, int cflags)
 {
-	if (git_config_string(&f->pattern, k, v) < 0)
+	if (git_config_string((char **) &f->pattern, k, v) < 0)
 		return -1;
 	f->cflags = cflags;
 	return 0;
@@ -445,15 +445,15 @@ int userdiff_config(const char *k, const char *v)
 	if (!strcmp(type, "binary"))
 		return parse_tristate(&drv->binary, k, v);
 	if (!strcmp(type, "command"))
-		return git_config_string(&drv->external, k, v);
+		return git_config_string((char **) &drv->external, k, v);
 	if (!strcmp(type, "textconv"))
-		return git_config_string(&drv->textconv, k, v);
+		return git_config_string((char **) &drv->textconv, k, v);
 	if (!strcmp(type, "cachetextconv"))
 		return parse_bool(&drv->textconv_want_cache, k, v);
 	if (!strcmp(type, "wordregex"))
-		return git_config_string(&drv->word_regex, k, v);
+		return git_config_string((char **) &drv->word_regex, k, v);
 	if (!strcmp(type, "algorithm"))
-		return git_config_string(&drv->algorithm, k, v);
+		return git_config_string((char **) &drv->algorithm, k, v);
 
 	return 0;
 }
diff --git a/userdiff.h b/userdiff.h
index cc8e5abfef..d726804c3e 100644
--- a/userdiff.h
+++ b/userdiff.h
@@ -7,19 +7,19 @@ struct index_state;
 struct repository;
 
 struct userdiff_funcname {
-	char *pattern;
+	const char *pattern;
 	int cflags;
 };
 
 struct userdiff_driver {
 	const char *name;
-	char *external;
-	char *algorithm;
+	const char *external;
+	const char *algorithm;
 	int binary;
 	struct userdiff_funcname funcname;
-	char *word_regex;
-	char *word_regex_multi_byte;
-	char *textconv;
+	const char *word_regex;
+	const char *word_regex_multi_byte;
+	const char *textconv;
 	struct notes_cache *textconv_cache;
 	int textconv_want_cache;
 };
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 4, 2024, 12:37 UTC · re: Patrick Steinhardt · lore

[PATCH v4 03/27] refs/reftable: stop micro-optimizing refname allocations on copy

When copying refs, we execute `write_copy_table()` to write the new table. As the names are given to us via `arg->newname` and `arg->oldname`, respectively, we optimize away some allocations by assigning those fields to the reftable records we are about to write directly, without duplicating them. This requires us to cast the input to `char *` pointers as they are in fact constant strings. Later on, we then unset the refname for all of the records before calling `reftable_log_record_release()` on them.

We also do this when assigning the "HEAD" constant, but here we do not cast because its type is `char[]` by default. It's about to be turned into `const char *` though once we enable `-Wwrite-strings` and will thus cause another warning.

It's quite dubious whether this micro-optimization really helps. We're about to write to disk anyway, which is going to be way slower than a small handful of allocations. Let's drop the optimization altogther and instead copy arguments to simplify the code and avoid the future warning with `-Wwrite-strings`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 refs/reftable-backend.c | 28 ++++++++++++++++------------
 1 file changed, 16 insertions(+), 12 deletions(-)
Show changes to refs/reftable-backend.c +16 −12
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 1af86bbdec..e77faa2b9d 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1340,10 +1340,10 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 	 * old reference.
 	 */
 	refs[0] = old_ref;
-	refs[0].refname = (char *)arg->newname;
+	refs[0].refname = xstrdup(arg->newname);
 	refs[0].update_index = creation_ts;
 	if (arg->delete_old) {
-		refs[1].refname = (char *)arg->oldname;
+		refs[1].refname = xstrdup(arg->oldname);
 		refs[1].value_type = REFTABLE_REF_DELETION;
 		refs[1].update_index = deletion_ts;
 	}
@@ -1366,7 +1366,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 		ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 		memset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));
 		fill_reftable_log_record(&logs[logs_nr], &committer_ident);
-		logs[logs_nr].refname = (char *)arg->newname;
+		logs[logs_nr].refname = xstrdup(arg->newname);
 		logs[logs_nr].update_index = deletion_ts;
 		logs[logs_nr].value.update.message =
 			xstrndup(arg->logmsg, arg->refs->write_options.block_size / 2);
@@ -1387,7 +1387,13 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 		if (append_head_reflog) {
 			ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 			logs[logs_nr] = logs[logs_nr - 1];
-			logs[logs_nr].refname = "HEAD";
+			logs[logs_nr].refname = xstrdup("HEAD");
+			logs[logs_nr].value.update.name =
+				xstrdup(logs[logs_nr].value.update.name);
+			logs[logs_nr].value.update.email =
+				xstrdup(logs[logs_nr].value.update.email);
+			logs[logs_nr].value.update.message =
+				xstrdup(logs[logs_nr].value.update.message);
 			logs_nr++;
 		}
 	}
@@ -1398,7 +1404,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 	ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 	memset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));
 	fill_reftable_log_record(&logs[logs_nr], &committer_ident);
-	logs[logs_nr].refname = (char *)arg->newname;
+	logs[logs_nr].refname = xstrdup(arg->newname);
 	logs[logs_nr].update_index = creation_ts;
 	logs[logs_nr].value.update.message =
 		xstrndup(arg->logmsg, arg->refs->write_options.block_size / 2);
@@ -1430,7 +1436,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 		 */
 		ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 		logs[logs_nr] = old_log;
-		logs[logs_nr].refname = (char *)arg->newname;
+		logs[logs_nr].refname = xstrdup(arg->newname);
 		logs_nr++;
 
 		/*
@@ -1439,7 +1445,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 		if (arg->delete_old) {
 			ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 			memset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));
-			logs[logs_nr].refname = (char *)arg->oldname;
+			logs[logs_nr].refname = xstrdup(arg->oldname);
 			logs[logs_nr].value_type = REFTABLE_LOG_DELETION;
 			logs[logs_nr].update_index = old_log.update_index;
 			logs_nr++;
@@ -1462,13 +1468,11 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 	reftable_iterator_destroy(&it);
 	string_list_clear(&skip, 0);
 	strbuf_release(&errbuf);
-	for (i = 0; i < logs_nr; i++) {
-		if (!strcmp(logs[i].refname, "HEAD"))
-			continue;
-		logs[i].refname = NULL;
+	for (i = 0; i < logs_nr; i++)
 		reftable_log_record_release(&logs[i]);
-	}
 	free(logs);
+	for (i = 0; i < ARRAY_SIZE(refs); i++)
+		reftable_ref_record_release(&refs[i]);
 	reftable_ref_record_release(&old_ref);
 	reftable_log_record_release(&old_log);
 	return ret;
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 4, 2024, 12:37 UTC · re: Patrick Steinhardt · lore

[PATCH v4 04/27] reftable: cast away constness when assigning constants to records

The reftable records are used in multiple ways throughout the reftable library. In many of those cases they merely act as input to a function without getting modified by it at all. Most importantly, this happens when writing records and when querying for records.

We rely on this in our tests and thus assign string constants to those fields, which is about to generate warnings as those fields are of type `char *`. While we could go through the process and instead allocate those strings in all of our tests, this feels quite unnecessary.

Instead, add casts to `char *` for all of those strings. As this is part of our tests, this also nicely serves as a demonstration that nothing writes or frees those string constants, which would otherwise lead to segfaults.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 reftable/block_test.c     |  2 +-
 reftable/merged_test.c    | 44 +++++++++++++++++------------------
 reftable/readwrite_test.c | 32 +++++++++++++-------------
 reftable/stack_test.c     | 48 +++++++++++++++++++--------------------
 4 files changed, 63 insertions(+), 63 deletions(-)
Show changes to 4 files +63 −63

reftable/block_test.c, reftable/merged_test.c, reftable/readwrite_test.c, reftable/stack_test.c

diff --git a/reftable/block_test.c b/reftable/block_test.c
index 26a9cfbc83..90aecd5a7c 100644
--- a/reftable/block_test.c
+++ b/reftable/block_test.c
@@ -42,7 +42,7 @@ static void test_block_read_write(void)
 	block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,
 			  header_off, hash_size(GIT_SHA1_FORMAT_ID));
 
-	rec.u.ref.refname = "";
+	rec.u.ref.refname = (char *) "";
 	rec.u.ref.value_type = REFTABLE_REF_DELETION;
 	n = block_writer_add(&bw, &rec);
 	EXPECT(n == REFTABLE_API_ERROR);
diff --git a/reftable/merged_test.c b/reftable/merged_test.c
index 530fc82d1c..6d1159d12d 100644
--- a/reftable/merged_test.c
+++ b/reftable/merged_test.c
@@ -124,13 +124,13 @@ static void readers_destroy(struct reftable_reader **readers, size_t n)
 static void test_merged_between(void)
 {
 	struct reftable_ref_record r1[] = { {
-		.refname = "b",
+		.refname = (char *) "b",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_VAL1,
 		.value.val1 = { 1, 2, 3, 0 },
 	} };
 	struct reftable_ref_record r2[] = { {
-		.refname = "a",
+		.refname = (char *) "a",
 		.update_index = 2,
 		.value_type = REFTABLE_REF_DELETION,
 	} };
@@ -165,38 +165,38 @@ static void test_merged(void)
 {
 	struct reftable_ref_record r1[] = {
 		{
-			.refname = "a",
+			.refname = (char *) "a",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
 		},
 		{
-			.refname = "b",
+			.refname = (char *) "b",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
 		},
 		{
-			.refname = "c",
+			.refname = (char *) "c",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
 		}
 	};
 	struct reftable_ref_record r2[] = { {
-		.refname = "a",
+		.refname = (char *) "a",
 		.update_index = 2,
 		.value_type = REFTABLE_REF_DELETION,
 	} };
 	struct reftable_ref_record r3[] = {
 		{
-			.refname = "c",
+			.refname = (char *) "c",
 			.update_index = 3,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 2 },
 		},
 		{
-			.refname = "d",
+			.refname = (char *) "d",
 			.update_index = 3,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
@@ -291,46 +291,46 @@ static void test_merged_logs(void)
 {
 	struct reftable_log_record r1[] = {
 		{
-			.refname = "a",
+			.refname = (char *) "a",
 			.update_index = 2,
 			.value_type = REFTABLE_LOG_UPDATE,
 			.value.update = {
 				.old_hash = { 2 },
 				/* deletion */
-				.name = "jane doe",
-				.email = "jane@invalid",
-				.message = "message2",
+				.name = (char *) "jane doe",
+				.email = (char *) "jane@invalid",
+				.message = (char *) "message2",
 			}
 		},
 		{
-			.refname = "a",
+			.refname = (char *) "a",
 			.update_index = 1,
 			.value_type = REFTABLE_LOG_UPDATE,
 			.value.update = {
 				.old_hash = { 1 },
 				.new_hash = { 2 },
-				.name = "jane doe",
-				.email = "jane@invalid",
-				.message = "message1",
+				.name = (char *) "jane doe",
+				.email = (char *) "jane@invalid",
+				.message = (char *) "message1",
 			}
 		},
 	};
 	struct reftable_log_record r2[] = {
 		{
-			.refname = "a",
+			.refname = (char *) "a",
 			.update_index = 3,
 			.value_type = REFTABLE_LOG_UPDATE,
 			.value.update = {
 				.new_hash = { 3 },
-				.name = "jane doe",
-				.email = "jane@invalid",
-				.message = "message3",
+				.name = (char *) "jane doe",
+				.email = (char *) "jane@invalid",
+				.message = (char *) "message3",
 			}
 		},
 	};
 	struct reftable_log_record r3[] = {
 		{
-			.refname = "a",
+			.refname = (char *) "a",
 			.update_index = 2,
 			.value_type = REFTABLE_LOG_DELETION,
 		},
@@ -406,7 +406,7 @@ static void test_default_write_opts(void)
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
 
 	struct reftable_ref_record rec = {
-		.refname = "master",
+		.refname = (char *) "master",
 		.update_index = 1,
 	};
 	int err;
diff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c
index a6dbd214c5..c55019232b 100644
--- a/reftable/readwrite_test.c
+++ b/reftable/readwrite_test.c
@@ -86,7 +86,7 @@ static void write_table(char ***names, struct strbuf *buf, int N,
 		log.update_index = update_index;
 		log.value_type = REFTABLE_LOG_UPDATE;
 		set_test_hash(log.value.update.new_hash, i);
-		log.value.update.message = "message";
+		log.value.update.message = (char *) "message";
 
 		n = reftable_writer_add_log(w, &log);
 		EXPECT(n == 0);
@@ -118,15 +118,15 @@ static void test_log_buffer_size(void)
 	int err;
 	int i;
 	struct reftable_log_record
-		log = { .refname = "refs/heads/master",
+		log = { .refname = (char *) "refs/heads/master",
 			.update_index = 0xa,
 			.value_type = REFTABLE_LOG_UPDATE,
 			.value = { .update = {
-					   .name = "Han-Wen Nienhuys",
-					   .email = "hanwen@google.com",
+					   .name = (char *) "Han-Wen Nienhuys",
+					   .email = (char *) "hanwen@google.com",
 					   .tz_offset = 100,
 					   .time = 0x5e430672,
-					   .message = "commit: 9\n",
+					   .message = (char *) "commit: 9\n",
 				   } } };
 	struct reftable_writer *w =
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
@@ -156,15 +156,15 @@ static void test_log_overflow(void)
 	};
 	int err;
 	struct reftable_log_record log = {
-		.refname = "refs/heads/master",
+		.refname = (char *) "refs/heads/master",
 		.update_index = 0xa,
 		.value_type = REFTABLE_LOG_UPDATE,
 		.value = {
 			.update = {
 				.old_hash = { 1 },
 				.new_hash = { 2 },
-				.name = "Han-Wen Nienhuys",
-				.email = "hanwen@google.com",
+				.name = (char *) "Han-Wen Nienhuys",
+				.email = (char *) "hanwen@google.com",
 				.tz_offset = 100,
 				.time = 0x5e430672,
 				.message = msg,
@@ -293,14 +293,14 @@ static void test_log_zlib_corruption(void)
 	char message[100] = { 0 };
 	int err, i, n;
 	struct reftable_log_record log = {
-		.refname = "refname",
+		.refname = (char *) "refname",
 		.value_type = REFTABLE_LOG_UPDATE,
 		.value = {
 			.update = {
 				.new_hash = { 1 },
 				.old_hash = { 2 },
-				.name = "My Name",
-				.email = "myname@invalid",
+				.name = (char *) "My Name",
+				.email = (char *) "myname@invalid",
 				.message = message,
 			},
 		},
@@ -728,7 +728,7 @@ static void test_write_empty_key(void)
 	struct reftable_writer *w =
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
 	struct reftable_ref_record ref = {
-		.refname = "",
+		.refname = (char *) "",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_DELETION,
 	};
@@ -752,18 +752,18 @@ static void test_write_key_order(void)
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
 	struct reftable_ref_record refs[2] = {
 		{
-			.refname = "b",
+			.refname = (char *) "b",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_SYMREF,
 			.value = {
-				.symref = "target",
+				.symref = (char *) "target",
 			},
 		}, {
-			.refname = "a",
+			.refname = (char *) "a",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_SYMREF,
 			.value = {
-				.symref = "target",
+				.symref = (char *) "target",
 			},
 		}
 	};
diff --git a/reftable/stack_test.c b/reftable/stack_test.c
index 07d89b45da..4abf92636d 100644
--- a/reftable/stack_test.c
+++ b/reftable/stack_test.c
@@ -156,10 +156,10 @@ static void test_reftable_stack_add_one(void)
 	struct reftable_stack *st = NULL;
 	int err;
 	struct reftable_ref_record ref = {
-		.refname = "HEAD",
+		.refname = (char *) "HEAD",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *) "master",
 	};
 	struct reftable_ref_record dest = { NULL };
 	struct stat stat_result = { 0 };
@@ -216,16 +216,16 @@ static void test_reftable_stack_uptodate(void)
 
 	int err;
 	struct reftable_ref_record ref1 = {
-		.refname = "HEAD",
+		.refname = (char *) "HEAD",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *) "master",
 	};
 	struct reftable_ref_record ref2 = {
-		.refname = "branch2",
+		.refname = (char *) "branch2",
 		.update_index = 2,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *) "master",
 	};
 
 
@@ -264,10 +264,10 @@ static void test_reftable_stack_transaction_api(void)
 	struct reftable_addition *add = NULL;
 
 	struct reftable_ref_record ref = {
-		.refname = "HEAD",
+		.refname = (char *) "HEAD",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *) "master",
 	};
 	struct reftable_ref_record dest = { NULL };
 
@@ -313,7 +313,7 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)
 		struct reftable_ref_record ref = {
 			.update_index = reftable_stack_next_update_index(st),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = (char *) "master",
 		};
 		char name[100];
 
@@ -356,7 +356,7 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)
 static void test_reftable_stack_auto_compaction_fails_gracefully(void)
 {
 	struct reftable_ref_record ref = {
-		.refname = "refs/heads/master",
+		.refname = (char *) "refs/heads/master",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_VAL1,
 		.value.val1 = {0x01},
@@ -409,16 +409,16 @@ static void test_reftable_stack_update_index_check(void)
 	struct reftable_stack *st = NULL;
 	int err;
 	struct reftable_ref_record ref1 = {
-		.refname = "name1",
+		.refname = (char *) "name1",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *) "master",
 	};
 	struct reftable_ref_record ref2 = {
-		.refname = "name2",
+		.refname = (char *) "name2",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *) "master",
 	};
 
 	err = reftable_new_stack(&st, dir, cfg);
@@ -561,7 +561,7 @@ static void test_reftable_stack_log_normalize(void)
 	struct reftable_stack *st = NULL;
 	char *dir = get_tmp_dir(__LINE__);
 	struct reftable_log_record input = {
-		.refname = "branch",
+		.refname = (char *) "branch",
 		.update_index = 1,
 		.value_type = REFTABLE_LOG_UPDATE,
 		.value = {
@@ -582,11 +582,11 @@ static void test_reftable_stack_log_normalize(void)
 	err = reftable_new_stack(&st, dir, cfg);
 	EXPECT_ERR(err);
 
-	input.value.update.message = "one\ntwo";
+	input.value.update.message = (char *) "one\ntwo";
 	err = reftable_stack_add(st, &write_test_log, &arg);
 	EXPECT(err == REFTABLE_API_ERROR);
 
-	input.value.update.message = "one";
+	input.value.update.message = (char *) "one";
 	err = reftable_stack_add(st, &write_test_log, &arg);
 	EXPECT_ERR(err);
 
@@ -594,7 +594,7 @@ static void test_reftable_stack_log_normalize(void)
 	EXPECT_ERR(err);
 	EXPECT(0 == strcmp(dest.value.update.message, "one\n"));
 
-	input.value.update.message = "two\n";
+	input.value.update.message = (char *) "two\n";
 	arg.update_index = 2;
 	err = reftable_stack_add(st, &write_test_log, &arg);
 	EXPECT_ERR(err);
@@ -697,9 +697,9 @@ static void test_reftable_stack_hash_id(void)
 	int err;
 
 	struct reftable_ref_record ref = {
-		.refname = "master",
+		.refname = (char *) "master",
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "target",
+		.value.symref = (char *) "target",
 		.update_index = 1,
 	};
 	struct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID };
@@ -879,7 +879,7 @@ static void test_reftable_stack_auto_compaction(void)
 			.refname = name,
 			.update_index = reftable_stack_next_update_index(st),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = (char *) "master",
 		};
 		snprintf(name, sizeof(name), "branch%04d", i);
 
@@ -913,7 +913,7 @@ static void test_reftable_stack_add_performs_auto_compaction(void)
 		struct reftable_ref_record ref = {
 			.update_index = reftable_stack_next_update_index(st),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = (char *) "master",
 		};
 
 		/*
@@ -964,7 +964,7 @@ static void test_reftable_stack_compaction_concurrent(void)
 			.refname = name,
 			.update_index = reftable_stack_next_update_index(st1),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = (char *) "master",
 		};
 		snprintf(name, sizeof(name), "branch%04d", i);
 
@@ -1014,7 +1014,7 @@ static void test_reftable_stack_compaction_concurrent_clean(void)
 			.refname = name,
 			.update_index = reftable_stack_next_update_index(st1),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = (char *) "master",
 		};
 		snprintf(name, sizeof(name), "branch%04d", i);
 
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 4, 2024, 12:37 UTC · re: Patrick Steinhardt · lore

[PATCH v4 05/27] refspec: remove global tag refspec structure

We have a global tag refspec structure that is used by both git-clone(1) and git-fetch(1). Initialization of the structure will break once we enable `-Wwrite-strings`, even though the breakage is harmless. While we could just add casts, the structure isn't really required in the first place as we can simply initialize the structures at the respective callsites.

Refactor the code accordingly.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c |  8 ++++++--
 builtin/fetch.c | 11 ++++++++---
 refspec.c       | 13 -------------
 refspec.h       |  1 -
 4 files changed, 14 insertions(+), 19 deletions(-)
Show changes to 4 files +14 −19

builtin/clone.c, builtin/fetch.c, refspec.c, refspec.h

diff --git a/builtin/clone.c b/builtin/clone.c
index 92ab7d7165..bde1d284a2 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -523,6 +523,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
 	struct ref *head = copy_ref(find_ref_by_name(refs, "HEAD"));
 	struct ref *local_refs = head;
 	struct ref **tail = head ? &head->next : &local_refs;
+	struct refspec_item tag_refspec;
+
+	refspec_item_init(&tag_refspec, TAG_REFSPEC, 0);
 
 	if (option_single_branch) {
 		struct ref *remote_head = NULL;
@@ -545,7 +548,7 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
 					      &tail, 0);
 
 			/* if --branch=tag, pull the requested tag explicitly */
-			get_fetch_map(remote_head, tag_refspec, &tail, 0);
+			get_fetch_map(remote_head, &tag_refspec, &tail, 0);
 		}
 		free_refs(remote_head);
 	} else {
@@ -555,8 +558,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
 	}
 
 	if (!option_mirror && !option_single_branch && !option_no_tags)
-		get_fetch_map(refs, tag_refspec, &tail, 0);
+		get_fetch_map(refs, &tag_refspec, &tail, 0);
 
+	refspec_item_clear(&tag_refspec);
 	return local_refs;
 }
 
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 75255dc600..06b60867f5 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -582,11 +582,16 @@ static struct ref *get_ref_map(struct remote *remote,
 		}
 	}
 
-	if (tags == TAGS_SET)
+	if (tags == TAGS_SET) {
+		struct refspec_item tag_refspec;
+
 		/* also fetch all tags */
-		get_fetch_map(remote_refs, tag_refspec, &tail, 0);
-	else if (tags == TAGS_DEFAULT && *autotags)
+		refspec_item_init(&tag_refspec, TAG_REFSPEC, 0);
+		get_fetch_map(remote_refs, &tag_refspec, &tail, 0);
+		refspec_item_clear(&tag_refspec);
+	} else if (tags == TAGS_DEFAULT && *autotags) {
 		find_non_local_tags(remote_refs, NULL, &ref_map, &tail);
+	}
 
 	/* Now append any refs to be updated opportunistically: */
 	*tail = orefs;
diff --git a/refspec.c b/refspec.c
index d60932f4de..1df5de6c2f 100644
--- a/refspec.c
+++ b/refspec.c
@@ -7,19 +7,6 @@
 #include "refspec.h"
 #include "strbuf.h"
 
-static struct refspec_item s_tag_refspec = {
-	.force = 0,
-	.pattern = 1,
-	.matching = 0,
-	.exact_sha1 = 0,
-	.negative = 0,
-	.src = "refs/tags/*",
-	.dst = "refs/tags/*",
-};
-
-/* See TAG_REFSPEC for the string version */
-const struct refspec_item *tag_refspec = &s_tag_refspec;
-
 /*
  * Parses the provided refspec 'refspec' and populates the refspec_item 'item'.
  * Returns 1 if successful and 0 if the refspec is invalid.
diff --git a/refspec.h b/refspec.h
index 8c0c446993..754be45cee 100644
--- a/refspec.h
+++ b/refspec.h
@@ -2,7 +2,6 @@
 #define REFSPEC_H
 
 #define TAG_REFSPEC "refs/tags/*:refs/tags/*"
-extern const struct refspec_item *tag_refspec;
 
 /**
  * A struct refspec_item holds the parsed interpretation of a refspec.  If it
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 4, 2024, 12:37 UTC · re: Patrick Steinhardt · lore

[PATCH v4 06/27] builtin/remote: cast away constness in `get_head_names()`

In `get_head_names()`, we assign the "refs/heads/*" string constant to `struct refspec_item::{src,dst}`, which are both non-constant pointers. Ideally, we'd refactor the code such that both of these fields were constant. But `struct refspec_item` is used for two different usecases with conflicting requirements:

  - To query for a source or destination based on the given refspec. The
    caller either sets `src` or `dst` as the branch that we want to
    search for, and the respective other field gets populated. The
    fields should be constant when being used as a query parameter,
    which is owned by the caller, and non-constant when being used as an
    out parameter, which is owned by the refspec item. This is is
    contradictory in itself already.
  - To store refspec items with their respective source and destination
    branches, in which case both fields should be owned by the struct.

Ideally, we'd split up this interface to clearly separate between querying and storing, which would enable us to clarify lifetimes of the strings. This would be a much bigger undertaking though.

Instead, accept the status quo for now and cast away the constness of the source and destination patterns. We know that those are not being written to or freed, so while this is ugly it certainly is fine for now.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/remote.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)
Show changes to builtin/remote.c +6 −6
diff --git a/builtin/remote.c b/builtin/remote.c
index d52b1c0e10..b44f580b8c 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -493,12 +493,13 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat
 {
 	struct ref *ref, *matches;
 	struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;
-	struct refspec_item refspec;
+	struct refspec_item refspec = {
+		.force = 0,
+		.pattern = 1,
+		.src = (char *) "refs/heads/*",
+		.dst = (char *) "refs/heads/*",
+	};
 
-	memset(&refspec, 0, sizeof(refspec));
-	refspec.force = 0;
-	refspec.pattern = 1;
-	refspec.src = refspec.dst = "refs/heads/*";
 	get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
 	matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
 				    fetch_map, 1);
@@ -507,7 +508,6 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat
 
 	free_refs(fetch_map);
 	free_refs(matches);
-
 	return 0;
 }
 
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 4, 2024, 12:37 UTC · re: Patrick Steinhardt · lore

[PATCH v4 07/27] diff: cast string constant in `fill_textconv()`

The `fill_textconv()` function is responsible for converting an input file with a textconv driver, which is then passed to the caller. Weirdly though, the function also handles the case where there is no textconv driver at all. In that case, it will return either the contents of the populated filespec, or an empty string if the filespec is invalid.

These two cases have differing memory ownership semantics. When there is a textconv driver, then the result is an allocated string. Otherwise, the result is either a string constant or owned by the filespec struct. All callers are in fact aware of this weirdness and only end up freeing the output buffer when they had a textconv driver.

Ideally, we'd split up this interface to only perform the conversion via the textconv driver, and BUG in case the caller didn't provide one. This would make memory ownership semantics much more straight forward. For now though, let's simply cast the empty string constant to `char *` to avoid a warning with `-Wwrite-strings`. This is equivalent to the same cast that we already have in `fill_mmfile()`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 diff.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to diff.c +1 −1
diff --git a/diff.c b/diff.c
index ffd867ef6c..cecda216cf 100644
--- a/diff.c
+++ b/diff.c
@@ -7235,7 +7235,7 @@ size_t fill_textconv(struct repository *r,
 
 	if (!driver) {
 		if (!DIFF_FILE_VALID(df)) {
-			*outbuf = "";
+			*outbuf = (char *) "";
 			return 0;
 		}
 		if (diff_populate_filespec(r, df, NULL))
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 4, 2024, 12:37 UTC · re: Patrick Steinhardt · lore

[PATCH v4 08/27] line-log: stop assigning string constant to file parent buffer

Stop assigning a string constant to the file parent buffer and instead assign an allocated string. While the code is fine in practice, it will break once we compile with `-Wwrite-strings`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 line-log.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
Show changes to line-log.c +3 −1
diff --git a/line-log.c b/line-log.c
index 8ff6ccb772..bd3e663c24 100644
--- a/line-log.c
+++ b/line-log.c
@@ -1032,6 +1032,7 @@ static int process_diff_filepair(struct rev_info *rev,
 	struct range_set tmp;
 	struct diff_ranges diff;
 	mmfile_t file_parent, file_target;
+	char *parent_data_to_free = NULL;
 
 	assert(pair->two->path);
 	while (rg) {
@@ -1056,7 +1057,7 @@ static int process_diff_filepair(struct rev_info *rev,
 		file_parent.ptr = pair->one->data;
 		file_parent.size = pair->one->size;
 	} else {
-		file_parent.ptr = "";
+		file_parent.ptr = parent_data_to_free = xstrdup("");
 		file_parent.size = 0;
 	}
 
@@ -1075,6 +1076,7 @@ static int process_diff_filepair(struct rev_info *rev,
 
 	diff_ranges_release(&diff);
 
+	free(parent_data_to_free);
 	return ((*diff_out)->parent.nr > 0);
 }
 
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 4, 2024, 12:37 UTC · re: Patrick Steinhardt · lore

[PATCH v4 09/27] line-log: always allocate the output prefix

The returned string by `output_prefix()` is sometimes a string constant and sometimes an allocated string. This has been fine until now because we always leak the allocated strings, and thus we never tried to free the string constant.

Fix the code to always return an allocated string and free the returned value at all callsites.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 line-log.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)
Show changes to line-log.c +11 −7
diff --git a/line-log.c b/line-log.c
index bd3e663c24..67c80b39a0 100644
--- a/line-log.c
+++ b/line-log.c
@@ -899,14 +899,12 @@ static void print_line(const char *prefix, char first,
 
 static char *output_prefix(struct diff_options *opt)
 {
-	char *prefix = "";
-
 	if (opt->output_prefix) {
 		struct strbuf *sb = opt->output_prefix(opt, opt->output_prefix_data);
-		prefix = sb->buf;
+		return sb->buf;
+	} else {
+		return xstrdup("");
 	}
-
-	return prefix;
 }
 
 static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *range)
@@ -927,7 +925,7 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang
 	const char *c_context = diff_get_color(opt->use_color, DIFF_CONTEXT);
 
 	if (!pair || !diff)
-		return;
+		goto out;
 
 	if (pair->one->oid_valid)
 		fill_line_ends(rev->diffopt.repo, pair->one, &p_lines, &p_ends);
@@ -1002,8 +1000,10 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang
 				   c_context, c_reset, opt->file);
 	}
 
+out:
 	free(p_ends);
 	free(t_ends);
+	free(prefix);
 }
 
 /*
@@ -1012,7 +1012,11 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang
  */
 static void dump_diff_hacky(struct rev_info *rev, struct line_log_data *range)
 {
-	fprintf(rev->diffopt.file, "%s\n", output_prefix(&rev->diffopt));
+	char *prefix = output_prefix(&rev->diffopt);
+
+	fprintf(rev->diffopt.file, "%s\n", prefix);
+	free(prefix);
+
 	while (range) {
 		dump_diff_hacky_one(rev, range);
 		range = range->next;
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 4, 2024, 12:37 UTC · re: Patrick Steinhardt · lore

[PATCH v4 10/27] entry: refactor how we remove items for delayed checkouts

When finalizing a delayed checkout, we sort out several strings from the passed-in string list by first assigning the empty string to those filters and then calling `string_list_remove_empty_items()`. Assigning the empty string will cause compiler warnings though as the string is a `char *` once we enable `-Wwrite-strings`.

Refactor the code to use a `NULL` pointer with `filter_string_list()` instead to avoid this warning.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 entry.c | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)
Show changes to entry.c +10 −4
diff --git a/entry.c b/entry.c
index b8c257f6f9..f291d8eee6 100644
--- a/entry.c
+++ b/entry.c
@@ -167,6 +167,11 @@ static int remove_available_paths(struct string_list_item *item, void *cb_data)
 	return !available;
 }
 
+static int string_is_not_null(struct string_list_item *item, void *data UNUSED)
+{
+	return !!item->string;
+}
+
 int finish_delayed_checkout(struct checkout *state, int show_progress)
 {
 	int errs = 0;
@@ -189,7 +194,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 			if (!async_query_available_blobs(filter->string, &available_paths)) {
 				/* Filter reported an error */
 				errs = 1;
-				filter->string = "";
+				filter->string = NULL;
 				continue;
 			}
 			if (available_paths.nr <= 0) {
@@ -199,7 +204,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 				 * filter from the list (see
 				 * "string_list_remove_empty_items" call below).
 				 */
-				filter->string = "";
+				filter->string = NULL;
 				continue;
 			}
 
@@ -225,7 +230,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 					 * Do not ask the filter for available blobs,
 					 * again, as the filter is likely buggy.
 					 */
-					filter->string = "";
+					filter->string = NULL;
 					continue;
 				}
 				ce = index_file_exists(state->istate, path->string,
@@ -239,7 +244,8 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 					errs = 1;
 			}
 		}
-		string_list_remove_empty_items(&dco->filters, 0);
+
+		filter_string_list(&dco->filters, 0, string_is_not_null, NULL);
 	}
 	stop_progress(&progress);
 	string_list_clear(&dco->filters, 0);
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 4, 2024, 12:37 UTC · re: Patrick Steinhardt · lore

[PATCH v4 11/27] ident: add casts for fallback name and GECOS

In `xgetpwuid_self()`, we return a fallback identity when it was not possible to look up the current identity. This fallback identity needs to be internal and must never be written to by the calles as specified by getpwuid(3P). As both the `pw_name` and `pw_gecos` fields are marked as non-constant though, it will cause a warning to assign constant strings to them once compiling with `-Wwrite-strings`.

Add explicit casts to avoid the warning.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 ident.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
Show changes to ident.c +2 −2
diff --git a/ident.c b/ident.c
index cc7afdbf81..caf41fb2a9 100644
--- a/ident.c
+++ b/ident.c
@@ -46,9 +46,9 @@ static struct passwd *xgetpwuid_self(int *is_bogus)
 	pw = getpwuid(getuid());
 	if (!pw) {
 		static struct passwd fallback;
-		fallback.pw_name = "unknown";
+		fallback.pw_name = (char *) "unknown";
 #ifndef NO_GECOS_IN_PWENT
-		fallback.pw_gecos = "Unknown";
+		fallback.pw_gecos = (char *) "Unknown";
 #endif
 		pw = &fallback;
 		if (is_bogus)
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 4, 2024, 12:37 UTC · re: Patrick Steinhardt · lore

[PATCH v4 12/27] object-file: mark cached object buffers as const

The buffers of cached objects are never modified, but are still stored as a non-constant pointer. This will cause a compiler warning once we enable the `-Wwrite-strings` compiler warning as we assign an empty constant string when initializing the static `empty_tree` cached object.

Convert the field to be constant. This requires us to shuffle around the code a bit because we memcpy(3P) into the allocated buffer in `pretend_object_file()`. This is easily fixed though by allocating the buffer into a temporary variable first.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 object-file.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)
Show changes to object-file.c +6 −3
diff --git a/object-file.c b/object-file.c
index 610b1f465c..3afe9fce06 100644
--- a/object-file.c
+++ b/object-file.c
@@ -277,7 +277,7 @@ int hash_algo_by_length(int len)
 static struct cached_object {
 	struct object_id oid;
 	enum object_type type;
-	void *buf;
+	const void *buf;
 	unsigned long size;
 } *cached_objects;
 static int cached_object_nr, cached_object_alloc;
@@ -1778,6 +1778,10 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,
 			struct object_id *oid)
 {
 	struct cached_object *co;
+	char *co_buf;
+
+	co_buf = xmalloc(len);
+	memcpy(co_buf, buf, len);
 
 	hash_object_file(the_hash_algo, buf, len, type, oid);
 	if (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||
@@ -1787,8 +1791,7 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,
 	co = &cached_objects[cached_object_nr++];
 	co->size = len;
 	co->type = type;
-	co->buf = xmalloc(len);
-	memcpy(co->buf, buf, len);
+	co->buf = co_buf;
 	oidcpy(&co->oid, oid);
 	return 0;
 }
-- 
2.45.1.410.g58bac47f8e.dirty
Junio C Hamano· Jun 6, 2024, 06:02 UTC · re: Patrick Steinhardt · lore

Re: [PATCH v4 12/27] object-file: mark cached object buffers as const

Patrick Steinhardt <ps@pks.im> writes:
Show 14 quoted lines
> The buffers of cached objects are never modified, but are still stored
> as a non-constant pointer. This will cause a compiler warning once we
> enable the `-Wwrite-strings` compiler warning as we assign an empty
> constant string when initializing the static `empty_tree` cached object.
>
> Convert the field to be constant. This requires us to shuffle around
> the code a bit because we memcpy(3P) into the allocated buffer in
> `pretend_object_file()`. This is easily fixed though by allocating the
> buffer into a temporary variable first.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  object-file.c | 9 ++++++---
>  1 file changed, 6 insertions(+), 3 deletions(-)

I haven't dug into the exact details, but I noticed that t6130, t7010, and t8002 started breaking linux-leaks/linux-reftable-leaks CI jobs for 'seen'. 'seen' excluding ps/no-writable-strings seems to pass the tests, and bisection indicates that 'seen' excluding ps/no-writable-strings after this step [12/27] still passes, but once this step is included, the leak tests break.

Show 24 quoted lines
> diff --git a/object-file.c b/object-file.c
> index 610b1f465c..3afe9fce06 100644
> --- a/object-file.c
> +++ b/object-file.c
> @@ -277,7 +277,7 @@ int hash_algo_by_length(int len)
>  static struct cached_object {
>  	struct object_id oid;
>  	enum object_type type;
> -	void *buf;
> +	const void *buf;
>  	unsigned long size;
>  } *cached_objects;
>  static int cached_object_nr, cached_object_alloc;
> @@ -1778,6 +1778,10 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,
>  			struct object_id *oid)
>  {
>  	struct cached_object *co;
> +	char *co_buf;
> +
> +	co_buf = xmalloc(len);
> +	memcpy(co_buf, buf, len);
>  
>  	hash_object_file(the_hash_algo, buf, len, type, oid);
>  	if (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||

There is an early return around here. Perhaps we are leaking co_buf when we hit it?

Show 10 quoted lines
> @@ -1787,8 +1791,7 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,
>  	co = &cached_objects[cached_object_nr++];
>  	co->size = len;
>  	co->type = type;
> -	co->buf = xmalloc(len);
> -	memcpy(co->buf, buf, len);
> +	co->buf = co_buf;
>  	oidcpy(&co->oid, oid);
>  	return 0;
>  }
Junio C Hamano· Jun 6, 2024, 06:10 UTC · re: Junio C Hamano · lore

Re: [PATCH v4 12/27] object-file: mark cached object buffers as const

Junio C Hamano <gitster@pobox.com> writes:
Show 8 quoted lines
>> +	co_buf = xmalloc(len);
>> +	memcpy(co_buf, buf, len);
>>  
>>  	hash_object_file(the_hash_algo, buf, len, type, oid);
>>  	if (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||
>
> There is an early return around here.  Perhaps we are leaking co_buf
> when we hit it?

Indeed, that seems to be the case. With the attached at the tip of the branch and rebuilding 'seen' seems to pass these 6130, 7010, 8002 tests with SANTIZE=leak.

From f307bbf7bd317d90db29bd1589b49e84b9e37e88 Mon Sep 17 00:00:00 2001
From: Junio C Hamano <gitster@pobox.com>
Date: Wed, 5 Jun 2024 23:03:34 -0700
Subject: [PATCH] fixup! object-file: mark cached object buffers as const
---
 object-file.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
Show changes to object-file.c +3 −1
diff --git a/object-file.c b/object-file.c
index b5b5a59dc6..2d5bd3a211 100644
--- a/object-file.c
+++ b/object-file.c
@@ -1785,8 +1785,10 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,
 
 	hash_object_file(the_hash_algo, buf, len, type, oid);
 	if (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||
-	    find_cached_object(oid))
+	    find_cached_object(oid)) {
+		free(co_buf);
 		return 0;
+	}
 	ALLOC_GROW(cached_objects, cached_object_nr + 1, cached_object_alloc);
 	co = &cached_objects[cached_object_nr++];
 	co->size = len;
-- 
2.45.2-409-g7b0defb391
Patrick Steinhardt· Jun 6, 2024, 10:03 UTC · re: Junio C Hamano · lore

Re: [PATCH v4 12/27] object-file: mark cached object buffers as const

On Wed, Jun 05, 2024 at 11:10:20PM -0700, Junio C Hamano wrote:
Show 14 quoted lines
> Junio C Hamano <gitster@pobox.com> writes:
> 
> >> +	co_buf = xmalloc(len);
> >> +	memcpy(co_buf, buf, len);
> >>  
> >>  	hash_object_file(the_hash_algo, buf, len, type, oid);
> >>  	if (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||
> >
> > There is an early return around here.  Perhaps we are leaking co_buf
> > when we hit it?
> 
> Indeed, that seems to be the case.  With the attached at the tip of
> the branch and rebuilding 'seen' seems to pass these 6130, 7010, 8002
> tests with SANTIZE=leak.
Indeed, thanks. Rolled into my local version now.
Patrick
Junio C Hamano· Jun 6, 2024, 16:25 UTC · re: Junio C Hamano · lore

Re: [PATCH v4 12/27] object-file: mark cached object buffers as const

Junio C Hamano <gitster@pobox.com> writes:
Show 29 quoted lines
> Indeed, that seems to be the case.  With the attached at the tip of
> the branch and rebuilding 'seen' seems to pass these 6130, 7010, 8002
> tests with SANTIZE=leak.
>
> From f307bbf7bd317d90db29bd1589b49e84b9e37e88 Mon Sep 17 00:00:00 2001
> From: Junio C Hamano <gitster@pobox.com>
> Date: Wed, 5 Jun 2024 23:03:34 -0700
> Subject: [PATCH] fixup! object-file: mark cached object buffers as const
>
> ---
>  object-file.c | 4 +++-
>  1 file changed, 3 insertions(+), 1 deletion(-)
>
> diff --git a/object-file.c b/object-file.c
> index b5b5a59dc6..2d5bd3a211 100644
> --- a/object-file.c
> +++ b/object-file.c
> @@ -1785,8 +1785,10 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,
>  
>  	hash_object_file(the_hash_algo, buf, len, type, oid);
>  	if (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||
> -	    find_cached_object(oid))
> +	    find_cached_object(oid)) {
> +		free(co_buf);
>  		return 0;
> +	}
>  	ALLOC_GROW(cached_objects, cached_object_nr + 1, cached_object_alloc);
>  	co = &cached_objects[cached_object_nr++];
>  	co->size = len;

Wait. Why do we need to allocate co_buf that early in the first place? IOW, shouldn't the fixup be more like this?

 object-file.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)
Show changes to diff +2 −3
diff --git c/object-file.c w/object-file.c
index b5b5a59dc6..0b58751f94 100644
--- c/object-file.c
+++ w/object-file.c
@@ -1780,9 +1780,6 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,
 	struct cached_object *co;
 	char *co_buf;
 
-	co_buf = xmalloc(len);
-	memcpy(co_buf, buf, len);
-
 	hash_object_file(the_hash_algo, buf, len, type, oid);
 	if (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||
 	    find_cached_object(oid))
@@ -1791,6 +1788,8 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,
 	co = &cached_objects[cached_object_nr++];
 	co->size = len;
 	co->type = type;
+	co_buf = xmalloc(len);
+	memcpy(co_buf, buf, len);
 	co->buf = co_buf;
 	oidcpy(&co->oid, oid);
 	return 0;
Patrick Steinhardt· Jun 7, 2024, 04:52 UTC · re: Junio C Hamano · lore

Re: [PATCH v4 12/27] object-file: mark cached object buffers as const

On Thu, Jun 06, 2024 at 09:25:20AM -0700, Junio C Hamano wrote:
Show 34 quoted lines
> Junio C Hamano <gitster@pobox.com> writes:
> 
> > Indeed, that seems to be the case.  With the attached at the tip of
> > the branch and rebuilding 'seen' seems to pass these 6130, 7010, 8002
> > tests with SANTIZE=leak.
> >
> > From f307bbf7bd317d90db29bd1589b49e84b9e37e88 Mon Sep 17 00:00:00 2001
> > From: Junio C Hamano <gitster@pobox.com>
> > Date: Wed, 5 Jun 2024 23:03:34 -0700
> > Subject: [PATCH] fixup! object-file: mark cached object buffers as const
> >
> > ---
> >  object-file.c | 4 +++-
> >  1 file changed, 3 insertions(+), 1 deletion(-)
> >
> > diff --git a/object-file.c b/object-file.c
> > index b5b5a59dc6..2d5bd3a211 100644
> > --- a/object-file.c
> > +++ b/object-file.c
> > @@ -1785,8 +1785,10 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,
> >  
> >  	hash_object_file(the_hash_algo, buf, len, type, oid);
> >  	if (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||
> > -	    find_cached_object(oid))
> > +	    find_cached_object(oid)) {
> > +		free(co_buf);
> >  		return 0;
> > +	}
> >  	ALLOC_GROW(cached_objects, cached_object_nr + 1, cached_object_alloc);
> >  	co = &cached_objects[cached_object_nr++];
> >  	co->size = len;
> 
> Wait.  Why do we need to allocate co_buf that early in the first
> place?  IOW, shouldn't the fixup be more like this?
That is of course the much more elegant solution here. Will adapt.
Patrick
Patrick Steinhardt· Jun 4, 2024, 12:37 UTC · re: Patrick Steinhardt · lore

[PATCH v4 13/27] object-file: make `buf` parameter of `index_mem()` a constant

The `buf` parameter of `index_mem()` is a non-constant string. This will break once we enable `-Wwrite-strings` because we also pass constants from at least one callsite.

Adapt the parameter to be a constant. As we cannot free the buffer without casting now, this also requires us to move the lifetime of the nested buffer around.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 object-file.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)
Show changes to object-file.c +7 −7
diff --git a/object-file.c b/object-file.c
index 3afe9fce06..b5b5a59dc6 100644
--- a/object-file.c
+++ b/object-file.c
@@ -2485,12 +2485,13 @@ static int hash_format_check_report(struct fsck_options *opts UNUSED,
 }
 
 static int index_mem(struct index_state *istate,
-		     struct object_id *oid, void *buf, size_t size,
+		     struct object_id *oid,
+		     const void *buf, size_t size,
 		     enum object_type type,
 		     const char *path, unsigned flags)
 {
+	struct strbuf nbuf = STRBUF_INIT;
 	int ret = 0;
-	int re_allocated = 0;
 	int write_object = flags & HASH_WRITE_OBJECT;
 
 	if (!type)
@@ -2500,11 +2501,10 @@ static int index_mem(struct index_state *istate,
 	 * Convert blobs to git internal format
 	 */
 	if ((type == OBJ_BLOB) && path) {
-		struct strbuf nbuf = STRBUF_INIT;
 		if (convert_to_git(istate, path, buf, size, &nbuf,
 				   get_conv_flags(flags))) {
-			buf = strbuf_detach(&nbuf, &size);
-			re_allocated = 1;
+			buf = nbuf.buf;
+			size = nbuf.len;
 		}
 	}
 	if (flags & HASH_FORMAT_CHECK) {
@@ -2521,8 +2521,8 @@ static int index_mem(struct index_state *istate,
 		ret = write_object_file(buf, size, type, oid);
 	else
 		hash_object_file(the_hash_algo, buf, size, type, oid);
-	if (re_allocated)
-		free(buf);
+
+	strbuf_release(&nbuf);
 	return ret;
 }
 
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 4, 2024, 12:37 UTC · re: Patrick Steinhardt · lore

[PATCH v4 14/27] pretty: add casts for decoration option pointers

The `struct decoration_options` have a prefix and suffix field which are both non-constant, but we assign a constant pointer to them. This is safe to do because we pass them to `format_decorations()`, which never modifies these pointers, and then immediately discard the structure. Add explicit casts to avoid compilation warnings with `-Wwrite-strings`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 pretty.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
Show changes to pretty.c +2 −2
diff --git a/pretty.c b/pretty.c
index ec05db5655..1df9d635fb 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1584,8 +1584,8 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
 	case 'D':
 		{
 			const struct decoration_options opts = {
-				.prefix = "",
-				.suffix = ""
+				.prefix = (char *) "",
+				.suffix = (char *) "",
 			};
 
 			format_decorations(sb, commit, c->auto_color, &opts);
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 4, 2024, 12:38 UTC · re: Patrick Steinhardt · lore

[PATCH v4 15/27] compat/win32: fix const-correctness with string constants

Adjust various places in our Win32 compatibility layer where we are not assigning string constants to `const char *` variables.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 compat/basename.c | 16 ++++++++++++++--
 compat/mingw.c    | 28 ++++++++++++++++------------
 compat/winansi.c  |  2 +-
 3 files changed, 31 insertions(+), 15 deletions(-)
Show changes to 3 files +31 −15

compat/basename.c, compat/mingw.c, compat/winansi.c

diff --git a/compat/basename.c b/compat/basename.c
index 96bd9533b4..c33579ef61 100644
--- a/compat/basename.c
+++ b/compat/basename.c
@@ -10,7 +10,13 @@ char *gitbasename (char *path)
 		skip_dos_drive_prefix(&path);
 
 	if (!path || !*path)
-		return ".";
+		/*
+		 * basename(3P) is mis-specified because it returns a
+		 * non-constant pointer even though it is specified to return a
+		 * pointer to internal memory at times. The cast is a result of
+		 * that.
+		 */
+		return (char *) ".";
 
 	for (base = path; *path; path++) {
 		if (!is_dir_sep(*path))
@@ -34,7 +40,13 @@ char *gitdirname(char *path)
 	int dos_drive_prefix;
 
 	if (!p)
-		return ".";
+		/*
+		 * dirname(3P) is mis-specified because it returns a
+		 * non-constant pointer even though it is specified to return a
+		 * pointer to internal memory at times. The cast is a result of
+		 * that.
+		 */
+		return (char *) ".";
 
 	if ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p)
 		goto dot;
diff --git a/compat/mingw.c b/compat/mingw.c
index 6b06ea540f..d378cd04cb 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -2279,7 +2279,11 @@ struct passwd *getpwuid(int uid)
 	p->pw_name = user_name;
 	p->pw_gecos = get_extended_user_info(NameDisplay);
 	if (!p->pw_gecos)
-		p->pw_gecos = "unknown";
+		/*
+		 * Data returned by getpwuid(3P) is treated as internal and
+		 * must never be written to or freed.
+		 */
+		p->pw_gecos = (char *) "unknown";
 	p->pw_dir = NULL;
 
 	initialized = 1;
@@ -2800,16 +2804,16 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)
 			strbuf_addf(report, "'%s' is on a file system that does "
 				    "not record ownership\n", path);
 		} else if (report) {
-			LPSTR str1, str2, str3, str4, to_free1 = NULL,
-			    to_free3 = NULL, to_local_free2 = NULL,
-			    to_local_free4 = NULL;
+			PCSTR str1, str2, str3, str4;
+			LPSTR to_free1 = NULL, to_free3 = NULL,
+			    to_local_free2 = NULL, to_local_free4 = NULL;
 
-			if (user_sid_to_user_name(sid, &str1))
-				to_free1 = str1;
+			if (user_sid_to_user_name(sid, &to_free1))
+				str1 = to_free1;
 			else
 				str1 = "(inconvertible)";
-			if (ConvertSidToStringSidA(sid, &str2))
-				to_local_free2 = str2;
+			if (ConvertSidToStringSidA(sid, &to_local_free2))
+				str2 = to_local_free2;
 			else
 				str2 = "(inconvertible)";
 
@@ -2822,13 +2826,13 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)
 				str4 = "(invalid)";
 			} else {
 				if (user_sid_to_user_name(current_user_sid,
-							  &str3))
-					to_free3 = str3;
+							  &to_free3))
+					str3 = to_free3;
 				else
 					str3 = "(inconvertible)";
 				if (ConvertSidToStringSidA(current_user_sid,
-							   &str4))
-					to_local_free4 = str4;
+							   &to_local_free4))
+					str4 = to_local_free4;
 				else
 					str4 = "(inconvertible)";
 			}
diff --git a/compat/winansi.c b/compat/winansi.c
index f83610f684..575813bde8 100644
--- a/compat/winansi.c
+++ b/compat/winansi.c
@@ -139,7 +139,7 @@ static void write_console(unsigned char *str, size_t len)
 	/* convert utf-8 to utf-16 */
 	int wlen = xutftowcsn(wbuf, (char*) str, ARRAY_SIZE(wbuf), len);
 	if (wlen < 0) {
-		wchar_t *err = L"[invalid]";
+		const wchar_t *err = L"[invalid]";
 		WriteConsoleW(console, err, wcslen(err), &dummy, NULL);
 		return;
 	}
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 4, 2024, 12:38 UTC · re: Patrick Steinhardt · lore

[PATCH v4 16/27] http: do not assign string constant to non-const field

In `write_accept_language()`, we put all acceptable languages into an array. While all entries in that array are allocated strings, the final entry in that array is a string constant. This is fine because we explicitly skip over the last entry when freeing the array, but will cause warnings once we enable `-Wwrite-strings`.

Adapt the code to also allocate the final entry.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 http.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)
Show changes to http.c +2 −3
diff --git a/http.c b/http.c
index 67cc47d28f..2dea2d03da 100644
--- a/http.c
+++ b/http.c
@@ -1974,7 +1974,7 @@ static void write_accept_language(struct strbuf *buf)
 
 		/* add '*' */
 		REALLOC_ARRAY(language_tags, num_langs + 1);
-		language_tags[num_langs++] = "*"; /* it's OK; this won't be freed */
+		language_tags[num_langs++] = xstrdup("*");
 
 		/* compute decimal_places */
 		for (max_q = 1, decimal_places = 0;
@@ -2004,8 +2004,7 @@ static void write_accept_language(struct strbuf *buf)
 		}
 	}
 
-	/* free language tags -- last one is a static '*' */
-	for (i = 0; i < num_langs - 1; i++)
+	for (i = 0; i < num_langs; i++)
 		free(language_tags[i]);
 	free(language_tags);
 }
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 4, 2024, 12:38 UTC · re: Patrick Steinhardt · lore

[PATCH v4 17/27] parse-options: cast long name for OPTION_ALIAS

We assign the long name for OPTION_ALIAS options to a non-constant value field. We know that the variable will never be written to, but this will cause warnings once we enable `-Wwrite-strings`.

Cast away the constness to be prepared for this change.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 parse-options.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to parse-options.h +1 −1
diff --git a/parse-options.h b/parse-options.h
index bd62e20268..ae15342390 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -355,7 +355,7 @@ struct option {
 	.type = OPTION_ALIAS, \
 	.short_name = (s), \
 	.long_name = (l), \
-	.value = (source_long_name), \
+	.value = (char *)(source_long_name), \
 }
 
 #define OPT_SUBCOMMAND_F(l, v, fn, f) { \
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 4, 2024, 12:38 UTC · re: Patrick Steinhardt · lore

[PATCH v4 18/27] send-pack: always allocate receive status

In `receive_status()`, we record the reason why ref updates have been rejected by the remote via the `remote_status`. But while we allocate the assigned string when a reason was given, we assign a string constant when no reason was given.

This has been working fine so far due to two reasons:
  - We don't ever free the refs in git-send-pack(1)'
  - Remotes always give a reason, at least as implemented by Git proper.

Adapt the code to always allocate the receive status string and free the refs.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/send-pack.c | 2 ++
 send-pack.c         | 2 +-
 2 files changed, 3 insertions(+), 1 deletion(-)
Show changes to 2 files +3 −1

builtin/send-pack.c, send-pack.c

diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index 3df9eaad09..17cae6bbbd 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -336,5 +336,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
 		/* stable plumbing output; do not modify or localize */
 		fprintf(stderr, "Everything up-to-date\n");
 
+	free_refs(remote_refs);
+	free_refs(local_refs);
 	return ret;
 }
diff --git a/send-pack.c b/send-pack.c
index 37f59d4f66..88e96d000b 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -259,7 +259,7 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)
 			if (p)
 				hint->remote_status = xstrdup(p);
 			else
-				hint->remote_status = "failed";
+				hint->remote_status = xstrdup("failed");
 		} else {
 			hint->status = REF_STATUS_OK;
 			hint->remote_status = xstrdup_or_null(p);
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 4, 2024, 12:38 UTC · re: Patrick Steinhardt · lore

[PATCH v4 19/27] remote-curl: avoid assigning string constant to non-const variable

When processing remote options, we split the option line into two by searching for a space. If there is one, we replace the space with '\0', otherwise we implicitly assume that the value is "true" and thus assign a string constant.

As the return value of strchr(3P) weirdly enough is a `char *` even though it gets a `const char *` as input, the assigned-to variable also is a non-constant. This is fine though because the argument is in fact an allocated string, and thus we are allowed to modify it. But this will break once we enable `-Wwrite-strings`.

Refactor the code stop splitting the fields with '\0' altogether. Instead, we can pass the length of the option name to `set_option()` and then use strncmp(3P) instead of strcmp(3P).

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 remote-curl.c | 53 ++++++++++++++++++++++++++-------------------------
 1 file changed, 27 insertions(+), 26 deletions(-)
Show changes to remote-curl.c +27 −26
diff --git a/remote-curl.c b/remote-curl.c
index cae98384da..d0f767df8e 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -58,9 +58,9 @@ struct options {
 static struct options options;
 static struct string_list cas_options = STRING_LIST_INIT_DUP;
 
-static int set_option(const char *name, const char *value)
+static int set_option(const char *name, size_t namelen, const char *value)
 {
-	if (!strcmp(name, "verbosity")) {
+	if (!strncmp(name, "verbosity", namelen)) {
 		char *end;
 		int v = strtol(value, &end, 10);
 		if (value == end || *end)
@@ -68,7 +68,7 @@ static int set_option(const char *name, const char *value)
 		options.verbosity = v;
 		return 0;
 	}
-	else if (!strcmp(name, "progress")) {
+	else if (!strncmp(name, "progress", namelen)) {
 		if (!strcmp(value, "true"))
 			options.progress = 1;
 		else if (!strcmp(value, "false"))
@@ -77,7 +77,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "depth")) {
+	else if (!strncmp(name, "depth", namelen)) {
 		char *end;
 		unsigned long v = strtoul(value, &end, 10);
 		if (value == end || *end)
@@ -85,15 +85,15 @@ static int set_option(const char *name, const char *value)
 		options.depth = v;
 		return 0;
 	}
-	else if (!strcmp(name, "deepen-since")) {
+	else if (!strncmp(name, "deepen-since", namelen)) {
 		options.deepen_since = xstrdup(value);
 		return 0;
 	}
-	else if (!strcmp(name, "deepen-not")) {
+	else if (!strncmp(name, "deepen-not", namelen)) {
 		string_list_append(&options.deepen_not, value);
 		return 0;
 	}
-	else if (!strcmp(name, "deepen-relative")) {
+	else if (!strncmp(name, "deepen-relative", namelen)) {
 		if (!strcmp(value, "true"))
 			options.deepen_relative = 1;
 		else if (!strcmp(value, "false"))
@@ -102,7 +102,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "followtags")) {
+	else if (!strncmp(name, "followtags", namelen)) {
 		if (!strcmp(value, "true"))
 			options.followtags = 1;
 		else if (!strcmp(value, "false"))
@@ -111,7 +111,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "dry-run")) {
+	else if (!strncmp(name, "dry-run", namelen)) {
 		if (!strcmp(value, "true"))
 			options.dry_run = 1;
 		else if (!strcmp(value, "false"))
@@ -120,7 +120,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "check-connectivity")) {
+	else if (!strncmp(name, "check-connectivity", namelen)) {
 		if (!strcmp(value, "true"))
 			options.check_self_contained_and_connected = 1;
 		else if (!strcmp(value, "false"))
@@ -129,7 +129,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "cas")) {
+	else if (!strncmp(name, "cas", namelen)) {
 		struct strbuf val = STRBUF_INIT;
 		strbuf_addstr(&val, "--force-with-lease=");
 		if (*value != '"')
@@ -139,7 +139,7 @@ static int set_option(const char *name, const char *value)
 		string_list_append(&cas_options, val.buf);
 		strbuf_release(&val);
 		return 0;
-	} else if (!strcmp(name, TRANS_OPT_FORCE_IF_INCLUDES)) {
+	} else if (!strncmp(name, TRANS_OPT_FORCE_IF_INCLUDES, namelen)) {
 		if (!strcmp(value, "true"))
 			options.force_if_includes = 1;
 		else if (!strcmp(value, "false"))
@@ -147,7 +147,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "cloning")) {
+	} else if (!strncmp(name, "cloning", namelen)) {
 		if (!strcmp(value, "true"))
 			options.cloning = 1;
 		else if (!strcmp(value, "false"))
@@ -155,7 +155,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "update-shallow")) {
+	} else if (!strncmp(name, "update-shallow", namelen)) {
 		if (!strcmp(value, "true"))
 			options.update_shallow = 1;
 		else if (!strcmp(value, "false"))
@@ -163,7 +163,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "pushcert")) {
+	} else if (!strncmp(name, "pushcert", namelen)) {
 		if (!strcmp(value, "true"))
 			options.push_cert = SEND_PACK_PUSH_CERT_ALWAYS;
 		else if (!strcmp(value, "false"))
@@ -173,7 +173,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "atomic")) {
+	} else if (!strncmp(name, "atomic", namelen)) {
 		if (!strcmp(value, "true"))
 			options.atomic = 1;
 		else if (!strcmp(value, "false"))
@@ -181,7 +181,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "push-option")) {
+	} else if (!strncmp(name, "push-option", namelen)) {
 		if (*value != '"')
 			string_list_append(&options.push_options, value);
 		else {
@@ -192,7 +192,7 @@ static int set_option(const char *name, const char *value)
 						 strbuf_detach(&unquoted, NULL));
 		}
 		return 0;
-	} else if (!strcmp(name, "family")) {
+	} else if (!strncmp(name, "family", namelen)) {
 		if (!strcmp(value, "ipv4"))
 			git_curl_ipresolve = CURL_IPRESOLVE_V4;
 		else if (!strcmp(value, "ipv6"))
@@ -202,16 +202,16 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "from-promisor")) {
+	} else if (!strncmp(name, "from-promisor", namelen)) {
 		options.from_promisor = 1;
 		return 0;
-	} else if (!strcmp(name, "refetch")) {
+	} else if (!strncmp(name, "refetch", namelen)) {
 		options.refetch = 1;
 		return 0;
-	} else if (!strcmp(name, "filter")) {
+	} else if (!strncmp(name, "filter", namelen)) {
 		options.filter = xstrdup(value);
 		return 0;
-	} else if (!strcmp(name, "object-format")) {
+	} else if (!strncmp(name, "object-format", namelen)) {
 		options.object_format = 1;
 		if (strcmp(value, "true"))
 			die(_("unknown value for object-format: %s"), value);
@@ -1588,15 +1588,16 @@ int cmd_main(int argc, const char **argv)
 			parse_push(&buf);
 
 		} else if (skip_prefix(buf.buf, "option ", &arg)) {
-			char *value = strchr(arg, ' ');
+			const char *value = strchrnul(arg, ' ');
+			size_t arglen = value - arg;
 			int result;
 
-			if (value)
-				*value++ = '\0';
+			if (*value)
+				value++; /* skip over SP */
 			else
 				value = "true";
 
-			result = set_option(arg, value);
+			result = set_option(arg, arglen, value);
 			if (!result)
 				printf("ok\n");
 			else if (result < 0)
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 4, 2024, 12:38 UTC · re: Patrick Steinhardt · lore

[PATCH v4 20/27] revision: always store allocated strings in output encoding

The `git_log_output_encoding` variable can be set via the `--encoding=` option. When doing so, we conditionally either assign it to the passed value, or if the value is "none" we assign it the empty string. Depending on which of the both code paths we pick though, the variable may end up being assigned either an allocated string or a string constant.

This is somewhat risky and may easily lead to bugs when a different code path may want to reassign a new value to it, freeing the previous value. We already to this when parsing the "i18n.logoutputencoding" config in `git_default_i18n_config()`. But because the config is typically parsed before we parse command line options this has been fine so far.

Regardless of that, safeguard the code such that the variable always contains an allocated string. While at it, also free the old value in case there was any to plug a potential memory leak.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 revision.c             | 3 ++-
 t/t3900-i18n-commit.sh | 1 +
 t/t3901-i18n-patch.sh  | 1 +
 3 files changed, 4 insertions(+), 1 deletion(-)
Show changes to 3 files +4 −1

revision.c, t/t3900-i18n-commit.sh, t/t3901-i18n-patch.sh

diff --git a/revision.c b/revision.c
index 7ddf0f151a..2ee6886078 100644
--- a/revision.c
+++ b/revision.c
@@ -2650,10 +2650,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 	} else if (!strcmp(arg, "--invert-grep")) {
 		revs->grep_filter.no_body_match = 1;
 	} else if ((argcount = parse_long_opt("encoding", argv, &optarg))) {
+		free(git_log_output_encoding);
 		if (strcmp(optarg, "none"))
 			git_log_output_encoding = xstrdup(optarg);
 		else
-			git_log_output_encoding = "";
+			git_log_output_encoding = xstrdup("");
 		return argcount;
 	} else if (!strcmp(arg, "--reverse")) {
 		revs->reverse ^= 1;
diff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh
index f27d09cfd9..db7b403bc1 100755
--- a/t/t3900-i18n-commit.sh
+++ b/t/t3900-i18n-commit.sh
@@ -5,6 +5,7 @@
 
 test_description='commit and log output encodings'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 compare_with () {
diff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh
index 4b37f78829..5f0b9afc3f 100755
--- a/t/t3901-i18n-patch.sh
+++ b/t/t3901-i18n-patch.sh
@@ -8,6 +8,7 @@ test_description='i18n settings and format-patch | am pipe'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 check_encoding () {
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 4, 2024, 12:38 UTC · re: Patrick Steinhardt · lore

[PATCH v4 21/27] mailmap: always store allocated strings in mailmap blob

Same as with the preceding commit, the `git_mailmap_blob` may sometimes contain an allocated string and sometimes it may contain a string constant. This is risky and can easily lead to bugs in case the variable is getting re-assigned, where the code may then try to free the previous value to avoid memory leaks.

Safeguard the code by always storing allocated strings in the variable.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 mailmap.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to mailmap.c +1 −1
diff --git a/mailmap.c b/mailmap.c
index b2efe29b3d..3d1e092fef 100644
--- a/mailmap.c
+++ b/mailmap.c
@@ -216,7 +216,7 @@ int read_mailmap(struct string_list *map)
 	map->cmp = namemap_cmp;
 
 	if (!git_mailmap_blob && is_bare_repository())
-		git_mailmap_blob = "HEAD:.mailmap";
+		git_mailmap_blob = xstrdup("HEAD:.mailmap");
 
 	if (!startup_info->have_repository || !is_bare_repository())
 		err |= read_mailmap_file(map, ".mailmap",
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 4, 2024, 12:38 UTC · re: Patrick Steinhardt · lore

[PATCH v4 22/27] imap-send: drop global `imap_server_conf` variable

In "imap-send.c", we have a global `sturct imap_server_conf` variable that keeps track of the configuration of the IMAP server. This variable is being populated mostly via the Git configuration.

Refactor the code to allocate the structure on the stack instead of having it globally. This change allows us to track its lifetime more closely.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 imap-send.c | 57 ++++++++++++++++++++++++++++-------------------------
 1 file changed, 30 insertions(+), 27 deletions(-)
Show changes to imap-send.c +30 −27
diff --git a/imap-send.c b/imap-send.c
index 8b723b34a5..67a7a6c456 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -82,10 +82,6 @@ struct imap_server_conf {
 	char *auth_method;
 };
 
-static struct imap_server_conf server = {
-	.ssl_verify = 1,
-};
-
 struct imap_socket {
 	int fd[2];
 	SSL *ssl;
@@ -110,6 +106,7 @@ struct imap {
 };
 
 struct imap_store {
+	const struct imap_server_conf *cfg;
 	/* currently open mailbox */
 	const char *name; /* foreign! maybe preset? */
 	int uidvalidity;
@@ -194,8 +191,8 @@ static void socket_perror(const char *func, struct imap_socket *sock, int ret)
 
 #ifdef NO_OPENSSL
 static int ssl_socket_connect(struct imap_socket *sock UNUSED,
-			      int use_tls_only UNUSED,
-			      int verify UNUSED)
+			      const struct imap_server_conf *cfg,
+			      int use_tls_only UNUSED)
 {
 	fprintf(stderr, "SSL requested but SSL support not compiled in\n");
 	return -1;
@@ -250,7 +247,9 @@ static int verify_hostname(X509 *cert, const char *hostname)
 		     cname, hostname);
 }
 
-static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int verify)
+static int ssl_socket_connect(struct imap_socket *sock,
+			      const struct imap_server_conf *cfg,
+			      int use_tls_only)
 {
 #if (OPENSSL_VERSION_NUMBER >= 0x10000000L)
 	const SSL_METHOD *meth;
@@ -279,7 +278,7 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
 	if (use_tls_only)
 		SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
 
-	if (verify)
+	if (cfg->ssl_verify)
 		SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
 
 	if (!SSL_CTX_set_default_verify_paths(ctx)) {
@@ -306,9 +305,9 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
 	 * OpenSSL does not document this function, but the implementation
 	 * returns 1 on success, 0 on failure after calling SSLerr().
 	 */
-	ret = SSL_set_tlsext_host_name(sock->ssl, server.host);
+	ret = SSL_set_tlsext_host_name(sock->ssl, cfg->host);
 	if (ret != 1)
-		warning("SSL_set_tlsext_host_name(%s) failed.", server.host);
+		warning("SSL_set_tlsext_host_name(%s) failed.", cfg->host);
 #endif
 
 	ret = SSL_connect(sock->ssl);
@@ -317,12 +316,12 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
 		return -1;
 	}
 
-	if (verify) {
+	if (cfg->ssl_verify) {
 		/* make sure the hostname matches that of the certificate */
 		cert = SSL_get_peer_certificate(sock->ssl);
 		if (!cert)
 			return error("unable to get peer certificate.");
-		if (verify_hostname(cert, server.host) < 0)
+		if (verify_hostname(cert, cfg->host) < 0)
 			return -1;
 	}
 
@@ -895,7 +894,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)
 	int ret;
 	char *response;
 
-	response = cram(prompt, server.user, server.pass);
+	response = cram(prompt, ctx->cfg->user, ctx->cfg->pass);
 
 	ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));
 	if (ret != strlen(response))
@@ -935,6 +934,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
 
 	CALLOC_ARRAY(ctx, 1);
 
+	ctx->cfg = srvc;
 	ctx->imap = CALLOC_ARRAY(imap, 1);
 	imap->buf.sock.fd[0] = imap->buf.sock.fd[1] = -1;
 	imap->in_progress_append = &imap->in_progress;
@@ -1035,7 +1035,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
 		imap->buf.sock.fd[1] = dup(s);
 
 		if (srvc->use_ssl &&
-		    ssl_socket_connect(&imap->buf.sock, 0, srvc->ssl_verify)) {
+		    ssl_socket_connect(&imap->buf.sock, srvc, 0)) {
 			close(s);
 			goto bail;
 		}
@@ -1068,8 +1068,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
 		if (!srvc->use_ssl && CAP(STARTTLS)) {
 			if (imap_exec(ctx, NULL, "STARTTLS") != RESP_OK)
 				goto bail;
-			if (ssl_socket_connect(&imap->buf.sock, 1,
-					       srvc->ssl_verify))
+			if (ssl_socket_connect(&imap->buf.sock, srvc, 1))
 				goto bail;
 			/* capabilities may have changed, so get the new capabilities */
 			if (imap_exec(ctx, NULL, "CAPABILITY") != RESP_OK)
@@ -1299,23 +1298,24 @@ static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, int *ofs)
 static int git_imap_config(const char *var, const char *val,
 			   const struct config_context *ctx, void *cb)
 {
+	struct imap_server_conf *cfg = cb;
 
 	if (!strcmp("imap.sslverify", var))
-		server.ssl_verify = git_config_bool(var, val);
+		cfg->ssl_verify = git_config_bool(var, val);
 	else if (!strcmp("imap.preformattedhtml", var))
-		server.use_html = git_config_bool(var, val);
+		cfg->use_html = git_config_bool(var, val);
 	else if (!strcmp("imap.folder", var))
-		return git_config_string(&server.folder, var, val);
+		return git_config_string(&cfg->folder, var, val);
 	else if (!strcmp("imap.user", var))
-		return git_config_string(&server.user, var, val);
+		return git_config_string(&cfg->user, var, val);
 	else if (!strcmp("imap.pass", var))
-		return git_config_string(&server.pass, var, val);
+		return git_config_string(&cfg->pass, var, val);
 	else if (!strcmp("imap.tunnel", var))
-		return git_config_string(&server.tunnel, var, val);
+		return git_config_string(&cfg->tunnel, var, val);
 	else if (!strcmp("imap.authmethod", var))
-		return git_config_string(&server.auth_method, var, val);
+		return git_config_string(&cfg->auth_method, var, val);
 	else if (!strcmp("imap.port", var))
-		server.port = git_config_int(var, val, ctx->kvi);
+		cfg->port = git_config_int(var, val, ctx->kvi);
 	else if (!strcmp("imap.host", var)) {
 		if (!val) {
 			return config_error_nonbool(var);
@@ -1324,11 +1324,11 @@ static int git_imap_config(const char *var, const char *val,
 				val += 5;
 			else if (starts_with(val, "imaps:")) {
 				val += 6;
-				server.use_ssl = 1;
+				cfg->use_ssl = 1;
 			}
 			if (starts_with(val, "//"))
 				val += 2;
-			server.host = xstrdup(val);
+			cfg->host = xstrdup(val);
 		}
 	} else
 		return git_default_config(var, val, ctx, cb);
@@ -1497,12 +1497,15 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,
 
 int cmd_main(int argc, const char **argv)
 {
+	struct imap_server_conf server = {
+		.ssl_verify = 1,
+	};
 	struct strbuf all_msgs = STRBUF_INIT;
 	int total;
 	int nongit_ok;
 
 	setup_git_directory_gently(&nongit_ok);
-	git_config(git_imap_config, NULL);
+	git_config(git_imap_config, &server);
 
 	argc = parse_options(argc, (const char **)argv, "", imap_send_options, imap_send_usage, 0);
 
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 4, 2024, 12:38 UTC · re: Patrick Steinhardt · lore

[PATCH v4 23/27] imap-send: fix leaking memory in `imap_server_conf`

We never free any of the config strings that we populate into the `struct imap_server_conf`. Fix this by creating a common exit path where we can free resources.

While at it, drop the unused member `imap_server_conf::name`.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 imap-send.c | 65 ++++++++++++++++++++++++++++++++++-------------------
 1 file changed, 42 insertions(+), 23 deletions(-)
Show changes to imap-send.c +42 −23
diff --git a/imap-send.c b/imap-send.c
index 67a7a6c456..da3e7ec17e 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -69,7 +69,6 @@ static void imap_warn(const char *, ...);
 static char *next_arg(char **);
 
 struct imap_server_conf {
-	const char *name;
 	char *tunnel;
 	char *host;
 	int port;
@@ -1300,23 +1299,28 @@ static int git_imap_config(const char *var, const char *val,
 {
 	struct imap_server_conf *cfg = cb;
 
-	if (!strcmp("imap.sslverify", var))
+	if (!strcmp("imap.sslverify", var)) {
 		cfg->ssl_verify = git_config_bool(var, val);
-	else if (!strcmp("imap.preformattedhtml", var))
+	} else if (!strcmp("imap.preformattedhtml", var)) {
 		cfg->use_html = git_config_bool(var, val);
-	else if (!strcmp("imap.folder", var))
+	} else if (!strcmp("imap.folder", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->folder, var, val);
-	else if (!strcmp("imap.user", var))
+	} else if (!strcmp("imap.user", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->user, var, val);
-	else if (!strcmp("imap.pass", var))
+	} else if (!strcmp("imap.pass", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->pass, var, val);
-	else if (!strcmp("imap.tunnel", var))
+	} else if (!strcmp("imap.tunnel", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->tunnel, var, val);
-	else if (!strcmp("imap.authmethod", var))
+	} else if (!strcmp("imap.authmethod", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->auth_method, var, val);
-	else if (!strcmp("imap.port", var))
+	} else if (!strcmp("imap.port", var)) {
 		cfg->port = git_config_int(var, val, ctx->kvi);
-	else if (!strcmp("imap.host", var)) {
+	} else if (!strcmp("imap.host", var)) {
 		if (!val) {
 			return config_error_nonbool(var);
 		} else {
@@ -1330,8 +1334,9 @@ static int git_imap_config(const char *var, const char *val,
 				val += 2;
 			cfg->host = xstrdup(val);
 		}
-	} else
+	} else {
 		return git_default_config(var, val, ctx, cb);
+	}
 
 	return 0;
 }
@@ -1503,6 +1508,7 @@ int cmd_main(int argc, const char **argv)
 	struct strbuf all_msgs = STRBUF_INIT;
 	int total;
 	int nongit_ok;
+	int ret;
 
 	setup_git_directory_gently(&nongit_ok);
 	git_config(git_imap_config, &server);
@@ -1529,42 +1535,55 @@ int cmd_main(int argc, const char **argv)
 
 	if (!server.folder) {
 		fprintf(stderr, "no imap store specified\n");
-		return 1;
+		ret = 1;
+		goto out;
 	}
 	if (!server.host) {
 		if (!server.tunnel) {
 			fprintf(stderr, "no imap host specified\n");
-			return 1;
+			ret = 1;
+			goto out;
 		}
-		server.host = "tunnel";
+		server.host = xstrdup("tunnel");
 	}
 
 	/* read the messages */
 	if (strbuf_read(&all_msgs, 0, 0) < 0) {
 		error_errno(_("could not read from stdin"));
-		return 1;
+		ret = 1;
+		goto out;
 	}
 
 	if (all_msgs.len == 0) {
 		fprintf(stderr, "nothing to send\n");
-		return 1;
+		ret = 1;
+		goto out;
 	}
 
 	total = count_messages(&all_msgs);
 	if (!total) {
 		fprintf(stderr, "no messages to send\n");
-		return 1;
+		ret = 1;
+		goto out;
 	}
 
 	/* write it to the imap server */
 
 	if (server.tunnel)
-		return append_msgs_to_imap(&server, &all_msgs, total);
-
+		ret = append_msgs_to_imap(&server, &all_msgs, total);
 #ifdef USE_CURL_FOR_IMAP_SEND
-	if (use_curl)
-		return curl_append_msgs_to_imap(&server, &all_msgs, total);
+	else if (use_curl)
+		ret = curl_append_msgs_to_imap(&server, &all_msgs, total);
 #endif
-
-	return append_msgs_to_imap(&server, &all_msgs, total);
+	else
+		ret = append_msgs_to_imap(&server, &all_msgs, total);
+
+out:
+	free(server.tunnel);
+	free(server.host);
+	free(server.folder);
+	free(server.user);
+	free(server.pass);
+	free(server.auth_method);
+	return ret;
 }
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 4, 2024, 12:38 UTC · re: Patrick Steinhardt · lore

[PATCH v4 24/27] builtin/rebase: do not assign default backend to non-constant field

The `struct rebase_options::default_backend` field is a non-constant string, but is being assigned a constant via `REBASE_OPTIONS_INIT`. Refactor the code to initialize and release options via two functions `rebase_options_init()` and `rebase_options_release()`. Like this, we can easily adapt the former funnction to use `xstrdup()` on the default value without hiding it away in a macro.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rebase.c | 67 ++++++++++++++++++++++++++++--------------------
 1 file changed, 39 insertions(+), 28 deletions(-)
Show changes to builtin/rebase.c +39 −28
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 14d4f0a5e6..11f276012c 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -131,25 +131,40 @@ struct rebase_options {
 	int config_update_refs;
 };
 
-#define REBASE_OPTIONS_INIT {			  	\
-		.type = REBASE_UNSPECIFIED,	  	\
-		.empty = EMPTY_UNSPECIFIED,	  	\
-		.keep_empty = 1,			\
-		.default_backend = "merge",	  	\
-		.flags = REBASE_NO_QUIET, 		\
-		.git_am_opts = STRVEC_INIT,		\
-		.exec = STRING_LIST_INIT_NODUP,		\
-		.git_format_patch_opt = STRBUF_INIT,	\
-		.fork_point = -1,			\
-		.reapply_cherry_picks = -1,             \
-		.allow_empty_message = 1,               \
-		.autosquash = -1,                       \
-		.rebase_merges = -1,                    \
-		.config_rebase_merges = -1,             \
-		.update_refs = -1,                      \
-		.config_update_refs = -1,               \
-		.strategy_opts = STRING_LIST_INIT_NODUP,\
-	}
+static void rebase_options_init(struct rebase_options *opts)
+{
+	memset(opts, 0, sizeof(*opts));
+	opts->type = REBASE_UNSPECIFIED;
+	opts->empty = EMPTY_UNSPECIFIED;
+	opts->default_backend = xstrdup("merge");
+	opts->keep_empty = 1;
+	opts->flags = REBASE_NO_QUIET;
+	strvec_init(&opts->git_am_opts);
+	string_list_init_nodup(&opts->exec);
+	strbuf_init(&opts->git_format_patch_opt, 0);
+	opts->fork_point = -1;
+	opts->reapply_cherry_picks = -1;
+	opts->allow_empty_message = 1;
+	opts->autosquash = -1;
+	opts->rebase_merges = -1;
+	opts->config_rebase_merges = -1;
+	opts->update_refs = -1;
+	opts->config_update_refs = -1;
+	string_list_init_nodup(&opts->strategy_opts);
+}
+
+static void rebase_options_release(struct rebase_options *opts)
+{
+	free(opts->default_backend);
+	free(opts->reflog_action);
+	free(opts->head_name);
+	strvec_clear(&opts->git_am_opts);
+	free(opts->gpg_sign_opt);
+	string_list_clear(&opts->exec, 0);
+	free(opts->strategy);
+	string_list_clear(&opts->strategy_opts, 0);
+	strbuf_release(&opts->git_format_patch_opt);
+}
 
 static struct replay_opts get_replay_opts(const struct rebase_options *opts)
 {
@@ -796,6 +811,7 @@ static int rebase_config(const char *var, const char *value,
 	}
 
 	if (!strcmp(var, "rebase.backend")) {
+		FREE_AND_NULL(opts->default_backend);
 		return git_config_string(&opts->default_backend, var, value);
 	}
 
@@ -1045,7 +1061,7 @@ static int check_exec_cmd(const char *cmd)
 
 int cmd_rebase(int argc, const char **argv, const char *prefix)
 {
-	struct rebase_options options = REBASE_OPTIONS_INIT;
+	struct rebase_options options;
 	const char *branch_name;
 	int ret, flags, total_argc, in_progress = 0;
 	int keep_base = 0;
@@ -1178,6 +1194,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	};
 	int i;
 
+	rebase_options_init(&options);
+
 	if (argc == 2 && !strcmp(argv[1], "-h"))
 		usage_with_options(builtin_rebase_usage,
 				   builtin_rebase_options);
@@ -1833,14 +1851,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 cleanup:
 	strbuf_release(&buf);
 	strbuf_release(&revisions);
-	free(options.reflog_action);
-	free(options.head_name);
-	strvec_clear(&options.git_am_opts);
-	free(options.gpg_sign_opt);
-	string_list_clear(&options.exec, 0);
-	free(options.strategy);
-	string_list_clear(&options.strategy_opts, 0);
-	strbuf_release(&options.git_format_patch_opt);
+	rebase_options_release(&options);
 	free(squash_onto_name);
 	free(keep_base_onto_name);
 	return !!ret;
-- 
2.45.1.410.g58bac47f8e.dirty
Phillip Wood· Jun 4, 2024, 14:06 UTC · re: Patrick Steinhardt · lore

Re: [PATCH v4 24/27] builtin/rebase: do not assign default backend to non-constant field

Hi Patrick
On 04/06/2024 13:38, Patrick Steinhardt wrote:
Show 6 quoted lines
> The `struct rebase_options::default_backend` field is a non-constant
> string, but is being assigned a constant via `REBASE_OPTIONS_INIT`.
> Refactor the code to initialize and release options via two functions
> `rebase_options_init()` and `rebase_options_release()`. Like this, we
> can easily adapt the former funnction to use `xstrdup()` on the default
> value without hiding it away in a macro.
Personally I'd be happy with

- .default_backend = "merge", \ + .default_backend = xstrdup("merge"), \

rather than adding an init function. I do agree that adding rebase_options_release() is a good idea and the rest of the changes look good to me

Thanks
Phillip
Show 111 quoted lines
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>   builtin/rebase.c | 67 ++++++++++++++++++++++++++++--------------------
>   1 file changed, 39 insertions(+), 28 deletions(-)
> 
> diff --git a/builtin/rebase.c b/builtin/rebase.c
> index 14d4f0a5e6..11f276012c 100644
> --- a/builtin/rebase.c
> +++ b/builtin/rebase.c
> @@ -131,25 +131,40 @@ struct rebase_options {
>   	int config_update_refs;
>   };
>   
> -#define REBASE_OPTIONS_INIT {			  	\
> -		.type = REBASE_UNSPECIFIED,	  	\
> -		.empty = EMPTY_UNSPECIFIED,	  	\
> -		.keep_empty = 1,			\
> -		.default_backend = "merge",	  	\
> -		.flags = REBASE_NO_QUIET, 		\
> -		.git_am_opts = STRVEC_INIT,		\
> -		.exec = STRING_LIST_INIT_NODUP,		\
> -		.git_format_patch_opt = STRBUF_INIT,	\
> -		.fork_point = -1,			\
> -		.reapply_cherry_picks = -1,             \
> -		.allow_empty_message = 1,               \
> -		.autosquash = -1,                       \
> -		.rebase_merges = -1,                    \
> -		.config_rebase_merges = -1,             \
> -		.update_refs = -1,                      \
> -		.config_update_refs = -1,               \
> -		.strategy_opts = STRING_LIST_INIT_NODUP,\
> -	}
> +static void rebase_options_init(struct rebase_options *opts)
> +{
> +	memset(opts, 0, sizeof(*opts));
> +	opts->type = REBASE_UNSPECIFIED;
> +	opts->empty = EMPTY_UNSPECIFIED;
> +	opts->default_backend = xstrdup("merge");
> +	opts->keep_empty = 1;
> +	opts->flags = REBASE_NO_QUIET;
> +	strvec_init(&opts->git_am_opts);
> +	string_list_init_nodup(&opts->exec);
> +	strbuf_init(&opts->git_format_patch_opt, 0);
> +	opts->fork_point = -1;
> +	opts->reapply_cherry_picks = -1;
> +	opts->allow_empty_message = 1;
> +	opts->autosquash = -1;
> +	opts->rebase_merges = -1;
> +	opts->config_rebase_merges = -1;
> +	opts->update_refs = -1;
> +	opts->config_update_refs = -1;
> +	string_list_init_nodup(&opts->strategy_opts);
> +}
> +
> +static void rebase_options_release(struct rebase_options *opts)
> +{
> +	free(opts->default_backend);
> +	free(opts->reflog_action);
> +	free(opts->head_name);
> +	strvec_clear(&opts->git_am_opts);
> +	free(opts->gpg_sign_opt);
> +	string_list_clear(&opts->exec, 0);
> +	free(opts->strategy);
> +	string_list_clear(&opts->strategy_opts, 0);
> +	strbuf_release(&opts->git_format_patch_opt);
> +}
>   
>   static struct replay_opts get_replay_opts(const struct rebase_options *opts)
>   {
> @@ -796,6 +811,7 @@ static int rebase_config(const char *var, const char *value,
>   	}
>   
>   	if (!strcmp(var, "rebase.backend")) {
> +		FREE_AND_NULL(opts->default_backend);
>   		return git_config_string(&opts->default_backend, var, value);
>   	}
>   
> @@ -1045,7 +1061,7 @@ static int check_exec_cmd(const char *cmd)
>   
>   int cmd_rebase(int argc, const char **argv, const char *prefix)
>   {
> -	struct rebase_options options = REBASE_OPTIONS_INIT;
> +	struct rebase_options options;
>   	const char *branch_name;
>   	int ret, flags, total_argc, in_progress = 0;
>   	int keep_base = 0;
> @@ -1178,6 +1194,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>   	};
>   	int i;
>   
> +	rebase_options_init(&options);
> +
>   	if (argc == 2 && !strcmp(argv[1], "-h"))
>   		usage_with_options(builtin_rebase_usage,
>   				   builtin_rebase_options);
> @@ -1833,14 +1851,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>   cleanup:
>   	strbuf_release(&buf);
>   	strbuf_release(&revisions);
> -	free(options.reflog_action);
> -	free(options.head_name);
> -	strvec_clear(&options.git_am_opts);
> -	free(options.gpg_sign_opt);
> -	string_list_clear(&options.exec, 0);
> -	free(options.strategy);
> -	string_list_clear(&options.strategy_opts, 0);
> -	strbuf_release(&options.git_format_patch_opt);
> +	rebase_options_release(&options);
>   	free(squash_onto_name);
>   	free(keep_base_onto_name);
>   	return !!ret;
Patrick Steinhardt· Jun 5, 2024, 05:40 UTC · re: Phillip Wood · lore

Re: [PATCH v4 24/27] builtin/rebase: do not assign default backend to non-constant field

On Tue, Jun 04, 2024 at 03:06:38PM +0100, Phillip Wood wrote:
Show 18 quoted lines
> Hi Patrick
> 
> On 04/06/2024 13:38, Patrick Steinhardt wrote:
> > The `struct rebase_options::default_backend` field is a non-constant
> > string, but is being assigned a constant via `REBASE_OPTIONS_INIT`.
> > Refactor the code to initialize and release options via two functions
> > `rebase_options_init()` and `rebase_options_release()`. Like this, we
> > can easily adapt the former funnction to use `xstrdup()` on the default
> > value without hiding it away in a macro.
> 
> Personally I'd be happy with
> 
> -		.default_backend = "merge",		\
> +		.default_backend = xstrdup("merge"),	\
> 
> rather than adding an init function. I do agree that adding
> rebase_options_release() is a good idea and the rest of the changes look
> good to me

Do we have any other cases where we allocate inside of a `_INIT` style macro? If so, I'd go with that precedence and just allocate inside of the macro. But if we don't, then I think I'm leaning more towards the way I did it in this patch.

Happy to be convinced otherwise, I don't really feel all that strongly about this. I'm merely aiming for the interface wth least surprises.

Patrick
Phillip Wood· Jun 5, 2024, 13:06 UTC · re: Patrick Steinhardt · lore

Re: [PATCH v4 24/27] builtin/rebase: do not assign default backend to non-constant field

On 05/06/2024 06:40, Patrick Steinhardt wrote:
> On Tue, Jun 04, 2024 at 03:06:38PM +0100, Phillip Wood wrote:
> Do we have any other cases where we allocate inside of a `_INIT` style
> macro? If so, I'd go with that precedence and just allocate inside of
> the macro. But if we don't, then I think I'm leaning more towards the
> way I did it in this patch.

I recently added an allocation to REPLAY_OPTS_INIT, I'm not sure if there are any others. In general code that does

	struct foo = FOO_INIT;
and does not call
	foo_release();

is asking for trouble so I don't feel that allocations in _INIT macros are generally a problem. The only reason not to have allocations in an _INIT macro is if it might be used to initialize a file scope or global variable but that's not the case here.

> Happy to be convinced otherwise, I don't really feel all that strongly
> about this. I'm merely aiming for the interface wth least surprises.

I'm not that fussed either, but I do prefer our _INIT macros over _init() functions as I think they're nicer to use and easier to write (no need to worry about memset() to zero out the struct).

Best Wishes
Phillip
Patrick Steinhardt· Jun 6, 2024, 09:50 UTC · re: Phillip Wood · lore

Re: [PATCH v4 24/27] builtin/rebase: do not assign default backend to non-constant field

On Wed, Jun 05, 2024 at 02:06:55PM +0100, Phillip Wood wrote:
Show 28 quoted lines
> On 05/06/2024 06:40, Patrick Steinhardt wrote:
> > On Tue, Jun 04, 2024 at 03:06:38PM +0100, Phillip Wood wrote:
> 
> > Do we have any other cases where we allocate inside of a `_INIT` style
> > macro? If so, I'd go with that precedence and just allocate inside of
> > the macro. But if we don't, then I think I'm leaning more towards the
> > way I did it in this patch.
> 
> I recently added an allocation to REPLAY_OPTS_INIT, I'm not sure if there
> are any others. In general code that does
> 
> 	struct foo = FOO_INIT;
> 
> and does not call
> 
> 	foo_release();
> 
> is asking for trouble so I don't feel that allocations in _INIT macros are
> generally a problem. The only reason not to have allocations in an _INIT
> macro is if it might be used to initialize a file scope or global variable
> but that's not the case here.
> 
> > Happy to be convinced otherwise, I don't really feel all that strongly
> > about this. I'm merely aiming for the interface wth least surprises.
> 
> I'm not that fussed either, but I do prefer our _INIT macros over _init()
> functions as I think they're nicer to use and easier to write (no need to
> worry about memset() to zero out the struct).

Okay, given that there is precedent, and given that Junio also seems to slightly lean into the direction of the `_INIT` macro, I'll adapt this as proposed by you.

Patrick
Junio C Hamano· Jun 5, 2024, 16:11 UTC · re: Patrick Steinhardt · lore

Re: [PATCH v4 24/27] builtin/rebase: do not assign default backend to non-constant field

Patrick Steinhardt <ps@pks.im> writes:
Show 16 quoted lines
>> Personally I'd be happy with
>> 
>> -		.default_backend = "merge",		\
>> +		.default_backend = xstrdup("merge"),	\
>> 
>> rather than adding an init function. I do agree that adding
>> rebase_options_release() is a good idea and the rest of the changes look
>> good to me
>
> Do we have any other cases where we allocate inside of a `_INIT` style
> macro? If so, I'd go with that precedence and just allocate inside of
> the macro. But if we don't, then I think I'm leaning more towards the
> way I did it in this patch.
>
> Happy to be convinced otherwise, I don't really feel all that strongly
> about this. I'm merely aiming for the interface wth least surprises.
FWIW, I am OK either way.

Having _init() and _release() may look symmetrical and there _might_ be cases where an initialization with a simple _INIT macro may not suffice and we must have _init() function.

But otherwise, especially with .designated_initializers, the _INIT macro makes it slightly easier to read (but not all much for this particular case, whose initial values have too many non-zero values and pretty much everything must be spelled out).

Thanks.
Patrick Steinhardt· Jun 4, 2024, 12:38 UTC · re: Patrick Steinhardt · lore

[PATCH v4 25/27] builtin/rebase: always store allocated string in `options.strategy`

The `struct rebase_options::strategy` field is a `char *`, but we do end up assigning string constants to it in two cases:

  - When being passed a `--strategy=` option via the command line.
  - When being passed a strategy option via `--strategy-option=`, but
    not a strategy.
This will cause warnings once we enable `-Wwrite-strings`.

Ideally, we'd just convert the field to be a `const char *`. But we also assign to this field via the GIT_TEST_MERGE_ALGORITHM envvar, which we have to strdup(3P) into it.

Instead, refactor the code to make sure that we only ever assign allocated strings to this field.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rebase.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)
Show changes to builtin/rebase.c +7 −7
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 11f276012c..26068cf542 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1063,6 +1063,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 {
 	struct rebase_options options;
 	const char *branch_name;
+	const char *strategy_opt = NULL;
 	int ret, flags, total_argc, in_progress = 0;
 	int keep_base = 0;
 	int ok_to_skip_pre_rebase = 0;
@@ -1177,7 +1178,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			PARSE_OPT_OPTARG, parse_opt_rebase_merges),
 		OPT_BOOL(0, "fork-point", &options.fork_point,
 			 N_("use 'merge-base --fork-point' to refine upstream")),
-		OPT_STRING('s', "strategy", &options.strategy,
+		OPT_STRING('s', "strategy", &strategy_opt,
 			   N_("strategy"), N_("use the given merge strategy")),
 		OPT_STRING_LIST('X', "strategy-option", &options.strategy_opts,
 				N_("option"),
@@ -1488,13 +1489,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		}
 	}
 
-	if (options.strategy_opts.nr && !options.strategy)
-		options.strategy = "ort";
-
-	if (options.strategy) {
-		options.strategy = xstrdup(options.strategy);
+	if (strategy_opt)
+		options.strategy = xstrdup(strategy_opt);
+	else if (options.strategy_opts.nr && !options.strategy)
+		options.strategy = xstrdup("ort");
+	if (options.strategy)
 		imply_merge(&options, "--strategy");
-	}
 
 	if (options.root && !options.onto_name)
 		imply_merge(&options, "--root without --onto");
-- 
2.45.1.410.g58bac47f8e.dirty
Phillip Wood· Jun 4, 2024, 14:10 UTC · re: Patrick Steinhardt · lore

Re: [PATCH v4 25/27] builtin/rebase: always store allocated string in `options.strategy`

Hi Patrick
On 04/06/2024 13:38, Patrick Steinhardt wrote:
Show 16 quoted lines
> The `struct rebase_options::strategy` field is a `char *`, but we do end
> up assigning string constants to it in two cases:
> 
>    - When being passed a `--strategy=` option via the command line.
> 
>    - When being passed a strategy option via `--strategy-option=`, but
>      not a strategy.
> 
> This will cause warnings once we enable `-Wwrite-strings`.
> 
> Ideally, we'd just convert the field to be a `const char *`. But we also
> assign to this field via the GIT_TEST_MERGE_ALGORITHM envvar, which we
> have to strdup(3P) into it.
> 
> Instead, refactor the code to make sure that we only ever assign
> allocated strings to this field.
This looks sensible
Thanks
Phillip
Show 45 quoted lines
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>   builtin/rebase.c | 14 +++++++-------
>   1 file changed, 7 insertions(+), 7 deletions(-)
> 
> diff --git a/builtin/rebase.c b/builtin/rebase.c
> index 11f276012c..26068cf542 100644
> --- a/builtin/rebase.c
> +++ b/builtin/rebase.c
> @@ -1063,6 +1063,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>   {
>   	struct rebase_options options;
>   	const char *branch_name;
> +	const char *strategy_opt = NULL;
>   	int ret, flags, total_argc, in_progress = 0;
>   	int keep_base = 0;
>   	int ok_to_skip_pre_rebase = 0;
> @@ -1177,7 +1178,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>   			PARSE_OPT_OPTARG, parse_opt_rebase_merges),
>   		OPT_BOOL(0, "fork-point", &options.fork_point,
>   			 N_("use 'merge-base --fork-point' to refine upstream")),
> -		OPT_STRING('s', "strategy", &options.strategy,
> +		OPT_STRING('s', "strategy", &strategy_opt,
>   			   N_("strategy"), N_("use the given merge strategy")),
>   		OPT_STRING_LIST('X', "strategy-option", &options.strategy_opts,
>   				N_("option"),
> @@ -1488,13 +1489,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>   		}
>   	}
>   
> -	if (options.strategy_opts.nr && !options.strategy)
> -		options.strategy = "ort";
> -
> -	if (options.strategy) {
> -		options.strategy = xstrdup(options.strategy);
> +	if (strategy_opt)
> +		options.strategy = xstrdup(strategy_opt);
> +	else if (options.strategy_opts.nr && !options.strategy)
> +		options.strategy = xstrdup("ort");
> +	if (options.strategy)
>   		imply_merge(&options, "--strategy");
> -	}
>   
>   	if (options.root && !options.onto_name)
>   		imply_merge(&options, "--root without --onto");
Patrick Steinhardt· Jun 4, 2024, 12:38 UTC · re: Patrick Steinhardt · lore

[PATCH v4 26/27] builtin/merge: always store allocated strings in `pull_twohead`

The `pull_twohead` configuration may sometimes contain an allocated string, and sometimes it may contain a string constant. Refactor this to instead always store an allocated string such that we can release its resources without risk.

While at it, manage the lifetime of other config strings, as well. Note that we explicitly don't free `cleanup_arg` here. This is because the variable may be assigned a string constant via command line options.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/merge.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)
Show changes to builtin/merge.c +11 −7
diff --git a/builtin/merge.c b/builtin/merge.c
index daed2d4e1e..fb3eb15b89 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -611,17 +611,19 @@ static int git_merge_config(const char *k, const char *v,
 		return 0;
 	}
 
-	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
+	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat")) {
 		show_diffstat = git_config_bool(k, v);
-	else if (!strcmp(k, "merge.verifysignatures"))
+	} else if (!strcmp(k, "merge.verifysignatures")) {
 		verify_signatures = git_config_bool(k, v);
-	else if (!strcmp(k, "pull.twohead"))
+	} else if (!strcmp(k, "pull.twohead")) {
+		FREE_AND_NULL(pull_twohead);
 		return git_config_string(&pull_twohead, k, v);
-	else if (!strcmp(k, "pull.octopus"))
+	} else if (!strcmp(k, "pull.octopus")) {
+		FREE_AND_NULL(pull_octopus);
 		return git_config_string(&pull_octopus, k, v);
-	else if (!strcmp(k, "commit.cleanup"))
+	} else if (!strcmp(k, "commit.cleanup")) {
 		return git_config_string(&cleanup_arg, k, v);
-	else if (!strcmp(k, "merge.ff")) {
+	} else if (!strcmp(k, "merge.ff")) {
 		int boolval = git_parse_maybe_bool(v);
 		if (0 <= boolval) {
 			fast_forward = boolval ? FF_ALLOW : FF_NO;
@@ -1294,7 +1296,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 	if (!pull_twohead) {
 		char *default_strategy = getenv("GIT_TEST_MERGE_ALGORITHM");
 		if (default_strategy && !strcmp(default_strategy, "ort"))
-			pull_twohead = "ort";
+			pull_twohead = xstrdup("ort");
 	}
 
 	init_diff_ui_defaults();
@@ -1793,6 +1795,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 	}
 	strbuf_release(&buf);
 	free(branch_to_free);
+	free(pull_twohead);
+	free(pull_octopus);
 	discard_index(the_repository->index);
 	return ret;
 }
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 4, 2024, 12:39 UTC · re: Patrick Steinhardt · lore

[PATCH v4 27/27] config.mak.dev: enable `-Wwrite-strings` warning

Writing to string constants is undefined behaviour and must be avoided in C. Even so, the compiler does not help us with this by default because those constants are not in fact marked as `const`. This makes it rather easy to accidentally assign a constant to a non-const variable or field and then later on try to either free it or write to it.

Enable `-Wwrite-strings` to catch such mistakes. With this warning enabled, the type of string constants is changed to `const char[]` and will thus cause compiler warnings when being assigned to non-const fields and variables.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 config.mak.dev | 1 +
 1 file changed, 1 insertion(+)
Show changes to config.mak.dev +1 −0
diff --git a/config.mak.dev b/config.mak.dev
index 981304727c..1ce4c70613 100644
--- a/config.mak.dev
+++ b/config.mak.dev
@@ -37,6 +37,7 @@ DEVELOPER_CFLAGS += -Wpointer-arith
 DEVELOPER_CFLAGS += -Wstrict-prototypes
 DEVELOPER_CFLAGS += -Wunused
 DEVELOPER_CFLAGS += -Wvla
+DEVELOPER_CFLAGS += -Wwrite-strings
 DEVELOPER_CFLAGS += -fno-common
 
 ifneq ($(filter clang4,$(COMPILER_FEATURES)),)
-- 
2.45.1.410.g58bac47f8e.dirty
Patrick Steinhardt· Jun 6, 2024, 10:27 UTC · re: Patrick Steinhardt · lore

[PATCH v5 00/27] Compile with `-Wwrite-strings`

Hi,

this is another version of my patch series that prepares our code base to compile with `-Wwrite-strings`. The effect of that warning is to turn string constants from type `char []` to `const char []` so that we can detect more readily when something may accidentally try to write to or free a constant.

Changes compared to v5:
  - Plug a memory leak in `pretend_object_file()`.
  - Drop `rebase_options_init()` in favor of `REBASE_OPTIONS_INIT`.
Thanks!
Patrick
Patrick Steinhardt (27):
  global: improve const correctness when assigning string constants
  global: convert intentionally-leaking config strings to consts
  refs/reftable: stop micro-optimizing refname allocations on copy
  reftable: cast away constness when assigning constants to records
  refspec: remove global tag refspec structure
  builtin/remote: cast away constness in `get_head_names()`
  diff: cast string constant in `fill_textconv()`
  line-log: stop assigning string constant to file parent buffer
  line-log: always allocate the output prefix
  entry: refactor how we remove items for delayed checkouts
  ident: add casts for fallback name and GECOS
  object-file: mark cached object buffers as const
  object-file: make `buf` parameter of `index_mem()` a constant
  pretty: add casts for decoration option pointers
  compat/win32: fix const-correctness with string constants
  http: do not assign string constant to non-const field
  parse-options: cast long name for OPTION_ALIAS
  send-pack: always allocate receive status
  remote-curl: avoid assigning string constant to non-const variable
  revision: always store allocated strings in output encoding
  mailmap: always store allocated strings in mailmap blob
  imap-send: drop global `imap_server_conf` variable
  imap-send: fix leaking memory in `imap_server_conf`
  builtin/rebase: do not assign default backend to non-constant field
  builtin/rebase: always store allocated string in `options.strategy`
  builtin/merge: always store allocated strings in `pull_twohead`
  config.mak.dev: enable `-Wwrite-strings` warning
 builtin/bisect.c             |   3 +-
 builtin/blame.c              |   2 +-
 builtin/bugreport.c          |   2 +-
 builtin/check-ignore.c       |   4 +-
 builtin/clone.c              |  14 ++--
 builtin/commit.c             |   6 +-
 builtin/diagnose.c           |   2 +-
 builtin/fetch.c              |  11 ++-
 builtin/log.c                |   2 +-
 builtin/mailsplit.c          |   4 +-
 builtin/merge.c              |  18 +++--
 builtin/pull.c               |  52 +++++++-------
 builtin/rebase.c             |  39 ++++++-----
 builtin/receive-pack.c       |   4 +-
 builtin/remote.c             |  12 ++--
 builtin/revert.c             |   2 +-
 builtin/send-pack.c          |   2 +
 compat/basename.c            |  16 ++++-
 compat/mingw.c               |  28 ++++----
 compat/regex/regcomp.c       |   2 +-
 compat/winansi.c             |   2 +-
 config.mak.dev               |   1 +
 diff.c                       |   6 +-
 diffcore-rename.c            |   6 +-
 entry.c                      |  14 ++--
 fmt-merge-msg.c              |   2 +-
 fsck.c                       |   2 +-
 fsck.h                       |   2 +-
 gpg-interface.c              |   6 +-
 http-backend.c               |   2 +-
 http.c                       |   5 +-
 ident.c                      |   4 +-
 imap-send.c                  | 130 ++++++++++++++++++++---------------
 line-log.c                   |  22 +++---
 mailmap.c                    |   2 +-
 merge-ll.c                   |  11 ++-
 object-file.c                |  27 +++++---
 parse-options.h              |   2 +-
 pretty.c                     |   6 +-
 refs.c                       |   2 +-
 refs.h                       |   2 +-
 refs/reftable-backend.c      |  28 ++++----
 refspec.c                    |  13 ----
 refspec.h                    |   1 -
 reftable/basics.c            |  15 ++--
 reftable/basics.h            |   4 +-
 reftable/basics_test.c       |   4 +-
 reftable/block_test.c        |   2 +-
 reftable/merged_test.c       |  44 ++++++------
 reftable/readwrite_test.c    |  32 ++++-----
 reftable/record.c            |   6 +-
 reftable/stack.c             |  10 +--
 reftable/stack_test.c        |  56 +++++++--------
 remote-curl.c                |  53 +++++++-------
 revision.c                   |   3 +-
 run-command.c                |   2 +-
 send-pack.c                  |   2 +-
 t/helper/test-hashmap.c      |   3 +-
 t/helper/test-json-writer.c  |  10 +--
 t/helper/test-regex.c        |   4 +-
 t/helper/test-rot13-filter.c |   5 +-
 t/t3900-i18n-commit.sh       |   1 +
 t/t3901-i18n-patch.sh        |   1 +
 t/unit-tests/t-strbuf.c      |  10 +--
 trailer.c                    |   2 +-
 userdiff.c                   |  10 +--
 userdiff.h                   |  12 ++--
 wt-status.c                  |   2 +-
 68 files changed, 448 insertions(+), 368 deletions(-)
Range-diff against v4:
 1:  e01fde88fe =  1:  e01fde88fe global: improve const correctness when assigning string constants
 2:  92cb0b28c6 =  2:  92cb0b28c6 global: convert intentionally-leaking config strings to consts
 3:  379145478c =  3:  379145478c refs/reftable: stop micro-optimizing refname allocations on copy
 4:  d0a2a2f6c5 =  4:  d0a2a2f6c5 reftable: cast away constness when assigning constants to records
 5:  ead27d3d97 =  5:  ead27d3d97 refspec: remove global tag refspec structure
 6:  7cb5df9182 =  6:  7cb5df9182 builtin/remote: cast away constness in `get_head_names()`
 7:  6e631a9ea4 =  7:  6e631a9ea4 diff: cast string constant in `fill_textconv()`
 8:  ac164651a3 =  8:  ac164651a3 line-log: stop assigning string constant to file parent buffer
 9:  b717af02f0 =  9:  b717af02f0 line-log: always allocate the output prefix
10:  b46dd3210d = 10:  b46dd3210d entry: refactor how we remove items for delayed checkouts
11:  030dbd0288 = 11:  030dbd0288 ident: add casts for fallback name and GECOS
12:  ecca8e973d ! 12:  5cd014c22c object-file: mark cached object buffers as const
    @@ object-file.c: int pretend_object_file(void *buf, unsigned long len, enum object
      
      	hash_object_file(the_hash_algo, buf, len, type, oid);
      	if (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||
    -@@ object-file.c: int pretend_object_file(void *buf, unsigned long len, enum object_type type,
    +-	    find_cached_object(oid))
    ++	    find_cached_object(oid)) {
    ++		free(co_buf);
    + 		return 0;
    ++	}
    + 	ALLOC_GROW(cached_objects, cached_object_nr + 1, cached_object_alloc);
      	co = &cached_objects[cached_object_nr++];
      	co->size = len;
      	co->type = type;
13:  62f0e47f94 = 13:  69d904ddce object-file: make `buf` parameter of `index_mem()` a constant
14:  e057ead2b4 = 14:  ed8f07aa59 pretty: add casts for decoration option pointers
15:  06b6120d26 = 15:  5953ae1dac compat/win32: fix const-correctness with string constants
16:  a8ef39d73d = 16:  c80f6eff8c http: do not assign string constant to non-const field
17:  9d596a07c5 = 17:  3afd012a88 parse-options: cast long name for OPTION_ALIAS
18:  4019b532f9 = 18:  527755b648 send-pack: always allocate receive status
19:  f2f1ada143 = 19:  4598592d2f remote-curl: avoid assigning string constant to non-const variable
20:  27660b908c = 20:  38fcea2845 revision: always store allocated strings in output encoding
21:  ef43c1b18f = 21:  f990bbeb85 mailmap: always store allocated strings in mailmap blob
22:  0a69ce4b44 = 22:  fff2379832 imap-send: drop global `imap_server_conf` variable
23:  9ccafd286b = 23:  9ab84e459a imap-send: fix leaking memory in `imap_server_conf`
24:  e19457d20c ! 24:  81c69da2e8 builtin/rebase: do not assign default backend to non-constant field
    @@ Commit message
     
         The `struct rebase_options::default_backend` field is a non-constant
         string, but is being assigned a constant via `REBASE_OPTIONS_INIT`.
    -    Refactor the code to initialize and release options via two functions
    -    `rebase_options_init()` and `rebase_options_release()`. Like this, we
    -    can easily adapt the former funnction to use `xstrdup()` on the default
    -    value without hiding it away in a macro.
    +    Fix this by using `xstrdup()` to assign the variable and introduce a new
    +    function `rebase_options_release()` that releases memory held by the
    +    structure, including the newly-allocated variable.
     
         Signed-off-by: Patrick Steinhardt <ps@pks.im>
     
      ## builtin/rebase.c ##
     @@ builtin/rebase.c: struct rebase_options {
    - 	int config_update_refs;
    - };
    - 
    --#define REBASE_OPTIONS_INIT {			  	\
    --		.type = REBASE_UNSPECIFIED,	  	\
    --		.empty = EMPTY_UNSPECIFIED,	  	\
    --		.keep_empty = 1,			\
    + 		.type = REBASE_UNSPECIFIED,	  	\
    + 		.empty = EMPTY_UNSPECIFIED,	  	\
    + 		.keep_empty = 1,			\
     -		.default_backend = "merge",	  	\
    --		.flags = REBASE_NO_QUIET, 		\
    --		.git_am_opts = STRVEC_INIT,		\
    --		.exec = STRING_LIST_INIT_NODUP,		\
    --		.git_format_patch_opt = STRBUF_INIT,	\
    --		.fork_point = -1,			\
    --		.reapply_cherry_picks = -1,             \
    --		.allow_empty_message = 1,               \
    --		.autosquash = -1,                       \
    --		.rebase_merges = -1,                    \
    --		.config_rebase_merges = -1,             \
    --		.update_refs = -1,                      \
    --		.config_update_refs = -1,               \
    --		.strategy_opts = STRING_LIST_INIT_NODUP,\
    --	}
    -+static void rebase_options_init(struct rebase_options *opts)
    -+{
    -+	memset(opts, 0, sizeof(*opts));
    -+	opts->type = REBASE_UNSPECIFIED;
    -+	opts->empty = EMPTY_UNSPECIFIED;
    -+	opts->default_backend = xstrdup("merge");
    -+	opts->keep_empty = 1;
    -+	opts->flags = REBASE_NO_QUIET;
    -+	strvec_init(&opts->git_am_opts);
    -+	string_list_init_nodup(&opts->exec);
    -+	strbuf_init(&opts->git_format_patch_opt, 0);
    -+	opts->fork_point = -1;
    -+	opts->reapply_cherry_picks = -1;
    -+	opts->allow_empty_message = 1;
    -+	opts->autosquash = -1;
    -+	opts->rebase_merges = -1;
    -+	opts->config_rebase_merges = -1;
    -+	opts->update_refs = -1;
    -+	opts->config_update_refs = -1;
    -+	string_list_init_nodup(&opts->strategy_opts);
    -+}
    -+
    ++		.default_backend = xstrdup("merge"),  	\
    + 		.flags = REBASE_NO_QUIET, 		\
    + 		.git_am_opts = STRVEC_INIT,		\
    + 		.exec = STRING_LIST_INIT_NODUP,		\
    +@@ builtin/rebase.c: struct rebase_options {
    + 		.strategy_opts = STRING_LIST_INIT_NODUP,\
    + 	}
    + 
     +static void rebase_options_release(struct rebase_options *opts)
     +{
     +	free(opts->default_backend);
    @@ builtin/rebase.c: struct rebase_options {
     +	string_list_clear(&opts->strategy_opts, 0);
     +	strbuf_release(&opts->git_format_patch_opt);
     +}
    - 
    ++
      static struct replay_opts get_replay_opts(const struct rebase_options *opts)
      {
    + 	struct replay_opts replay = REPLAY_OPTS_INIT;
     @@ builtin/rebase.c: static int rebase_config(const char *var, const char *value,
      	}
      
    @@ builtin/rebase.c: static int rebase_config(const char *var, const char *value,
      		return git_config_string(&opts->default_backend, var, value);
      	}
      
    -@@ builtin/rebase.c: static int check_exec_cmd(const char *cmd)
    - 
    - int cmd_rebase(int argc, const char **argv, const char *prefix)
    - {
    --	struct rebase_options options = REBASE_OPTIONS_INIT;
    -+	struct rebase_options options;
    - 	const char *branch_name;
    - 	int ret, flags, total_argc, in_progress = 0;
    - 	int keep_base = 0;
    -@@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)
    - 	};
    - 	int i;
    - 
    -+	rebase_options_init(&options);
    -+
    - 	if (argc == 2 && !strcmp(argv[1], "-h"))
    - 		usage_with_options(builtin_rebase_usage,
    - 				   builtin_rebase_options);
     @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)
      cleanup:
      	strbuf_release(&buf);
25:  f548241960 ! 25:  6819bf6116 builtin/rebase: always store allocated string in `options.strategy`
    @@ Commit message
      ## builtin/rebase.c ##
     @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)
      {
    - 	struct rebase_options options;
    + 	struct rebase_options options = REBASE_OPTIONS_INIT;
      	const char *branch_name;
     +	const char *strategy_opt = NULL;
      	int ret, flags, total_argc, in_progress = 0;
26:  78ac075644 = 26:  a1d2149429 builtin/merge: always store allocated strings in `pull_twohead`
27:  0cd4ce07d8 = 27:  c714b67199 config.mak.dev: enable `-Wwrite-strings` warning
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 6, 2024, 10:27 UTC · re: Patrick Steinhardt · lore

[PATCH v5 01/27] global: improve const correctness when assigning string constants

We're about to enable `-Wwrite-strings`, which changes the type of string constants to `const char[]`. Fix various sites where we assign such constants to non-const variables.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/bisect.c             |  3 ++-
 builtin/blame.c              |  2 +-
 builtin/bugreport.c          |  2 +-
 builtin/check-ignore.c       |  4 +--
 builtin/clone.c              |  6 ++---
 builtin/commit.c             |  6 ++---
 builtin/diagnose.c           |  2 +-
 builtin/log.c                |  2 +-
 builtin/mailsplit.c          |  4 +--
 builtin/pull.c               | 52 ++++++++++++++++++------------------
 builtin/receive-pack.c       |  4 +--
 builtin/revert.c             |  2 +-
 compat/regex/regcomp.c       |  2 +-
 diff.c                       |  4 +--
 diffcore-rename.c            |  6 ++---
 fmt-merge-msg.c              |  2 +-
 fsck.c                       |  2 +-
 fsck.h                       |  2 +-
 gpg-interface.c              |  2 +-
 http-backend.c               |  2 +-
 imap-send.c                  |  6 ++---
 pretty.c                     |  2 +-
 refs.c                       |  2 +-
 refs.h                       |  2 +-
 reftable/basics.c            | 15 +++++------
 reftable/basics.h            |  4 +--
 reftable/basics_test.c       |  4 +--
 reftable/record.c            |  6 ++---
 reftable/stack.c             | 10 ++++---
 reftable/stack_test.c        |  8 +++---
 run-command.c                |  2 +-
 t/helper/test-hashmap.c      |  3 ++-
 t/helper/test-json-writer.c  | 10 +++----
 t/helper/test-regex.c        |  4 +--
 t/helper/test-rot13-filter.c |  5 ++--
 t/unit-tests/t-strbuf.c      | 10 ++++---
 trailer.c                    |  2 +-
 wt-status.c                  |  2 +-
 38 files changed, 106 insertions(+), 102 deletions(-)
Show changes to 38 files +106 −102

builtin/bisect.c, builtin/blame.c, builtin/bugreport.c, builtin/check-ignore.c, builtin/clone.c, builtin/commit.c, builtin/diagnose.c, builtin/log.c, builtin/mailsplit.c, builtin/pull.c, builtin/receive-pack.c, builtin/revert.c, compat/regex/regcomp.c, diff.c, diffcore-rename.c, fmt-merge-msg.c, fsck.c, fsck.h, gpg-interface.c, http-backend.c, imap-send.c, pretty.c, refs.c, refs.h, reftable/basics.c, reftable/basics.h, reftable/basics_test.c, reftable/record.c, reftable/stack.c, reftable/stack_test.c, run-command.c, t/helper/test-hashmap.c, t/helper/test-json-writer.c, t/helper/test-regex.c, t/helper/test-rot13-filter.c, t/unit-tests/t-strbuf.c, trailer.c, wt-status.c

diff --git a/builtin/bisect.c b/builtin/bisect.c
index a58432b9d9..dabce9b542 100644
--- a/builtin/bisect.c
+++ b/builtin/bisect.c
@@ -262,7 +262,8 @@ static int bisect_reset(const char *commit)
 	return bisect_clean_state();
 }
 
-static void log_commit(FILE *fp, char *fmt, const char *state,
+static void log_commit(FILE *fp,
+		       const char *fmt, const char *state,
 		       struct commit *commit)
 {
 	struct pretty_print_context pp = {0};
diff --git a/builtin/blame.c b/builtin/blame.c
index 838cd476be..98c7629b6a 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -134,7 +134,7 @@ static void get_ac_line(const char *inbuf, const char *what,
 {
 	struct ident_split ident;
 	size_t len, maillen, namelen;
-	char *tmp, *endp;
+	const char *tmp, *endp;
 	const char *namebuf, *mailbuf;
 
 	tmp = strstr(inbuf, what);
diff --git a/builtin/bugreport.c b/builtin/bugreport.c
index 25f860a0d9..b3cc77af53 100644
--- a/builtin/bugreport.c
+++ b/builtin/bugreport.c
@@ -107,7 +107,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix)
 	struct tm tm;
 	enum diagnose_mode diagnose = DIAGNOSE_NONE;
 	char *option_output = NULL;
-	char *option_suffix = "%Y-%m-%d-%H%M";
+	const char *option_suffix = "%Y-%m-%d-%H%M";
 	const char *user_relative_path = NULL;
 	char *prefixed_filename;
 	size_t output_path_len;
diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c
index 6c43430ec4..2bda6a1d46 100644
--- a/builtin/check-ignore.c
+++ b/builtin/check-ignore.c
@@ -35,8 +35,8 @@ static const struct option check_ignore_options[] = {
 
 static void output_pattern(const char *path, struct path_pattern *pattern)
 {
-	char *bang  = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE)  ? "!" : "";
-	char *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? "/" : "";
+	const char *bang  = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE)  ? "!" : "";
+	const char *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? "/" : "";
 	if (!nul_term_line) {
 		if (!verbose) {
 			write_name_quoted(path, stdout, '\n');
diff --git a/builtin/clone.c b/builtin/clone.c
index 23993b905b..92ab7d7165 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -71,7 +71,7 @@ static char *option_branch = NULL;
 static struct string_list option_not = STRING_LIST_INIT_NODUP;
 static const char *real_git_dir;
 static const char *ref_format;
-static char *option_upload_pack = "git-upload-pack";
+static const char *option_upload_pack = "git-upload-pack";
 static int option_verbosity;
 static int option_progress = -1;
 static int option_sparse_checkout;
@@ -177,8 +177,8 @@ static struct option builtin_clone_options[] = {
 
 static const char *get_repo_path_1(struct strbuf *path, int *is_bundle)
 {
-	static char *suffix[] = { "/.git", "", ".git/.git", ".git" };
-	static char *bundle_suffix[] = { ".bundle", "" };
+	static const char *suffix[] = { "/.git", "", ".git/.git", ".git" };
+	static const char *bundle_suffix[] = { ".bundle", "" };
 	size_t baselen = path->len;
 	struct stat st;
 	int i;
diff --git a/builtin/commit.c b/builtin/commit.c
index f53e7e86ff..75c741173e 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,7 +113,7 @@ static char *template_file;
  * the commit message and/or authorship.
  */
 static const char *author_message, *author_message_buffer;
-static char *edit_message, *use_message;
+static const char *edit_message, *use_message;
 static char *fixup_message, *fixup_commit, *squash_message;
 static const char *fixup_prefix;
 static int all, also, interactive, patch_interactive, only, amend, signoff;
@@ -121,8 +121,8 @@ static int edit_flag = -1; /* unspecified */
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
-static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
-static char *sign_commit, *pathspec_from_file;
+static const char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
+static const char *sign_commit, *pathspec_from_file;
 static struct strvec trailer_args = STRVEC_INIT;
 
 /*
diff --git a/builtin/diagnose.c b/builtin/diagnose.c
index 4f22eb2b55..4857a4395b 100644
--- a/builtin/diagnose.c
+++ b/builtin/diagnose.c
@@ -18,7 +18,7 @@ int cmd_diagnose(int argc, const char **argv, const char *prefix)
 	struct tm tm;
 	enum diagnose_mode mode = DIAGNOSE_STATS;
 	char *option_output = NULL;
-	char *option_suffix = "%Y-%m-%d-%H%M";
+	const char *option_suffix = "%Y-%m-%d-%H%M";
 	char *prefixed_filename;
 
 	const struct option diagnose_options[] = {
diff --git a/builtin/log.c b/builtin/log.c
index 78a247d8a9..b8846a9458 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1283,7 +1283,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids)
 	o2->flags = flags2;
 }
 
-static void gen_message_id(struct rev_info *info, char *base)
+static void gen_message_id(struct rev_info *info, const char *base)
 {
 	struct strbuf buf = STRBUF_INIT;
 	strbuf_addf(&buf, "%s.%"PRItime".git.%s", base,
diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c
index 3af9ddb8ae..fe6dbc5d05 100644
--- a/builtin/mailsplit.c
+++ b/builtin/mailsplit.c
@@ -113,8 +113,8 @@ static int populate_maildir_list(struct string_list *list, const char *path)
 	DIR *dir;
 	struct dirent *dent;
 	char *name = NULL;
-	char *subs[] = { "cur", "new", NULL };
-	char **sub;
+	const char *subs[] = { "cur", "new", NULL };
+	const char **sub;
 	int ret = -1;
 
 	for (sub = subs; *sub; ++sub) {
diff --git a/builtin/pull.c b/builtin/pull.c
index d622202bce..2d0429f14f 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -71,48 +71,48 @@ static const char * const pull_usage[] = {
 
 /* Shared options */
 static int opt_verbosity;
-static char *opt_progress;
+static const char *opt_progress;
 static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
 static int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT;
 
 /* Options passed to git-merge or git-rebase */
 static enum rebase_type opt_rebase = -1;
-static char *opt_diffstat;
-static char *opt_log;
-static char *opt_signoff;
-static char *opt_squash;
-static char *opt_commit;
-static char *opt_edit;
-static char *cleanup_arg;
-static char *opt_ff;
-static char *opt_verify_signatures;
-static char *opt_verify;
+static const char *opt_diffstat;
+static const char *opt_log;
+static const char *opt_signoff;
+static const char *opt_squash;
+static const char *opt_commit;
+static const char *opt_edit;
+static const char *cleanup_arg;
+static const char *opt_ff;
+static const char *opt_verify_signatures;
+static const char *opt_verify;
 static int opt_autostash = -1;
 static int config_autostash;
 static int check_trust_level = 1;
 static struct strvec opt_strategies = STRVEC_INIT;
 static struct strvec opt_strategy_opts = STRVEC_INIT;
-static char *opt_gpg_sign;
+static const char *opt_gpg_sign;
 static int opt_allow_unrelated_histories;
 
 /* Options passed to git-fetch */
-static char *opt_all;
-static char *opt_append;
-static char *opt_upload_pack;
+static const char *opt_all;
+static const char *opt_append;
+static const char *opt_upload_pack;
 static int opt_force;
-static char *opt_tags;
-static char *opt_prune;
-static char *max_children;
+static const char *opt_tags;
+static const char *opt_prune;
+static const char *max_children;
 static int opt_dry_run;
-static char *opt_keep;
-static char *opt_depth;
-static char *opt_unshallow;
-static char *opt_update_shallow;
-static char *opt_refmap;
-static char *opt_ipv4;
-static char *opt_ipv6;
+static const char *opt_keep;
+static const char *opt_depth;
+static const char *opt_unshallow;
+static const char *opt_update_shallow;
+static const char *opt_refmap;
+static const char *opt_ipv4;
+static const char *opt_ipv6;
 static int opt_show_forced_updates = -1;
-static char *set_upstream;
+static const char *set_upstream;
 static struct strvec opt_fetch = STRVEC_INIT;
 
 static struct option pull_options[] = {
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 01c1f04ece..c8d12ee0a7 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1249,7 +1249,7 @@ static int run_proc_receive_hook(struct command *commands,
 	return code;
 }
 
-static char *refuse_unconfigured_deny_msg =
+static const char *refuse_unconfigured_deny_msg =
 	N_("By default, updating the current branch in a non-bare repository\n"
 	   "is denied, because it will make the index and work tree inconsistent\n"
 	   "with what you pushed, and will require 'git reset --hard' to match\n"
@@ -1269,7 +1269,7 @@ static void refuse_unconfigured_deny(void)
 	rp_error("%s", _(refuse_unconfigured_deny_msg));
 }
 
-static char *refuse_unconfigured_deny_delete_current_msg =
+static const char *refuse_unconfigured_deny_delete_current_msg =
 	N_("By default, deleting the current branch is denied, because the next\n"
 	   "'git clone' won't result in any file checked out, causing confusion.\n"
 	   "\n"
diff --git a/builtin/revert.c b/builtin/revert.c
index 53935d2c68..7bf2b4e11d 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -179,7 +179,7 @@ static int run_sequencer(int argc, const char **argv, const char *prefix,
 
 	/* Check for incompatible command line arguments */
 	if (cmd) {
-		char *this_operation;
+		const char *this_operation;
 		if (cmd == 'q')
 			this_operation = "--quit";
 		else if (cmd == 'c')
diff --git a/compat/regex/regcomp.c b/compat/regex/regcomp.c
index 2bc0f1187a..6c5d455e92 100644
--- a/compat/regex/regcomp.c
+++ b/compat/regex/regcomp.c
@@ -848,7 +848,7 @@ init_dfa (re_dfa_t *dfa, size_t pat_len)
 {
   unsigned int table_size;
 #ifndef _LIBC
-  char *codeset_name;
+  const char *codeset_name;
 #endif
 
   memset (dfa, '\0', sizeof (re_dfa_t));
diff --git a/diff.c b/diff.c
index e70301df76..ffd867ef6c 100644
--- a/diff.c
+++ b/diff.c
@@ -3764,7 +3764,7 @@ static void builtin_diff(const char *name_a,
 	return;
 }
 
-static char *get_compact_summary(const struct diff_filepair *p, int is_renamed)
+static const char *get_compact_summary(const struct diff_filepair *p, int is_renamed)
 {
 	if (!is_renamed) {
 		if (p->status == DIFF_STATUS_ADDED) {
@@ -4076,7 +4076,7 @@ static int reuse_worktree_file(struct index_state *istate,
 static int diff_populate_gitlink(struct diff_filespec *s, int size_only)
 {
 	struct strbuf buf = STRBUF_INIT;
-	char *dirty = "";
+	const char *dirty = "";
 
 	/* Are we looking at the work tree? */
 	if (s->dirty_submodule)
diff --git a/diffcore-rename.c b/diffcore-rename.c
index 5a6e2bcac7..0e1adb87df 100644
--- a/diffcore-rename.c
+++ b/diffcore-rename.c
@@ -406,7 +406,7 @@ static const char *get_highest_rename_path(struct strintmap *counts)
 	return highest_destination_dir;
 }
 
-static char *UNKNOWN_DIR = "/";  /* placeholder -- short, illegal directory */
+static const char *UNKNOWN_DIR = "/";  /* placeholder -- short, illegal directory */
 
 static int dir_rename_already_determinable(struct strintmap *counts)
 {
@@ -429,8 +429,8 @@ static int dir_rename_already_determinable(struct strintmap *counts)
 }
 
 static void increment_count(struct dir_rename_info *info,
-			    char *old_dir,
-			    char *new_dir)
+			    const char *old_dir,
+			    const char *new_dir)
 {
 	struct strintmap *counts;
 	struct strmap_entry *e;
diff --git a/fmt-merge-msg.c b/fmt-merge-msg.c
index 7d144b803a..5af63ab5ab 100644
--- a/fmt-merge-msg.c
+++ b/fmt-merge-msg.c
@@ -447,7 +447,7 @@ static void fmt_merge_msg_title(struct strbuf *out,
 				const char *current_branch)
 {
 	int i = 0;
-	char *sep = "";
+	const char *sep = "";
 
 	strbuf_addstr(out, "Merge ");
 	for (i = 0; i < srcs.nr; i++) {
diff --git a/fsck.c b/fsck.c
index 7dff41413e..61cd48aa25 100644
--- a/fsck.c
+++ b/fsck.c
@@ -1231,7 +1231,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size,
 }
 
 int fsck_buffer(const struct object_id *oid, enum object_type type,
-		void *data, unsigned long size,
+		const void *data, unsigned long size,
 		struct fsck_options *options)
 {
 	if (type == OBJ_BLOB)
diff --git a/fsck.h b/fsck.h
index 17fa2dda5d..4f0c4e6479 100644
--- a/fsck.h
+++ b/fsck.h
@@ -202,7 +202,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size,
  * struct.
  */
 int fsck_buffer(const struct object_id *oid, enum object_type,
-		void *data, unsigned long size,
+		const void *data, unsigned long size,
 		struct fsck_options *options);
 
 /*
diff --git a/gpg-interface.c b/gpg-interface.c
index 5193223714..71a9382a61 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -727,7 +727,7 @@ static int git_gpg_config(const char *var, const char *value,
 			  void *cb UNUSED)
 {
 	struct gpg_format *fmt = NULL;
-	char *fmtname = NULL;
+	const char *fmtname = NULL;
 	char *trust;
 	int ret;
 
diff --git a/http-backend.c b/http-backend.c
index 5b65287ac9..5b4dca65ed 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -753,7 +753,7 @@ static int bad_request(struct strbuf *hdr, const struct service_cmd *c)
 
 int cmd_main(int argc UNUSED, const char **argv UNUSED)
 {
-	char *method = getenv("REQUEST_METHOD");
+	const char *method = getenv("REQUEST_METHOD");
 	const char *proto_header;
 	char *dir;
 	struct service_cmd *cmd = NULL;
diff --git a/imap-send.c b/imap-send.c
index a5d1510180..8b723b34a5 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -1215,9 +1215,9 @@ static int imap_store_msg(struct imap_store *ctx, struct strbuf *msg)
 static void wrap_in_html(struct strbuf *msg)
 {
 	struct strbuf buf = STRBUF_INIT;
-	static char *content_type = "Content-Type: text/html;\n";
-	static char *pre_open = "<pre>\n";
-	static char *pre_close = "</pre>\n";
+	static const char *content_type = "Content-Type: text/html;\n";
+	static const char *pre_open = "<pre>\n";
+	static const char *pre_close = "</pre>\n";
 	const char *body = strstr(msg->buf, "\n\n");
 
 	if (!body)
diff --git a/pretty.c b/pretty.c
index 22a81506b7..ec05db5655 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1325,7 +1325,7 @@ int format_set_trailers_options(struct process_trailer_options *opts,
 static size_t parse_describe_args(const char *start, struct strvec *args)
 {
 	struct {
-		char *name;
+		const char *name;
 		enum {
 			DESCRIBE_ARG_BOOL,
 			DESCRIBE_ARG_INTEGER,
diff --git a/refs.c b/refs.c
index 8260c27cde..292e8d947e 100644
--- a/refs.c
+++ b/refs.c
@@ -159,7 +159,7 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref)
 {
 	struct ref_namespace_info *info = &ref_namespace[namespace];
 	if (info->ref_updated)
-		free(info->ref);
+		free((char *)info->ref);
 	info->ref = ref;
 	info->ref_updated = 1;
 }
diff --git a/refs.h b/refs.h
index 34568ee1fb..923f751d18 100644
--- a/refs.h
+++ b/refs.h
@@ -975,7 +975,7 @@ struct ref_store *get_worktree_ref_store(const struct worktree *wt);
  */
 
 struct ref_namespace_info {
-	char *ref;
+	const char *ref;
 	enum decoration_type decoration;
 
 	/*
diff --git a/reftable/basics.c b/reftable/basics.c
index fea711db7e..0058619ca6 100644
--- a/reftable/basics.c
+++ b/reftable/basics.c
@@ -67,9 +67,9 @@ void free_names(char **a)
 	reftable_free(a);
 }
 
-size_t names_length(char **names)
+size_t names_length(const char **names)
 {
-	char **p = names;
+	const char **p = names;
 	while (*p)
 		p++;
 	return p - names;
@@ -102,15 +102,12 @@ void parse_names(char *buf, int size, char ***namesp)
 	*namesp = names;
 }
 
-int names_equal(char **a, char **b)
+int names_equal(const char **a, const char **b)
 {
-	int i = 0;
-	for (; a[i] && b[i]; i++) {
-		if (strcmp(a[i], b[i])) {
+	size_t i = 0;
+	for (; a[i] && b[i]; i++)
+		if (strcmp(a[i], b[i]))
 			return 0;
-		}
-	}
-
 	return a[i] == b[i];
 }
 
diff --git a/reftable/basics.h b/reftable/basics.h
index 523ecd5307..c8fec68d4e 100644
--- a/reftable/basics.h
+++ b/reftable/basics.h
@@ -42,10 +42,10 @@ void free_names(char **a);
 void parse_names(char *buf, int size, char ***namesp);
 
 /* compares two NULL-terminated arrays of strings. */
-int names_equal(char **a, char **b);
+int names_equal(const char **a, const char **b);
 
 /* returns the array size of a NULL-terminated array of strings. */
-size_t names_length(char **names);
+size_t names_length(const char **names);
 
 /* Allocation routines; they invoke the functions set through
  * reftable_set_alloc() */
diff --git a/reftable/basics_test.c b/reftable/basics_test.c
index 997c4d9e01..13bc761817 100644
--- a/reftable/basics_test.c
+++ b/reftable/basics_test.c
@@ -58,8 +58,8 @@ static void test_binsearch(void)
 
 static void test_names_length(void)
 {
-	char *a[] = { "a", "b", NULL };
-	EXPECT(names_length(a) == 2);
+	const char *names[] = { "a", "b", NULL };
+	EXPECT(names_length(names) == 2);
 }
 
 static void test_parse_names_normal(void)
diff --git a/reftable/record.c b/reftable/record.c
index 5506f3e913..a2cba5ef74 100644
--- a/reftable/record.c
+++ b/reftable/record.c
@@ -116,7 +116,7 @@ static int decode_string(struct strbuf *dest, struct string_view in)
 	return start_len - in.len;
 }
 
-static int encode_string(char *str, struct string_view s)
+static int encode_string(const char *str, struct string_view s)
 {
 	struct string_view start = s;
 	int l = strlen(str);
@@ -969,9 +969,9 @@ static int reftable_log_record_decode(void *rec, struct strbuf key,
 	return REFTABLE_FORMAT_ERROR;
 }
 
-static int null_streq(char *a, char *b)
+static int null_streq(const char *a, const char *b)
 {
-	char *empty = "";
+	const char *empty = "";
 	if (!a)
 		a = empty;
 
diff --git a/reftable/stack.c b/reftable/stack.c
index a59ebe038d..09549c51c9 100644
--- a/reftable/stack.c
+++ b/reftable/stack.c
@@ -204,7 +204,8 @@ static struct reftable_reader **stack_copy_readers(struct reftable_stack *st,
 	return cur;
 }
 
-static int reftable_stack_reload_once(struct reftable_stack *st, char **names,
+static int reftable_stack_reload_once(struct reftable_stack *st,
+				      const char **names,
 				      int reuse_open)
 {
 	size_t cur_len = !st->merged ? 0 : st->merged->stack_len;
@@ -222,7 +223,7 @@ static int reftable_stack_reload_once(struct reftable_stack *st, char **names,
 
 	while (*names) {
 		struct reftable_reader *rd = NULL;
-		char *name = *names++;
+		const char *name = *names++;
 
 		/* this is linear; we assume compaction keeps the number of
 		   tables under control so this is not quadratic. */
@@ -354,7 +355,7 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
 				goto out;
 		}
 
-		err = reftable_stack_reload_once(st, names, reuse_open);
+		err = reftable_stack_reload_once(st, (const char **) names, reuse_open);
 		if (!err)
 			break;
 		if (err != REFTABLE_NOT_EXIST_ERROR)
@@ -368,7 +369,8 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
 		err = read_lines(st->list_file, &names_after);
 		if (err < 0)
 			goto out;
-		if (names_equal(names_after, names)) {
+		if (names_equal((const char **) names_after,
+				(const char **) names)) {
 			err = REFTABLE_NOT_EXIST_ERROR;
 			goto out;
 		}
diff --git a/reftable/stack_test.c b/reftable/stack_test.c
index 7889f818d1..07d89b45da 100644
--- a/reftable/stack_test.c
+++ b/reftable/stack_test.c
@@ -83,7 +83,7 @@ static void test_read_file(void)
 	char out[1024] = "line1\n\nline2\nline3";
 	int n, err;
 	char **names = NULL;
-	char *want[] = { "line1", "line2", "line3" };
+	const char *want[] = { "line1", "line2", "line3" };
 	int i = 0;
 
 	EXPECT(fd > 0);
@@ -116,9 +116,9 @@ static void test_parse_names(void)
 
 static void test_names_equal(void)
 {
-	char *a[] = { "a", "b", "c", NULL };
-	char *b[] = { "a", "b", "d", NULL };
-	char *c[] = { "a", "b", NULL };
+	const char *a[] = { "a", "b", "c", NULL };
+	const char *b[] = { "a", "b", "d", NULL };
+	const char *c[] = { "a", "b", NULL };
 
 	EXPECT(names_equal(a, a));
 	EXPECT(!names_equal(a, b));
diff --git a/run-command.c b/run-command.c
index 1b821042b4..7600531fb6 100644
--- a/run-command.c
+++ b/run-command.c
@@ -663,7 +663,7 @@ int start_command(struct child_process *cmd)
 	int need_in, need_out, need_err;
 	int fdin[2], fdout[2], fderr[2];
 	int failed_errno;
-	char *str;
+	const char *str;
 
 	/*
 	 * In case of errors we must keep the promise to close FDs
diff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c
index 0eb0b3d49c..2912899558 100644
--- a/t/helper/test-hashmap.c
+++ b/t/helper/test-hashmap.c
@@ -36,7 +36,8 @@ static int test_entry_cmp(const void *cmp_data,
 }
 
 static struct test_entry *alloc_test_entry(unsigned int hash,
-					   char *key, char *value)
+					   const char *key,
+					   const char *value)
 {
 	size_t klen = strlen(key);
 	size_t vlen = strlen(value);
diff --git a/t/helper/test-json-writer.c b/t/helper/test-json-writer.c
index afe393f597..ed52eb76bf 100644
--- a/t/helper/test-json-writer.c
+++ b/t/helper/test-json-writer.c
@@ -174,7 +174,7 @@ static void make_arr4(int pretty)
 	jw_end(&arr4);
 }
 
-static char *expect_nest1 =
+static const char *expect_nest1 =
 	"{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true},\"arr1\":[\"abc\",42,true]}";
 
 static struct json_writer nest1 = JSON_WRITER_INIT;
@@ -195,10 +195,10 @@ static void make_nest1(int pretty)
 	jw_release(&arr1);
 }
 
-static char *expect_inline1 =
+static const char *expect_inline1 =
 	"{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true},\"arr1\":[\"abc\",42,true]}";
 
-static char *pretty_inline1 =
+static const char *pretty_inline1 =
 	("{\n"
 	 "  \"obj1\": {\n"
 	 "    \"a\": \"abc\",\n"
@@ -236,10 +236,10 @@ static void make_inline1(int pretty)
 	jw_end(&inline1);
 }
 
-static char *expect_inline2 =
+static const char *expect_inline2 =
 	"[[1,2],[3,4],{\"a\":\"abc\"}]";
 
-static char *pretty_inline2 =
+static const char *pretty_inline2 =
 	("[\n"
 	 "  [\n"
 	 "    1,\n"
diff --git a/t/helper/test-regex.c b/t/helper/test-regex.c
index 80042eafc2..366bd70976 100644
--- a/t/helper/test-regex.c
+++ b/t/helper/test-regex.c
@@ -20,8 +20,8 @@ static struct reg_flag reg_flags[] = {
 
 static int test_regex_bug(void)
 {
-	char *pat = "[^={} \t]+";
-	char *str = "={}\nfred";
+	const char *pat = "[^={} \t]+";
+	const char *str = "={}\nfred";
 	regex_t r;
 	regmatch_t m[1];
 
diff --git a/t/helper/test-rot13-filter.c b/t/helper/test-rot13-filter.c
index f8d564c622..7e1d9e0ee4 100644
--- a/t/helper/test-rot13-filter.c
+++ b/t/helper/test-rot13-filter.c
@@ -136,7 +136,7 @@ static void free_delay_entries(void)
 	strmap_clear(&delay, 0);
 }
 
-static void add_delay_entry(char *pathname, int count, int requested)
+static void add_delay_entry(const char *pathname, int count, int requested)
 {
 	struct delay_entry *entry = xcalloc(1, sizeof(*entry));
 	entry->count = count;
@@ -189,7 +189,8 @@ static void reply_list_available_blobs_cmd(void)
 static void command_loop(void)
 {
 	for (;;) {
-		char *buf, *output;
+		char *buf;
+		const char *output;
 		char *pathname;
 		struct delay_entry *entry;
 		struct strbuf input = STRBUF_INIT;
diff --git a/t/unit-tests/t-strbuf.c b/t/unit-tests/t-strbuf.c
index de434a4441..6027dafef7 100644
--- a/t/unit-tests/t-strbuf.c
+++ b/t/unit-tests/t-strbuf.c
@@ -2,7 +2,8 @@
 #include "strbuf.h"
 
 /* wrapper that supplies tests with an empty, initialized strbuf */
-static void setup(void (*f)(struct strbuf*, void*), void *data)
+static void setup(void (*f)(struct strbuf*, const void*),
+		  const void *data)
 {
 	struct strbuf buf = STRBUF_INIT;
 
@@ -13,7 +14,8 @@ static void setup(void (*f)(struct strbuf*, void*), void *data)
 }
 
 /* wrapper that supplies tests with a populated, initialized strbuf */
-static void setup_populated(void (*f)(struct strbuf*, void*), char *init_str, void *data)
+static void setup_populated(void (*f)(struct strbuf*, const void*),
+			    const char *init_str, const void *data)
 {
 	struct strbuf buf = STRBUF_INIT;
 
@@ -64,7 +66,7 @@ static void t_dynamic_init(void)
 	strbuf_release(&buf);
 }
 
-static void t_addch(struct strbuf *buf, void *data)
+static void t_addch(struct strbuf *buf, const void *data)
 {
 	const char *p_ch = data;
 	const char ch = *p_ch;
@@ -83,7 +85,7 @@ static void t_addch(struct strbuf *buf, void *data)
 	check_char(buf->buf[buf->len], ==, '\0');
 }
 
-static void t_addstr(struct strbuf *buf, void *data)
+static void t_addstr(struct strbuf *buf, const void *data)
 {
 	const char *text = data;
 	size_t len = strlen(text);
diff --git a/trailer.c b/trailer.c
index 2bcb9ba8f7..72e5136c73 100644
--- a/trailer.c
+++ b/trailer.c
@@ -63,7 +63,7 @@ struct arg_item {
 
 static LIST_HEAD(conf_head);
 
-static char *separators = ":";
+static const char *separators = ":";
 
 static int configured;
 
diff --git a/wt-status.c b/wt-status.c
index ff4be071ca..7912545e4e 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -2408,7 +2408,7 @@ static void wt_porcelain_v2_print_unmerged_entry(
 		int mode;
 		struct object_id oid;
 	} stages[3];
-	char *key;
+	const char *key;
 	char submodule_token[5];
 	char unmerged_prefix = 'u';
 	char eol_char = s->null_termination ? '\0' : '\n';
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 6, 2024, 10:27 UTC · re: Patrick Steinhardt · lore

[PATCH v5 02/27] global: convert intentionally-leaking config strings to consts

There are multiple cases where we intentionally leak config strings:
  - `struct gpg_format` is used to track programs that can be used for
    signing commits, either via gpg(1), gpgsm(1) or ssh-keygen(1). The
    user can override the commands via several config variables. As the
    array is populated once, only, and the struct memers are never
    written to or free'd.
  - `struct ll_merge_driver` is used to track merge drivers. Same as
    with the GPG format, these drivers are populated once and then
    reused. Its data is never written to or free'd, either.
  - `struct userdiff_funcname` and `struct userdiff_driver` can be
    configured via `diff.<driver>.*` to add additional drivers. Again,
    these have a global lifetime and are never written to or free'd.

All of these are intentionally kept alive and are never written to. Furthermore, all of these are being assigned both string constants in some places, and allocated strings in other places. This will cause warnings once we enable `-Wwrite-strings`, so let's mark the respective fields as `const char *` and cast away the constness when assigning those values.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 gpg-interface.c |  4 ++--
 merge-ll.c      | 11 ++++++++---
 userdiff.c      | 10 +++++-----
 userdiff.h      | 12 ++++++------
 4 files changed, 21 insertions(+), 16 deletions(-)
Show changes to 4 files +21 −16

gpg-interface.c, merge-ll.c, userdiff.c, userdiff.h

diff --git a/gpg-interface.c b/gpg-interface.c
index 71a9382a61..5c824aeb25 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -34,7 +34,7 @@ static enum signature_trust_level configured_min_trust_level = TRUST_UNDEFINED;
 
 struct gpg_format {
 	const char *name;
-	char *program;
+	const char *program;
 	const char **verify_args;
 	const char **sigs;
 	int (*verify_signed_buffer)(struct signature_check *sigc,
@@ -783,7 +783,7 @@ static int git_gpg_config(const char *var, const char *value,
 
 	if (fmtname) {
 		fmt = get_format_by_name(fmtname);
-		return git_config_string(&fmt->program, var, value);
+		return git_config_string((char **) &fmt->program, var, value);
 	}
 
 	return 0;
diff --git a/merge-ll.c b/merge-ll.c
index e29b15fa4a..180c19df67 100644
--- a/merge-ll.c
+++ b/merge-ll.c
@@ -27,7 +27,7 @@ typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *,
 
 struct ll_merge_driver {
 	const char *name;
-	char *description;
+	const char *description;
 	ll_merge_fn fn;
 	char *recursive;
 	struct ll_merge_driver *next;
@@ -304,8 +304,13 @@ static int read_merge_config(const char *var, const char *value,
 		ll_user_merge_tail = &(fn->next);
 	}
 
-	if (!strcmp("name", key))
-		return git_config_string(&fn->description, var, value);
+	if (!strcmp("name", key)) {
+		/*
+		 * The description is leaking, but that's okay as we want to
+		 * keep around the merge drivers anyway.
+		 */
+		return git_config_string((char **) &fn->description, var, value);
+	}
 
 	if (!strcmp("driver", key)) {
 		if (!value)
diff --git a/userdiff.c b/userdiff.c
index 82bc76b910..371032a413 100644
--- a/userdiff.c
+++ b/userdiff.c
@@ -399,7 +399,7 @@ static struct userdiff_driver *userdiff_find_by_namelen(const char *name, size_t
 static int parse_funcname(struct userdiff_funcname *f, const char *k,
 		const char *v, int cflags)
 {
-	if (git_config_string(&f->pattern, k, v) < 0)
+	if (git_config_string((char **) &f->pattern, k, v) < 0)
 		return -1;
 	f->cflags = cflags;
 	return 0;
@@ -445,15 +445,15 @@ int userdiff_config(const char *k, const char *v)
 	if (!strcmp(type, "binary"))
 		return parse_tristate(&drv->binary, k, v);
 	if (!strcmp(type, "command"))
-		return git_config_string(&drv->external, k, v);
+		return git_config_string((char **) &drv->external, k, v);
 	if (!strcmp(type, "textconv"))
-		return git_config_string(&drv->textconv, k, v);
+		return git_config_string((char **) &drv->textconv, k, v);
 	if (!strcmp(type, "cachetextconv"))
 		return parse_bool(&drv->textconv_want_cache, k, v);
 	if (!strcmp(type, "wordregex"))
-		return git_config_string(&drv->word_regex, k, v);
+		return git_config_string((char **) &drv->word_regex, k, v);
 	if (!strcmp(type, "algorithm"))
-		return git_config_string(&drv->algorithm, k, v);
+		return git_config_string((char **) &drv->algorithm, k, v);
 
 	return 0;
 }
diff --git a/userdiff.h b/userdiff.h
index cc8e5abfef..d726804c3e 100644
--- a/userdiff.h
+++ b/userdiff.h
@@ -7,19 +7,19 @@ struct index_state;
 struct repository;
 
 struct userdiff_funcname {
-	char *pattern;
+	const char *pattern;
 	int cflags;
 };
 
 struct userdiff_driver {
 	const char *name;
-	char *external;
-	char *algorithm;
+	const char *external;
+	const char *algorithm;
 	int binary;
 	struct userdiff_funcname funcname;
-	char *word_regex;
-	char *word_regex_multi_byte;
-	char *textconv;
+	const char *word_regex;
+	const char *word_regex_multi_byte;
+	const char *textconv;
 	struct notes_cache *textconv_cache;
 	int textconv_want_cache;
 };
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 6, 2024, 10:27 UTC · re: Patrick Steinhardt · lore

[PATCH v5 03/27] refs/reftable: stop micro-optimizing refname allocations on copy

When copying refs, we execute `write_copy_table()` to write the new table. As the names are given to us via `arg->newname` and `arg->oldname`, respectively, we optimize away some allocations by assigning those fields to the reftable records we are about to write directly, without duplicating them. This requires us to cast the input to `char *` pointers as they are in fact constant strings. Later on, we then unset the refname for all of the records before calling `reftable_log_record_release()` on them.

We also do this when assigning the "HEAD" constant, but here we do not cast because its type is `char[]` by default. It's about to be turned into `const char *` though once we enable `-Wwrite-strings` and will thus cause another warning.

It's quite dubious whether this micro-optimization really helps. We're about to write to disk anyway, which is going to be way slower than a small handful of allocations. Let's drop the optimization altogther and instead copy arguments to simplify the code and avoid the future warning with `-Wwrite-strings`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 refs/reftable-backend.c | 28 ++++++++++++++++------------
 1 file changed, 16 insertions(+), 12 deletions(-)
Show changes to refs/reftable-backend.c +16 −12
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 1af86bbdec..e77faa2b9d 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1340,10 +1340,10 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 	 * old reference.
 	 */
 	refs[0] = old_ref;
-	refs[0].refname = (char *)arg->newname;
+	refs[0].refname = xstrdup(arg->newname);
 	refs[0].update_index = creation_ts;
 	if (arg->delete_old) {
-		refs[1].refname = (char *)arg->oldname;
+		refs[1].refname = xstrdup(arg->oldname);
 		refs[1].value_type = REFTABLE_REF_DELETION;
 		refs[1].update_index = deletion_ts;
 	}
@@ -1366,7 +1366,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 		ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 		memset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));
 		fill_reftable_log_record(&logs[logs_nr], &committer_ident);
-		logs[logs_nr].refname = (char *)arg->newname;
+		logs[logs_nr].refname = xstrdup(arg->newname);
 		logs[logs_nr].update_index = deletion_ts;
 		logs[logs_nr].value.update.message =
 			xstrndup(arg->logmsg, arg->refs->write_options.block_size / 2);
@@ -1387,7 +1387,13 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 		if (append_head_reflog) {
 			ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 			logs[logs_nr] = logs[logs_nr - 1];
-			logs[logs_nr].refname = "HEAD";
+			logs[logs_nr].refname = xstrdup("HEAD");
+			logs[logs_nr].value.update.name =
+				xstrdup(logs[logs_nr].value.update.name);
+			logs[logs_nr].value.update.email =
+				xstrdup(logs[logs_nr].value.update.email);
+			logs[logs_nr].value.update.message =
+				xstrdup(logs[logs_nr].value.update.message);
 			logs_nr++;
 		}
 	}
@@ -1398,7 +1404,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 	ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 	memset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));
 	fill_reftable_log_record(&logs[logs_nr], &committer_ident);
-	logs[logs_nr].refname = (char *)arg->newname;
+	logs[logs_nr].refname = xstrdup(arg->newname);
 	logs[logs_nr].update_index = creation_ts;
 	logs[logs_nr].value.update.message =
 		xstrndup(arg->logmsg, arg->refs->write_options.block_size / 2);
@@ -1430,7 +1436,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 		 */
 		ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 		logs[logs_nr] = old_log;
-		logs[logs_nr].refname = (char *)arg->newname;
+		logs[logs_nr].refname = xstrdup(arg->newname);
 		logs_nr++;
 
 		/*
@@ -1439,7 +1445,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 		if (arg->delete_old) {
 			ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 			memset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));
-			logs[logs_nr].refname = (char *)arg->oldname;
+			logs[logs_nr].refname = xstrdup(arg->oldname);
 			logs[logs_nr].value_type = REFTABLE_LOG_DELETION;
 			logs[logs_nr].update_index = old_log.update_index;
 			logs_nr++;
@@ -1462,13 +1468,11 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 	reftable_iterator_destroy(&it);
 	string_list_clear(&skip, 0);
 	strbuf_release(&errbuf);
-	for (i = 0; i < logs_nr; i++) {
-		if (!strcmp(logs[i].refname, "HEAD"))
-			continue;
-		logs[i].refname = NULL;
+	for (i = 0; i < logs_nr; i++)
 		reftable_log_record_release(&logs[i]);
-	}
 	free(logs);
+	for (i = 0; i < ARRAY_SIZE(refs); i++)
+		reftable_ref_record_release(&refs[i]);
 	reftable_ref_record_release(&old_ref);
 	reftable_log_record_release(&old_log);
 	return ret;
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 6, 2024, 10:28 UTC · re: Patrick Steinhardt · lore

[PATCH v5 04/27] reftable: cast away constness when assigning constants to records

The reftable records are used in multiple ways throughout the reftable library. In many of those cases they merely act as input to a function without getting modified by it at all. Most importantly, this happens when writing records and when querying for records.

We rely on this in our tests and thus assign string constants to those fields, which is about to generate warnings as those fields are of type `char *`. While we could go through the process and instead allocate those strings in all of our tests, this feels quite unnecessary.

Instead, add casts to `char *` for all of those strings. As this is part of our tests, this also nicely serves as a demonstration that nothing writes or frees those string constants, which would otherwise lead to segfaults.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 reftable/block_test.c     |  2 +-
 reftable/merged_test.c    | 44 +++++++++++++++++------------------
 reftable/readwrite_test.c | 32 +++++++++++++-------------
 reftable/stack_test.c     | 48 +++++++++++++++++++--------------------
 4 files changed, 63 insertions(+), 63 deletions(-)
Show changes to 4 files +63 −63

reftable/block_test.c, reftable/merged_test.c, reftable/readwrite_test.c, reftable/stack_test.c

diff --git a/reftable/block_test.c b/reftable/block_test.c
index 26a9cfbc83..90aecd5a7c 100644
--- a/reftable/block_test.c
+++ b/reftable/block_test.c
@@ -42,7 +42,7 @@ static void test_block_read_write(void)
 	block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,
 			  header_off, hash_size(GIT_SHA1_FORMAT_ID));
 
-	rec.u.ref.refname = "";
+	rec.u.ref.refname = (char *) "";
 	rec.u.ref.value_type = REFTABLE_REF_DELETION;
 	n = block_writer_add(&bw, &rec);
 	EXPECT(n == REFTABLE_API_ERROR);
diff --git a/reftable/merged_test.c b/reftable/merged_test.c
index 530fc82d1c..6d1159d12d 100644
--- a/reftable/merged_test.c
+++ b/reftable/merged_test.c
@@ -124,13 +124,13 @@ static void readers_destroy(struct reftable_reader **readers, size_t n)
 static void test_merged_between(void)
 {
 	struct reftable_ref_record r1[] = { {
-		.refname = "b",
+		.refname = (char *) "b",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_VAL1,
 		.value.val1 = { 1, 2, 3, 0 },
 	} };
 	struct reftable_ref_record r2[] = { {
-		.refname = "a",
+		.refname = (char *) "a",
 		.update_index = 2,
 		.value_type = REFTABLE_REF_DELETION,
 	} };
@@ -165,38 +165,38 @@ static void test_merged(void)
 {
 	struct reftable_ref_record r1[] = {
 		{
-			.refname = "a",
+			.refname = (char *) "a",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
 		},
 		{
-			.refname = "b",
+			.refname = (char *) "b",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
 		},
 		{
-			.refname = "c",
+			.refname = (char *) "c",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
 		}
 	};
 	struct reftable_ref_record r2[] = { {
-		.refname = "a",
+		.refname = (char *) "a",
 		.update_index = 2,
 		.value_type = REFTABLE_REF_DELETION,
 	} };
 	struct reftable_ref_record r3[] = {
 		{
-			.refname = "c",
+			.refname = (char *) "c",
 			.update_index = 3,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 2 },
 		},
 		{
-			.refname = "d",
+			.refname = (char *) "d",
 			.update_index = 3,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
@@ -291,46 +291,46 @@ static void test_merged_logs(void)
 {
 	struct reftable_log_record r1[] = {
 		{
-			.refname = "a",
+			.refname = (char *) "a",
 			.update_index = 2,
 			.value_type = REFTABLE_LOG_UPDATE,
 			.value.update = {
 				.old_hash = { 2 },
 				/* deletion */
-				.name = "jane doe",
-				.email = "jane@invalid",
-				.message = "message2",
+				.name = (char *) "jane doe",
+				.email = (char *) "jane@invalid",
+				.message = (char *) "message2",
 			}
 		},
 		{
-			.refname = "a",
+			.refname = (char *) "a",
 			.update_index = 1,
 			.value_type = REFTABLE_LOG_UPDATE,
 			.value.update = {
 				.old_hash = { 1 },
 				.new_hash = { 2 },
-				.name = "jane doe",
-				.email = "jane@invalid",
-				.message = "message1",
+				.name = (char *) "jane doe",
+				.email = (char *) "jane@invalid",
+				.message = (char *) "message1",
 			}
 		},
 	};
 	struct reftable_log_record r2[] = {
 		{
-			.refname = "a",
+			.refname = (char *) "a",
 			.update_index = 3,
 			.value_type = REFTABLE_LOG_UPDATE,
 			.value.update = {
 				.new_hash = { 3 },
-				.name = "jane doe",
-				.email = "jane@invalid",
-				.message = "message3",
+				.name = (char *) "jane doe",
+				.email = (char *) "jane@invalid",
+				.message = (char *) "message3",
 			}
 		},
 	};
 	struct reftable_log_record r3[] = {
 		{
-			.refname = "a",
+			.refname = (char *) "a",
 			.update_index = 2,
 			.value_type = REFTABLE_LOG_DELETION,
 		},
@@ -406,7 +406,7 @@ static void test_default_write_opts(void)
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
 
 	struct reftable_ref_record rec = {
-		.refname = "master",
+		.refname = (char *) "master",
 		.update_index = 1,
 	};
 	int err;
diff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c
index a6dbd214c5..c55019232b 100644
--- a/reftable/readwrite_test.c
+++ b/reftable/readwrite_test.c
@@ -86,7 +86,7 @@ static void write_table(char ***names, struct strbuf *buf, int N,
 		log.update_index = update_index;
 		log.value_type = REFTABLE_LOG_UPDATE;
 		set_test_hash(log.value.update.new_hash, i);
-		log.value.update.message = "message";
+		log.value.update.message = (char *) "message";
 
 		n = reftable_writer_add_log(w, &log);
 		EXPECT(n == 0);
@@ -118,15 +118,15 @@ static void test_log_buffer_size(void)
 	int err;
 	int i;
 	struct reftable_log_record
-		log = { .refname = "refs/heads/master",
+		log = { .refname = (char *) "refs/heads/master",
 			.update_index = 0xa,
 			.value_type = REFTABLE_LOG_UPDATE,
 			.value = { .update = {
-					   .name = "Han-Wen Nienhuys",
-					   .email = "hanwen@google.com",
+					   .name = (char *) "Han-Wen Nienhuys",
+					   .email = (char *) "hanwen@google.com",
 					   .tz_offset = 100,
 					   .time = 0x5e430672,
-					   .message = "commit: 9\n",
+					   .message = (char *) "commit: 9\n",
 				   } } };
 	struct reftable_writer *w =
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
@@ -156,15 +156,15 @@ static void test_log_overflow(void)
 	};
 	int err;
 	struct reftable_log_record log = {
-		.refname = "refs/heads/master",
+		.refname = (char *) "refs/heads/master",
 		.update_index = 0xa,
 		.value_type = REFTABLE_LOG_UPDATE,
 		.value = {
 			.update = {
 				.old_hash = { 1 },
 				.new_hash = { 2 },
-				.name = "Han-Wen Nienhuys",
-				.email = "hanwen@google.com",
+				.name = (char *) "Han-Wen Nienhuys",
+				.email = (char *) "hanwen@google.com",
 				.tz_offset = 100,
 				.time = 0x5e430672,
 				.message = msg,
@@ -293,14 +293,14 @@ static void test_log_zlib_corruption(void)
 	char message[100] = { 0 };
 	int err, i, n;
 	struct reftable_log_record log = {
-		.refname = "refname",
+		.refname = (char *) "refname",
 		.value_type = REFTABLE_LOG_UPDATE,
 		.value = {
 			.update = {
 				.new_hash = { 1 },
 				.old_hash = { 2 },
-				.name = "My Name",
-				.email = "myname@invalid",
+				.name = (char *) "My Name",
+				.email = (char *) "myname@invalid",
 				.message = message,
 			},
 		},
@@ -728,7 +728,7 @@ static void test_write_empty_key(void)
 	struct reftable_writer *w =
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
 	struct reftable_ref_record ref = {
-		.refname = "",
+		.refname = (char *) "",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_DELETION,
 	};
@@ -752,18 +752,18 @@ static void test_write_key_order(void)
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
 	struct reftable_ref_record refs[2] = {
 		{
-			.refname = "b",
+			.refname = (char *) "b",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_SYMREF,
 			.value = {
-				.symref = "target",
+				.symref = (char *) "target",
 			},
 		}, {
-			.refname = "a",
+			.refname = (char *) "a",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_SYMREF,
 			.value = {
-				.symref = "target",
+				.symref = (char *) "target",
 			},
 		}
 	};
diff --git a/reftable/stack_test.c b/reftable/stack_test.c
index 07d89b45da..4abf92636d 100644
--- a/reftable/stack_test.c
+++ b/reftable/stack_test.c
@@ -156,10 +156,10 @@ static void test_reftable_stack_add_one(void)
 	struct reftable_stack *st = NULL;
 	int err;
 	struct reftable_ref_record ref = {
-		.refname = "HEAD",
+		.refname = (char *) "HEAD",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *) "master",
 	};
 	struct reftable_ref_record dest = { NULL };
 	struct stat stat_result = { 0 };
@@ -216,16 +216,16 @@ static void test_reftable_stack_uptodate(void)
 
 	int err;
 	struct reftable_ref_record ref1 = {
-		.refname = "HEAD",
+		.refname = (char *) "HEAD",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *) "master",
 	};
 	struct reftable_ref_record ref2 = {
-		.refname = "branch2",
+		.refname = (char *) "branch2",
 		.update_index = 2,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *) "master",
 	};
 
 
@@ -264,10 +264,10 @@ static void test_reftable_stack_transaction_api(void)
 	struct reftable_addition *add = NULL;
 
 	struct reftable_ref_record ref = {
-		.refname = "HEAD",
+		.refname = (char *) "HEAD",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *) "master",
 	};
 	struct reftable_ref_record dest = { NULL };
 
@@ -313,7 +313,7 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)
 		struct reftable_ref_record ref = {
 			.update_index = reftable_stack_next_update_index(st),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = (char *) "master",
 		};
 		char name[100];
 
@@ -356,7 +356,7 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)
 static void test_reftable_stack_auto_compaction_fails_gracefully(void)
 {
 	struct reftable_ref_record ref = {
-		.refname = "refs/heads/master",
+		.refname = (char *) "refs/heads/master",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_VAL1,
 		.value.val1 = {0x01},
@@ -409,16 +409,16 @@ static void test_reftable_stack_update_index_check(void)
 	struct reftable_stack *st = NULL;
 	int err;
 	struct reftable_ref_record ref1 = {
-		.refname = "name1",
+		.refname = (char *) "name1",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *) "master",
 	};
 	struct reftable_ref_record ref2 = {
-		.refname = "name2",
+		.refname = (char *) "name2",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *) "master",
 	};
 
 	err = reftable_new_stack(&st, dir, cfg);
@@ -561,7 +561,7 @@ static void test_reftable_stack_log_normalize(void)
 	struct reftable_stack *st = NULL;
 	char *dir = get_tmp_dir(__LINE__);
 	struct reftable_log_record input = {
-		.refname = "branch",
+		.refname = (char *) "branch",
 		.update_index = 1,
 		.value_type = REFTABLE_LOG_UPDATE,
 		.value = {
@@ -582,11 +582,11 @@ static void test_reftable_stack_log_normalize(void)
 	err = reftable_new_stack(&st, dir, cfg);
 	EXPECT_ERR(err);
 
-	input.value.update.message = "one\ntwo";
+	input.value.update.message = (char *) "one\ntwo";
 	err = reftable_stack_add(st, &write_test_log, &arg);
 	EXPECT(err == REFTABLE_API_ERROR);
 
-	input.value.update.message = "one";
+	input.value.update.message = (char *) "one";
 	err = reftable_stack_add(st, &write_test_log, &arg);
 	EXPECT_ERR(err);
 
@@ -594,7 +594,7 @@ static void test_reftable_stack_log_normalize(void)
 	EXPECT_ERR(err);
 	EXPECT(0 == strcmp(dest.value.update.message, "one\n"));
 
-	input.value.update.message = "two\n";
+	input.value.update.message = (char *) "two\n";
 	arg.update_index = 2;
 	err = reftable_stack_add(st, &write_test_log, &arg);
 	EXPECT_ERR(err);
@@ -697,9 +697,9 @@ static void test_reftable_stack_hash_id(void)
 	int err;
 
 	struct reftable_ref_record ref = {
-		.refname = "master",
+		.refname = (char *) "master",
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "target",
+		.value.symref = (char *) "target",
 		.update_index = 1,
 	};
 	struct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID };
@@ -879,7 +879,7 @@ static void test_reftable_stack_auto_compaction(void)
 			.refname = name,
 			.update_index = reftable_stack_next_update_index(st),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = (char *) "master",
 		};
 		snprintf(name, sizeof(name), "branch%04d", i);
 
@@ -913,7 +913,7 @@ static void test_reftable_stack_add_performs_auto_compaction(void)
 		struct reftable_ref_record ref = {
 			.update_index = reftable_stack_next_update_index(st),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = (char *) "master",
 		};
 
 		/*
@@ -964,7 +964,7 @@ static void test_reftable_stack_compaction_concurrent(void)
 			.refname = name,
 			.update_index = reftable_stack_next_update_index(st1),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = (char *) "master",
 		};
 		snprintf(name, sizeof(name), "branch%04d", i);
 
@@ -1014,7 +1014,7 @@ static void test_reftable_stack_compaction_concurrent_clean(void)
 			.refname = name,
 			.update_index = reftable_stack_next_update_index(st1),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = (char *) "master",
 		};
 		snprintf(name, sizeof(name), "branch%04d", i);
 
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 6, 2024, 10:28 UTC · re: Patrick Steinhardt · lore

[PATCH v5 05/27] refspec: remove global tag refspec structure

We have a global tag refspec structure that is used by both git-clone(1) and git-fetch(1). Initialization of the structure will break once we enable `-Wwrite-strings`, even though the breakage is harmless. While we could just add casts, the structure isn't really required in the first place as we can simply initialize the structures at the respective callsites.

Refactor the code accordingly.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c |  8 ++++++--
 builtin/fetch.c | 11 ++++++++---
 refspec.c       | 13 -------------
 refspec.h       |  1 -
 4 files changed, 14 insertions(+), 19 deletions(-)
Show changes to 4 files +14 −19

builtin/clone.c, builtin/fetch.c, refspec.c, refspec.h

diff --git a/builtin/clone.c b/builtin/clone.c
index 92ab7d7165..bde1d284a2 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -523,6 +523,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
 	struct ref *head = copy_ref(find_ref_by_name(refs, "HEAD"));
 	struct ref *local_refs = head;
 	struct ref **tail = head ? &head->next : &local_refs;
+	struct refspec_item tag_refspec;
+
+	refspec_item_init(&tag_refspec, TAG_REFSPEC, 0);
 
 	if (option_single_branch) {
 		struct ref *remote_head = NULL;
@@ -545,7 +548,7 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
 					      &tail, 0);
 
 			/* if --branch=tag, pull the requested tag explicitly */
-			get_fetch_map(remote_head, tag_refspec, &tail, 0);
+			get_fetch_map(remote_head, &tag_refspec, &tail, 0);
 		}
 		free_refs(remote_head);
 	} else {
@@ -555,8 +558,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
 	}
 
 	if (!option_mirror && !option_single_branch && !option_no_tags)
-		get_fetch_map(refs, tag_refspec, &tail, 0);
+		get_fetch_map(refs, &tag_refspec, &tail, 0);
 
+	refspec_item_clear(&tag_refspec);
 	return local_refs;
 }
 
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 75255dc600..06b60867f5 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -582,11 +582,16 @@ static struct ref *get_ref_map(struct remote *remote,
 		}
 	}
 
-	if (tags == TAGS_SET)
+	if (tags == TAGS_SET) {
+		struct refspec_item tag_refspec;
+
 		/* also fetch all tags */
-		get_fetch_map(remote_refs, tag_refspec, &tail, 0);
-	else if (tags == TAGS_DEFAULT && *autotags)
+		refspec_item_init(&tag_refspec, TAG_REFSPEC, 0);
+		get_fetch_map(remote_refs, &tag_refspec, &tail, 0);
+		refspec_item_clear(&tag_refspec);
+	} else if (tags == TAGS_DEFAULT && *autotags) {
 		find_non_local_tags(remote_refs, NULL, &ref_map, &tail);
+	}
 
 	/* Now append any refs to be updated opportunistically: */
 	*tail = orefs;
diff --git a/refspec.c b/refspec.c
index d60932f4de..1df5de6c2f 100644
--- a/refspec.c
+++ b/refspec.c
@@ -7,19 +7,6 @@
 #include "refspec.h"
 #include "strbuf.h"
 
-static struct refspec_item s_tag_refspec = {
-	.force = 0,
-	.pattern = 1,
-	.matching = 0,
-	.exact_sha1 = 0,
-	.negative = 0,
-	.src = "refs/tags/*",
-	.dst = "refs/tags/*",
-};
-
-/* See TAG_REFSPEC for the string version */
-const struct refspec_item *tag_refspec = &s_tag_refspec;
-
 /*
  * Parses the provided refspec 'refspec' and populates the refspec_item 'item'.
  * Returns 1 if successful and 0 if the refspec is invalid.
diff --git a/refspec.h b/refspec.h
index 8c0c446993..754be45cee 100644
--- a/refspec.h
+++ b/refspec.h
@@ -2,7 +2,6 @@
 #define REFSPEC_H
 
 #define TAG_REFSPEC "refs/tags/*:refs/tags/*"
-extern const struct refspec_item *tag_refspec;
 
 /**
  * A struct refspec_item holds the parsed interpretation of a refspec.  If it
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 6, 2024, 10:28 UTC · re: Patrick Steinhardt · lore

[PATCH v5 06/27] builtin/remote: cast away constness in `get_head_names()`

In `get_head_names()`, we assign the "refs/heads/*" string constant to `struct refspec_item::{src,dst}`, which are both non-constant pointers. Ideally, we'd refactor the code such that both of these fields were constant. But `struct refspec_item` is used for two different usecases with conflicting requirements:

  - To query for a source or destination based on the given refspec. The
    caller either sets `src` or `dst` as the branch that we want to
    search for, and the respective other field gets populated. The
    fields should be constant when being used as a query parameter,
    which is owned by the caller, and non-constant when being used as an
    out parameter, which is owned by the refspec item. This is is
    contradictory in itself already.
  - To store refspec items with their respective source and destination
    branches, in which case both fields should be owned by the struct.

Ideally, we'd split up this interface to clearly separate between querying and storing, which would enable us to clarify lifetimes of the strings. This would be a much bigger undertaking though.

Instead, accept the status quo for now and cast away the constness of the source and destination patterns. We know that those are not being written to or freed, so while this is ugly it certainly is fine for now.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/remote.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)
Show changes to builtin/remote.c +6 −6
diff --git a/builtin/remote.c b/builtin/remote.c
index d52b1c0e10..b44f580b8c 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -493,12 +493,13 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat
 {
 	struct ref *ref, *matches;
 	struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;
-	struct refspec_item refspec;
+	struct refspec_item refspec = {
+		.force = 0,
+		.pattern = 1,
+		.src = (char *) "refs/heads/*",
+		.dst = (char *) "refs/heads/*",
+	};
 
-	memset(&refspec, 0, sizeof(refspec));
-	refspec.force = 0;
-	refspec.pattern = 1;
-	refspec.src = refspec.dst = "refs/heads/*";
 	get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
 	matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
 				    fetch_map, 1);
@@ -507,7 +508,6 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat
 
 	free_refs(fetch_map);
 	free_refs(matches);
-
 	return 0;
 }
 
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 6, 2024, 10:28 UTC · re: Patrick Steinhardt · lore

[PATCH v5 07/27] diff: cast string constant in `fill_textconv()`

The `fill_textconv()` function is responsible for converting an input file with a textconv driver, which is then passed to the caller. Weirdly though, the function also handles the case where there is no textconv driver at all. In that case, it will return either the contents of the populated filespec, or an empty string if the filespec is invalid.

These two cases have differing memory ownership semantics. When there is a textconv driver, then the result is an allocated string. Otherwise, the result is either a string constant or owned by the filespec struct. All callers are in fact aware of this weirdness and only end up freeing the output buffer when they had a textconv driver.

Ideally, we'd split up this interface to only perform the conversion via the textconv driver, and BUG in case the caller didn't provide one. This would make memory ownership semantics much more straight forward. For now though, let's simply cast the empty string constant to `char *` to avoid a warning with `-Wwrite-strings`. This is equivalent to the same cast that we already have in `fill_mmfile()`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 diff.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to diff.c +1 −1
diff --git a/diff.c b/diff.c
index ffd867ef6c..cecda216cf 100644
--- a/diff.c
+++ b/diff.c
@@ -7235,7 +7235,7 @@ size_t fill_textconv(struct repository *r,
 
 	if (!driver) {
 		if (!DIFF_FILE_VALID(df)) {
-			*outbuf = "";
+			*outbuf = (char *) "";
 			return 0;
 		}
 		if (diff_populate_filespec(r, df, NULL))
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 6, 2024, 10:28 UTC · re: Patrick Steinhardt · lore

[PATCH v5 08/27] line-log: stop assigning string constant to file parent buffer

Stop assigning a string constant to the file parent buffer and instead assign an allocated string. While the code is fine in practice, it will break once we compile with `-Wwrite-strings`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 line-log.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
Show changes to line-log.c +3 −1
diff --git a/line-log.c b/line-log.c
index 8ff6ccb772..bd3e663c24 100644
--- a/line-log.c
+++ b/line-log.c
@@ -1032,6 +1032,7 @@ static int process_diff_filepair(struct rev_info *rev,
 	struct range_set tmp;
 	struct diff_ranges diff;
 	mmfile_t file_parent, file_target;
+	char *parent_data_to_free = NULL;
 
 	assert(pair->two->path);
 	while (rg) {
@@ -1056,7 +1057,7 @@ static int process_diff_filepair(struct rev_info *rev,
 		file_parent.ptr = pair->one->data;
 		file_parent.size = pair->one->size;
 	} else {
-		file_parent.ptr = "";
+		file_parent.ptr = parent_data_to_free = xstrdup("");
 		file_parent.size = 0;
 	}
 
@@ -1075,6 +1076,7 @@ static int process_diff_filepair(struct rev_info *rev,
 
 	diff_ranges_release(&diff);
 
+	free(parent_data_to_free);
 	return ((*diff_out)->parent.nr > 0);
 }
 
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 6, 2024, 10:28 UTC · re: Patrick Steinhardt · lore

[PATCH v5 09/27] line-log: always allocate the output prefix

The returned string by `output_prefix()` is sometimes a string constant and sometimes an allocated string. This has been fine until now because we always leak the allocated strings, and thus we never tried to free the string constant.

Fix the code to always return an allocated string and free the returned value at all callsites.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 line-log.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)
Show changes to line-log.c +11 −7
diff --git a/line-log.c b/line-log.c
index bd3e663c24..67c80b39a0 100644
--- a/line-log.c
+++ b/line-log.c
@@ -899,14 +899,12 @@ static void print_line(const char *prefix, char first,
 
 static char *output_prefix(struct diff_options *opt)
 {
-	char *prefix = "";
-
 	if (opt->output_prefix) {
 		struct strbuf *sb = opt->output_prefix(opt, opt->output_prefix_data);
-		prefix = sb->buf;
+		return sb->buf;
+	} else {
+		return xstrdup("");
 	}
-
-	return prefix;
 }
 
 static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *range)
@@ -927,7 +925,7 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang
 	const char *c_context = diff_get_color(opt->use_color, DIFF_CONTEXT);
 
 	if (!pair || !diff)
-		return;
+		goto out;
 
 	if (pair->one->oid_valid)
 		fill_line_ends(rev->diffopt.repo, pair->one, &p_lines, &p_ends);
@@ -1002,8 +1000,10 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang
 				   c_context, c_reset, opt->file);
 	}
 
+out:
 	free(p_ends);
 	free(t_ends);
+	free(prefix);
 }
 
 /*
@@ -1012,7 +1012,11 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang
  */
 static void dump_diff_hacky(struct rev_info *rev, struct line_log_data *range)
 {
-	fprintf(rev->diffopt.file, "%s\n", output_prefix(&rev->diffopt));
+	char *prefix = output_prefix(&rev->diffopt);
+
+	fprintf(rev->diffopt.file, "%s\n", prefix);
+	free(prefix);
+
 	while (range) {
 		dump_diff_hacky_one(rev, range);
 		range = range->next;
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 6, 2024, 10:28 UTC · re: Patrick Steinhardt · lore

[PATCH v5 10/27] entry: refactor how we remove items for delayed checkouts

When finalizing a delayed checkout, we sort out several strings from the passed-in string list by first assigning the empty string to those filters and then calling `string_list_remove_empty_items()`. Assigning the empty string will cause compiler warnings though as the string is a `char *` once we enable `-Wwrite-strings`.

Refactor the code to use a `NULL` pointer with `filter_string_list()` instead to avoid this warning.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 entry.c | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)
Show changes to entry.c +10 −4
diff --git a/entry.c b/entry.c
index b8c257f6f9..f291d8eee6 100644
--- a/entry.c
+++ b/entry.c
@@ -167,6 +167,11 @@ static int remove_available_paths(struct string_list_item *item, void *cb_data)
 	return !available;
 }
 
+static int string_is_not_null(struct string_list_item *item, void *data UNUSED)
+{
+	return !!item->string;
+}
+
 int finish_delayed_checkout(struct checkout *state, int show_progress)
 {
 	int errs = 0;
@@ -189,7 +194,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 			if (!async_query_available_blobs(filter->string, &available_paths)) {
 				/* Filter reported an error */
 				errs = 1;
-				filter->string = "";
+				filter->string = NULL;
 				continue;
 			}
 			if (available_paths.nr <= 0) {
@@ -199,7 +204,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 				 * filter from the list (see
 				 * "string_list_remove_empty_items" call below).
 				 */
-				filter->string = "";
+				filter->string = NULL;
 				continue;
 			}
 
@@ -225,7 +230,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 					 * Do not ask the filter for available blobs,
 					 * again, as the filter is likely buggy.
 					 */
-					filter->string = "";
+					filter->string = NULL;
 					continue;
 				}
 				ce = index_file_exists(state->istate, path->string,
@@ -239,7 +244,8 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 					errs = 1;
 			}
 		}
-		string_list_remove_empty_items(&dco->filters, 0);
+
+		filter_string_list(&dco->filters, 0, string_is_not_null, NULL);
 	}
 	stop_progress(&progress);
 	string_list_clear(&dco->filters, 0);
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 6, 2024, 10:28 UTC · re: Patrick Steinhardt · lore

[PATCH v5 11/27] ident: add casts for fallback name and GECOS

In `xgetpwuid_self()`, we return a fallback identity when it was not possible to look up the current identity. This fallback identity needs to be internal and must never be written to by the calles as specified by getpwuid(3P). As both the `pw_name` and `pw_gecos` fields are marked as non-constant though, it will cause a warning to assign constant strings to them once compiling with `-Wwrite-strings`.

Add explicit casts to avoid the warning.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 ident.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
Show changes to ident.c +2 −2
diff --git a/ident.c b/ident.c
index cc7afdbf81..caf41fb2a9 100644
--- a/ident.c
+++ b/ident.c
@@ -46,9 +46,9 @@ static struct passwd *xgetpwuid_self(int *is_bogus)
 	pw = getpwuid(getuid());
 	if (!pw) {
 		static struct passwd fallback;
-		fallback.pw_name = "unknown";
+		fallback.pw_name = (char *) "unknown";
 #ifndef NO_GECOS_IN_PWENT
-		fallback.pw_gecos = "Unknown";
+		fallback.pw_gecos = (char *) "Unknown";
 #endif
 		pw = &fallback;
 		if (is_bogus)
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 6, 2024, 10:28 UTC · re: Patrick Steinhardt · lore

[PATCH v5 12/27] object-file: mark cached object buffers as const

The buffers of cached objects are never modified, but are still stored as a non-constant pointer. This will cause a compiler warning once we enable the `-Wwrite-strings` compiler warning as we assign an empty constant string when initializing the static `empty_tree` cached object.

Convert the field to be constant. This requires us to shuffle around the code a bit because we memcpy(3P) into the allocated buffer in `pretend_object_file()`. This is easily fixed though by allocating the buffer into a temporary variable first.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 object-file.c | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)
Show changes to object-file.c +9 −4
diff --git a/object-file.c b/object-file.c
index 610b1f465c..6309b24387 100644
--- a/object-file.c
+++ b/object-file.c
@@ -277,7 +277,7 @@ int hash_algo_by_length(int len)
 static struct cached_object {
 	struct object_id oid;
 	enum object_type type;
-	void *buf;
+	const void *buf;
 	unsigned long size;
 } *cached_objects;
 static int cached_object_nr, cached_object_alloc;
@@ -1778,17 +1778,22 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,
 			struct object_id *oid)
 {
 	struct cached_object *co;
+	char *co_buf;
+
+	co_buf = xmalloc(len);
+	memcpy(co_buf, buf, len);
 
 	hash_object_file(the_hash_algo, buf, len, type, oid);
 	if (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||
-	    find_cached_object(oid))
+	    find_cached_object(oid)) {
+		free(co_buf);
 		return 0;
+	}
 	ALLOC_GROW(cached_objects, cached_object_nr + 1, cached_object_alloc);
 	co = &cached_objects[cached_object_nr++];
 	co->size = len;
 	co->type = type;
-	co->buf = xmalloc(len);
-	memcpy(co->buf, buf, len);
+	co->buf = co_buf;
 	oidcpy(&co->oid, oid);
 	return 0;
 }
-- 
2.45.2.409.g7b0defb391.dirty
Junio C Hamano· Jun 6, 2024, 17:54 UTC · re: Patrick Steinhardt · lore

Re: [PATCH v5 12/27] object-file: mark cached object buffers as const

Patrick Steinhardt <ps@pks.im> writes:
Show 8 quoted lines
> @@ -1778,17 +1778,22 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,
>  			struct object_id *oid)
>  {
>  	struct cached_object *co;
> +	char *co_buf;
> +
> +	co_buf = xmalloc(len);
> +	memcpy(co_buf, buf, len);

I do not see why we need to do this so early. The copy is not used or buf gets modified by the call to hash_object_file(), so ...

Show 14 quoted lines
>  	hash_object_file(the_hash_algo, buf, len, type, oid);
>  	if (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||
> -	    find_cached_object(oid))
> +	    find_cached_object(oid)) {
> +		free(co_buf);
>  		return 0;
> +	}
>  	ALLOC_GROW(cached_objects, cached_object_nr + 1, cached_object_alloc);
>  	co = &cached_objects[cached_object_nr++];
>  	co->size = len;
>  	co->type = type;
> -	co->buf = xmalloc(len);
> -	memcpy(co->buf, buf, len);
> +	co->buf = co_buf;

... wouldn't this be a better place to do the "copy to the heap memory pointed by a writable pointer and then point that piece of memory with a read-only pointer" pattern?

>  	oidcpy(&co->oid, oid);
>  	return 0;
>  }
Patrick Steinhardt· Jun 6, 2024, 10:28 UTC · re: Patrick Steinhardt · lore

[PATCH v5 13/27] object-file: make `buf` parameter of `index_mem()` a constant

The `buf` parameter of `index_mem()` is a non-constant string. This will break once we enable `-Wwrite-strings` because we also pass constants from at least one callsite.

Adapt the parameter to be a constant. As we cannot free the buffer without casting now, this also requires us to move the lifetime of the nested buffer around.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 object-file.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)
Show changes to object-file.c +7 −7
diff --git a/object-file.c b/object-file.c
index 6309b24387..2d5bd3a211 100644
--- a/object-file.c
+++ b/object-file.c
@@ -2487,12 +2487,13 @@ static int hash_format_check_report(struct fsck_options *opts UNUSED,
 }
 
 static int index_mem(struct index_state *istate,
-		     struct object_id *oid, void *buf, size_t size,
+		     struct object_id *oid,
+		     const void *buf, size_t size,
 		     enum object_type type,
 		     const char *path, unsigned flags)
 {
+	struct strbuf nbuf = STRBUF_INIT;
 	int ret = 0;
-	int re_allocated = 0;
 	int write_object = flags & HASH_WRITE_OBJECT;
 
 	if (!type)
@@ -2502,11 +2503,10 @@ static int index_mem(struct index_state *istate,
 	 * Convert blobs to git internal format
 	 */
 	if ((type == OBJ_BLOB) && path) {
-		struct strbuf nbuf = STRBUF_INIT;
 		if (convert_to_git(istate, path, buf, size, &nbuf,
 				   get_conv_flags(flags))) {
-			buf = strbuf_detach(&nbuf, &size);
-			re_allocated = 1;
+			buf = nbuf.buf;
+			size = nbuf.len;
 		}
 	}
 	if (flags & HASH_FORMAT_CHECK) {
@@ -2523,8 +2523,8 @@ static int index_mem(struct index_state *istate,
 		ret = write_object_file(buf, size, type, oid);
 	else
 		hash_object_file(the_hash_algo, buf, size, type, oid);
-	if (re_allocated)
-		free(buf);
+
+	strbuf_release(&nbuf);
 	return ret;
 }
 
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 6, 2024, 10:28 UTC · re: Patrick Steinhardt · lore

[PATCH v5 14/27] pretty: add casts for decoration option pointers

The `struct decoration_options` have a prefix and suffix field which are both non-constant, but we assign a constant pointer to them. This is safe to do because we pass them to `format_decorations()`, which never modifies these pointers, and then immediately discard the structure. Add explicit casts to avoid compilation warnings with `-Wwrite-strings`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 pretty.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
Show changes to pretty.c +2 −2
diff --git a/pretty.c b/pretty.c
index ec05db5655..1df9d635fb 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1584,8 +1584,8 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
 	case 'D':
 		{
 			const struct decoration_options opts = {
-				.prefix = "",
-				.suffix = ""
+				.prefix = (char *) "",
+				.suffix = (char *) "",
 			};
 
 			format_decorations(sb, commit, c->auto_color, &opts);
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 6, 2024, 10:28 UTC · re: Patrick Steinhardt · lore

[PATCH v5 15/27] compat/win32: fix const-correctness with string constants

Adjust various places in our Win32 compatibility layer where we are not assigning string constants to `const char *` variables.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 compat/basename.c | 16 ++++++++++++++--
 compat/mingw.c    | 28 ++++++++++++++++------------
 compat/winansi.c  |  2 +-
 3 files changed, 31 insertions(+), 15 deletions(-)
Show changes to 3 files +31 −15

compat/basename.c, compat/mingw.c, compat/winansi.c

diff --git a/compat/basename.c b/compat/basename.c
index 96bd9533b4..c33579ef61 100644
--- a/compat/basename.c
+++ b/compat/basename.c
@@ -10,7 +10,13 @@ char *gitbasename (char *path)
 		skip_dos_drive_prefix(&path);
 
 	if (!path || !*path)
-		return ".";
+		/*
+		 * basename(3P) is mis-specified because it returns a
+		 * non-constant pointer even though it is specified to return a
+		 * pointer to internal memory at times. The cast is a result of
+		 * that.
+		 */
+		return (char *) ".";
 
 	for (base = path; *path; path++) {
 		if (!is_dir_sep(*path))
@@ -34,7 +40,13 @@ char *gitdirname(char *path)
 	int dos_drive_prefix;
 
 	if (!p)
-		return ".";
+		/*
+		 * dirname(3P) is mis-specified because it returns a
+		 * non-constant pointer even though it is specified to return a
+		 * pointer to internal memory at times. The cast is a result of
+		 * that.
+		 */
+		return (char *) ".";
 
 	if ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p)
 		goto dot;
diff --git a/compat/mingw.c b/compat/mingw.c
index 6b06ea540f..d378cd04cb 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -2279,7 +2279,11 @@ struct passwd *getpwuid(int uid)
 	p->pw_name = user_name;
 	p->pw_gecos = get_extended_user_info(NameDisplay);
 	if (!p->pw_gecos)
-		p->pw_gecos = "unknown";
+		/*
+		 * Data returned by getpwuid(3P) is treated as internal and
+		 * must never be written to or freed.
+		 */
+		p->pw_gecos = (char *) "unknown";
 	p->pw_dir = NULL;
 
 	initialized = 1;
@@ -2800,16 +2804,16 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)
 			strbuf_addf(report, "'%s' is on a file system that does "
 				    "not record ownership\n", path);
 		} else if (report) {
-			LPSTR str1, str2, str3, str4, to_free1 = NULL,
-			    to_free3 = NULL, to_local_free2 = NULL,
-			    to_local_free4 = NULL;
+			PCSTR str1, str2, str3, str4;
+			LPSTR to_free1 = NULL, to_free3 = NULL,
+			    to_local_free2 = NULL, to_local_free4 = NULL;
 
-			if (user_sid_to_user_name(sid, &str1))
-				to_free1 = str1;
+			if (user_sid_to_user_name(sid, &to_free1))
+				str1 = to_free1;
 			else
 				str1 = "(inconvertible)";
-			if (ConvertSidToStringSidA(sid, &str2))
-				to_local_free2 = str2;
+			if (ConvertSidToStringSidA(sid, &to_local_free2))
+				str2 = to_local_free2;
 			else
 				str2 = "(inconvertible)";
 
@@ -2822,13 +2826,13 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)
 				str4 = "(invalid)";
 			} else {
 				if (user_sid_to_user_name(current_user_sid,
-							  &str3))
-					to_free3 = str3;
+							  &to_free3))
+					str3 = to_free3;
 				else
 					str3 = "(inconvertible)";
 				if (ConvertSidToStringSidA(current_user_sid,
-							   &str4))
-					to_local_free4 = str4;
+							   &to_local_free4))
+					str4 = to_local_free4;
 				else
 					str4 = "(inconvertible)";
 			}
diff --git a/compat/winansi.c b/compat/winansi.c
index f83610f684..575813bde8 100644
--- a/compat/winansi.c
+++ b/compat/winansi.c
@@ -139,7 +139,7 @@ static void write_console(unsigned char *str, size_t len)
 	/* convert utf-8 to utf-16 */
 	int wlen = xutftowcsn(wbuf, (char*) str, ARRAY_SIZE(wbuf), len);
 	if (wlen < 0) {
-		wchar_t *err = L"[invalid]";
+		const wchar_t *err = L"[invalid]";
 		WriteConsoleW(console, err, wcslen(err), &dummy, NULL);
 		return;
 	}
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 6, 2024, 10:29 UTC · re: Patrick Steinhardt · lore

[PATCH v5 16/27] http: do not assign string constant to non-const field

In `write_accept_language()`, we put all acceptable languages into an array. While all entries in that array are allocated strings, the final entry in that array is a string constant. This is fine because we explicitly skip over the last entry when freeing the array, but will cause warnings once we enable `-Wwrite-strings`.

Adapt the code to also allocate the final entry.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 http.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)
Show changes to http.c +2 −3
diff --git a/http.c b/http.c
index 67cc47d28f..2dea2d03da 100644
--- a/http.c
+++ b/http.c
@@ -1974,7 +1974,7 @@ static void write_accept_language(struct strbuf *buf)
 
 		/* add '*' */
 		REALLOC_ARRAY(language_tags, num_langs + 1);
-		language_tags[num_langs++] = "*"; /* it's OK; this won't be freed */
+		language_tags[num_langs++] = xstrdup("*");
 
 		/* compute decimal_places */
 		for (max_q = 1, decimal_places = 0;
@@ -2004,8 +2004,7 @@ static void write_accept_language(struct strbuf *buf)
 		}
 	}
 
-	/* free language tags -- last one is a static '*' */
-	for (i = 0; i < num_langs - 1; i++)
+	for (i = 0; i < num_langs; i++)
 		free(language_tags[i]);
 	free(language_tags);
 }
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 6, 2024, 10:29 UTC · re: Patrick Steinhardt · lore

[PATCH v5 17/27] parse-options: cast long name for OPTION_ALIAS

We assign the long name for OPTION_ALIAS options to a non-constant value field. We know that the variable will never be written to, but this will cause warnings once we enable `-Wwrite-strings`.

Cast away the constness to be prepared for this change.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 parse-options.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to parse-options.h +1 −1
diff --git a/parse-options.h b/parse-options.h
index bd62e20268..ae15342390 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -355,7 +355,7 @@ struct option {
 	.type = OPTION_ALIAS, \
 	.short_name = (s), \
 	.long_name = (l), \
-	.value = (source_long_name), \
+	.value = (char *)(source_long_name), \
 }
 
 #define OPT_SUBCOMMAND_F(l, v, fn, f) { \
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 6, 2024, 10:29 UTC · re: Patrick Steinhardt · lore

[PATCH v5 18/27] send-pack: always allocate receive status

In `receive_status()`, we record the reason why ref updates have been rejected by the remote via the `remote_status`. But while we allocate the assigned string when a reason was given, we assign a string constant when no reason was given.

This has been working fine so far due to two reasons:
  - We don't ever free the refs in git-send-pack(1)'
  - Remotes always give a reason, at least as implemented by Git proper.

Adapt the code to always allocate the receive status string and free the refs.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/send-pack.c | 2 ++
 send-pack.c         | 2 +-
 2 files changed, 3 insertions(+), 1 deletion(-)
Show changes to 2 files +3 −1

builtin/send-pack.c, send-pack.c

diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index 3df9eaad09..17cae6bbbd 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -336,5 +336,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
 		/* stable plumbing output; do not modify or localize */
 		fprintf(stderr, "Everything up-to-date\n");
 
+	free_refs(remote_refs);
+	free_refs(local_refs);
 	return ret;
 }
diff --git a/send-pack.c b/send-pack.c
index 37f59d4f66..88e96d000b 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -259,7 +259,7 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)
 			if (p)
 				hint->remote_status = xstrdup(p);
 			else
-				hint->remote_status = "failed";
+				hint->remote_status = xstrdup("failed");
 		} else {
 			hint->status = REF_STATUS_OK;
 			hint->remote_status = xstrdup_or_null(p);
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 6, 2024, 10:29 UTC · re: Patrick Steinhardt · lore

[PATCH v5 19/27] remote-curl: avoid assigning string constant to non-const variable

When processing remote options, we split the option line into two by searching for a space. If there is one, we replace the space with '\0', otherwise we implicitly assume that the value is "true" and thus assign a string constant.

As the return value of strchr(3P) weirdly enough is a `char *` even though it gets a `const char *` as input, the assigned-to variable also is a non-constant. This is fine though because the argument is in fact an allocated string, and thus we are allowed to modify it. But this will break once we enable `-Wwrite-strings`.

Refactor the code stop splitting the fields with '\0' altogether. Instead, we can pass the length of the option name to `set_option()` and then use strncmp(3P) instead of strcmp(3P).

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 remote-curl.c | 53 ++++++++++++++++++++++++++-------------------------
 1 file changed, 27 insertions(+), 26 deletions(-)
Show changes to remote-curl.c +27 −26
diff --git a/remote-curl.c b/remote-curl.c
index cae98384da..d0f767df8e 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -58,9 +58,9 @@ struct options {
 static struct options options;
 static struct string_list cas_options = STRING_LIST_INIT_DUP;
 
-static int set_option(const char *name, const char *value)
+static int set_option(const char *name, size_t namelen, const char *value)
 {
-	if (!strcmp(name, "verbosity")) {
+	if (!strncmp(name, "verbosity", namelen)) {
 		char *end;
 		int v = strtol(value, &end, 10);
 		if (value == end || *end)
@@ -68,7 +68,7 @@ static int set_option(const char *name, const char *value)
 		options.verbosity = v;
 		return 0;
 	}
-	else if (!strcmp(name, "progress")) {
+	else if (!strncmp(name, "progress", namelen)) {
 		if (!strcmp(value, "true"))
 			options.progress = 1;
 		else if (!strcmp(value, "false"))
@@ -77,7 +77,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "depth")) {
+	else if (!strncmp(name, "depth", namelen)) {
 		char *end;
 		unsigned long v = strtoul(value, &end, 10);
 		if (value == end || *end)
@@ -85,15 +85,15 @@ static int set_option(const char *name, const char *value)
 		options.depth = v;
 		return 0;
 	}
-	else if (!strcmp(name, "deepen-since")) {
+	else if (!strncmp(name, "deepen-since", namelen)) {
 		options.deepen_since = xstrdup(value);
 		return 0;
 	}
-	else if (!strcmp(name, "deepen-not")) {
+	else if (!strncmp(name, "deepen-not", namelen)) {
 		string_list_append(&options.deepen_not, value);
 		return 0;
 	}
-	else if (!strcmp(name, "deepen-relative")) {
+	else if (!strncmp(name, "deepen-relative", namelen)) {
 		if (!strcmp(value, "true"))
 			options.deepen_relative = 1;
 		else if (!strcmp(value, "false"))
@@ -102,7 +102,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "followtags")) {
+	else if (!strncmp(name, "followtags", namelen)) {
 		if (!strcmp(value, "true"))
 			options.followtags = 1;
 		else if (!strcmp(value, "false"))
@@ -111,7 +111,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "dry-run")) {
+	else if (!strncmp(name, "dry-run", namelen)) {
 		if (!strcmp(value, "true"))
 			options.dry_run = 1;
 		else if (!strcmp(value, "false"))
@@ -120,7 +120,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "check-connectivity")) {
+	else if (!strncmp(name, "check-connectivity", namelen)) {
 		if (!strcmp(value, "true"))
 			options.check_self_contained_and_connected = 1;
 		else if (!strcmp(value, "false"))
@@ -129,7 +129,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "cas")) {
+	else if (!strncmp(name, "cas", namelen)) {
 		struct strbuf val = STRBUF_INIT;
 		strbuf_addstr(&val, "--force-with-lease=");
 		if (*value != '"')
@@ -139,7 +139,7 @@ static int set_option(const char *name, const char *value)
 		string_list_append(&cas_options, val.buf);
 		strbuf_release(&val);
 		return 0;
-	} else if (!strcmp(name, TRANS_OPT_FORCE_IF_INCLUDES)) {
+	} else if (!strncmp(name, TRANS_OPT_FORCE_IF_INCLUDES, namelen)) {
 		if (!strcmp(value, "true"))
 			options.force_if_includes = 1;
 		else if (!strcmp(value, "false"))
@@ -147,7 +147,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "cloning")) {
+	} else if (!strncmp(name, "cloning", namelen)) {
 		if (!strcmp(value, "true"))
 			options.cloning = 1;
 		else if (!strcmp(value, "false"))
@@ -155,7 +155,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "update-shallow")) {
+	} else if (!strncmp(name, "update-shallow", namelen)) {
 		if (!strcmp(value, "true"))
 			options.update_shallow = 1;
 		else if (!strcmp(value, "false"))
@@ -163,7 +163,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "pushcert")) {
+	} else if (!strncmp(name, "pushcert", namelen)) {
 		if (!strcmp(value, "true"))
 			options.push_cert = SEND_PACK_PUSH_CERT_ALWAYS;
 		else if (!strcmp(value, "false"))
@@ -173,7 +173,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "atomic")) {
+	} else if (!strncmp(name, "atomic", namelen)) {
 		if (!strcmp(value, "true"))
 			options.atomic = 1;
 		else if (!strcmp(value, "false"))
@@ -181,7 +181,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "push-option")) {
+	} else if (!strncmp(name, "push-option", namelen)) {
 		if (*value != '"')
 			string_list_append(&options.push_options, value);
 		else {
@@ -192,7 +192,7 @@ static int set_option(const char *name, const char *value)
 						 strbuf_detach(&unquoted, NULL));
 		}
 		return 0;
-	} else if (!strcmp(name, "family")) {
+	} else if (!strncmp(name, "family", namelen)) {
 		if (!strcmp(value, "ipv4"))
 			git_curl_ipresolve = CURL_IPRESOLVE_V4;
 		else if (!strcmp(value, "ipv6"))
@@ -202,16 +202,16 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "from-promisor")) {
+	} else if (!strncmp(name, "from-promisor", namelen)) {
 		options.from_promisor = 1;
 		return 0;
-	} else if (!strcmp(name, "refetch")) {
+	} else if (!strncmp(name, "refetch", namelen)) {
 		options.refetch = 1;
 		return 0;
-	} else if (!strcmp(name, "filter")) {
+	} else if (!strncmp(name, "filter", namelen)) {
 		options.filter = xstrdup(value);
 		return 0;
-	} else if (!strcmp(name, "object-format")) {
+	} else if (!strncmp(name, "object-format", namelen)) {
 		options.object_format = 1;
 		if (strcmp(value, "true"))
 			die(_("unknown value for object-format: %s"), value);
@@ -1588,15 +1588,16 @@ int cmd_main(int argc, const char **argv)
 			parse_push(&buf);
 
 		} else if (skip_prefix(buf.buf, "option ", &arg)) {
-			char *value = strchr(arg, ' ');
+			const char *value = strchrnul(arg, ' ');
+			size_t arglen = value - arg;
 			int result;
 
-			if (value)
-				*value++ = '\0';
+			if (*value)
+				value++; /* skip over SP */
 			else
 				value = "true";
 
-			result = set_option(arg, value);
+			result = set_option(arg, arglen, value);
 			if (!result)
 				printf("ok\n");
 			else if (result < 0)
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 6, 2024, 10:29 UTC · re: Patrick Steinhardt · lore

[PATCH v5 20/27] revision: always store allocated strings in output encoding

The `git_log_output_encoding` variable can be set via the `--encoding=` option. When doing so, we conditionally either assign it to the passed value, or if the value is "none" we assign it the empty string. Depending on which of the both code paths we pick though, the variable may end up being assigned either an allocated string or a string constant.

This is somewhat risky and may easily lead to bugs when a different code path may want to reassign a new value to it, freeing the previous value. We already to this when parsing the "i18n.logoutputencoding" config in `git_default_i18n_config()`. But because the config is typically parsed before we parse command line options this has been fine so far.

Regardless of that, safeguard the code such that the variable always contains an allocated string. While at it, also free the old value in case there was any to plug a potential memory leak.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 revision.c             | 3 ++-
 t/t3900-i18n-commit.sh | 1 +
 t/t3901-i18n-patch.sh  | 1 +
 3 files changed, 4 insertions(+), 1 deletion(-)
Show changes to 3 files +4 −1

revision.c, t/t3900-i18n-commit.sh, t/t3901-i18n-patch.sh

diff --git a/revision.c b/revision.c
index 7ddf0f151a..2ee6886078 100644
--- a/revision.c
+++ b/revision.c
@@ -2650,10 +2650,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 	} else if (!strcmp(arg, "--invert-grep")) {
 		revs->grep_filter.no_body_match = 1;
 	} else if ((argcount = parse_long_opt("encoding", argv, &optarg))) {
+		free(git_log_output_encoding);
 		if (strcmp(optarg, "none"))
 			git_log_output_encoding = xstrdup(optarg);
 		else
-			git_log_output_encoding = "";
+			git_log_output_encoding = xstrdup("");
 		return argcount;
 	} else if (!strcmp(arg, "--reverse")) {
 		revs->reverse ^= 1;
diff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh
index f27d09cfd9..db7b403bc1 100755
--- a/t/t3900-i18n-commit.sh
+++ b/t/t3900-i18n-commit.sh
@@ -5,6 +5,7 @@
 
 test_description='commit and log output encodings'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 compare_with () {
diff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh
index 4b37f78829..5f0b9afc3f 100755
--- a/t/t3901-i18n-patch.sh
+++ b/t/t3901-i18n-patch.sh
@@ -8,6 +8,7 @@ test_description='i18n settings and format-patch | am pipe'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 check_encoding () {
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 6, 2024, 10:29 UTC · re: Patrick Steinhardt · lore

[PATCH v5 21/27] mailmap: always store allocated strings in mailmap blob

Same as with the preceding commit, the `git_mailmap_blob` may sometimes contain an allocated string and sometimes it may contain a string constant. This is risky and can easily lead to bugs in case the variable is getting re-assigned, where the code may then try to free the previous value to avoid memory leaks.

Safeguard the code by always storing allocated strings in the variable.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 mailmap.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to mailmap.c +1 −1
diff --git a/mailmap.c b/mailmap.c
index b2efe29b3d..3d1e092fef 100644
--- a/mailmap.c
+++ b/mailmap.c
@@ -216,7 +216,7 @@ int read_mailmap(struct string_list *map)
 	map->cmp = namemap_cmp;
 
 	if (!git_mailmap_blob && is_bare_repository())
-		git_mailmap_blob = "HEAD:.mailmap";
+		git_mailmap_blob = xstrdup("HEAD:.mailmap");
 
 	if (!startup_info->have_repository || !is_bare_repository())
 		err |= read_mailmap_file(map, ".mailmap",
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 6, 2024, 10:29 UTC · re: Patrick Steinhardt · lore

[PATCH v5 22/27] imap-send: drop global `imap_server_conf` variable

In "imap-send.c", we have a global `sturct imap_server_conf` variable that keeps track of the configuration of the IMAP server. This variable is being populated mostly via the Git configuration.

Refactor the code to allocate the structure on the stack instead of having it globally. This change allows us to track its lifetime more closely.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 imap-send.c | 57 ++++++++++++++++++++++++++++-------------------------
 1 file changed, 30 insertions(+), 27 deletions(-)
Show changes to imap-send.c +30 −27
diff --git a/imap-send.c b/imap-send.c
index 8b723b34a5..67a7a6c456 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -82,10 +82,6 @@ struct imap_server_conf {
 	char *auth_method;
 };
 
-static struct imap_server_conf server = {
-	.ssl_verify = 1,
-};
-
 struct imap_socket {
 	int fd[2];
 	SSL *ssl;
@@ -110,6 +106,7 @@ struct imap {
 };
 
 struct imap_store {
+	const struct imap_server_conf *cfg;
 	/* currently open mailbox */
 	const char *name; /* foreign! maybe preset? */
 	int uidvalidity;
@@ -194,8 +191,8 @@ static void socket_perror(const char *func, struct imap_socket *sock, int ret)
 
 #ifdef NO_OPENSSL
 static int ssl_socket_connect(struct imap_socket *sock UNUSED,
-			      int use_tls_only UNUSED,
-			      int verify UNUSED)
+			      const struct imap_server_conf *cfg,
+			      int use_tls_only UNUSED)
 {
 	fprintf(stderr, "SSL requested but SSL support not compiled in\n");
 	return -1;
@@ -250,7 +247,9 @@ static int verify_hostname(X509 *cert, const char *hostname)
 		     cname, hostname);
 }
 
-static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int verify)
+static int ssl_socket_connect(struct imap_socket *sock,
+			      const struct imap_server_conf *cfg,
+			      int use_tls_only)
 {
 #if (OPENSSL_VERSION_NUMBER >= 0x10000000L)
 	const SSL_METHOD *meth;
@@ -279,7 +278,7 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
 	if (use_tls_only)
 		SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
 
-	if (verify)
+	if (cfg->ssl_verify)
 		SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
 
 	if (!SSL_CTX_set_default_verify_paths(ctx)) {
@@ -306,9 +305,9 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
 	 * OpenSSL does not document this function, but the implementation
 	 * returns 1 on success, 0 on failure after calling SSLerr().
 	 */
-	ret = SSL_set_tlsext_host_name(sock->ssl, server.host);
+	ret = SSL_set_tlsext_host_name(sock->ssl, cfg->host);
 	if (ret != 1)
-		warning("SSL_set_tlsext_host_name(%s) failed.", server.host);
+		warning("SSL_set_tlsext_host_name(%s) failed.", cfg->host);
 #endif
 
 	ret = SSL_connect(sock->ssl);
@@ -317,12 +316,12 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
 		return -1;
 	}
 
-	if (verify) {
+	if (cfg->ssl_verify) {
 		/* make sure the hostname matches that of the certificate */
 		cert = SSL_get_peer_certificate(sock->ssl);
 		if (!cert)
 			return error("unable to get peer certificate.");
-		if (verify_hostname(cert, server.host) < 0)
+		if (verify_hostname(cert, cfg->host) < 0)
 			return -1;
 	}
 
@@ -895,7 +894,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)
 	int ret;
 	char *response;
 
-	response = cram(prompt, server.user, server.pass);
+	response = cram(prompt, ctx->cfg->user, ctx->cfg->pass);
 
 	ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));
 	if (ret != strlen(response))
@@ -935,6 +934,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
 
 	CALLOC_ARRAY(ctx, 1);
 
+	ctx->cfg = srvc;
 	ctx->imap = CALLOC_ARRAY(imap, 1);
 	imap->buf.sock.fd[0] = imap->buf.sock.fd[1] = -1;
 	imap->in_progress_append = &imap->in_progress;
@@ -1035,7 +1035,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
 		imap->buf.sock.fd[1] = dup(s);
 
 		if (srvc->use_ssl &&
-		    ssl_socket_connect(&imap->buf.sock, 0, srvc->ssl_verify)) {
+		    ssl_socket_connect(&imap->buf.sock, srvc, 0)) {
 			close(s);
 			goto bail;
 		}
@@ -1068,8 +1068,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
 		if (!srvc->use_ssl && CAP(STARTTLS)) {
 			if (imap_exec(ctx, NULL, "STARTTLS") != RESP_OK)
 				goto bail;
-			if (ssl_socket_connect(&imap->buf.sock, 1,
-					       srvc->ssl_verify))
+			if (ssl_socket_connect(&imap->buf.sock, srvc, 1))
 				goto bail;
 			/* capabilities may have changed, so get the new capabilities */
 			if (imap_exec(ctx, NULL, "CAPABILITY") != RESP_OK)
@@ -1299,23 +1298,24 @@ static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, int *ofs)
 static int git_imap_config(const char *var, const char *val,
 			   const struct config_context *ctx, void *cb)
 {
+	struct imap_server_conf *cfg = cb;
 
 	if (!strcmp("imap.sslverify", var))
-		server.ssl_verify = git_config_bool(var, val);
+		cfg->ssl_verify = git_config_bool(var, val);
 	else if (!strcmp("imap.preformattedhtml", var))
-		server.use_html = git_config_bool(var, val);
+		cfg->use_html = git_config_bool(var, val);
 	else if (!strcmp("imap.folder", var))
-		return git_config_string(&server.folder, var, val);
+		return git_config_string(&cfg->folder, var, val);
 	else if (!strcmp("imap.user", var))
-		return git_config_string(&server.user, var, val);
+		return git_config_string(&cfg->user, var, val);
 	else if (!strcmp("imap.pass", var))
-		return git_config_string(&server.pass, var, val);
+		return git_config_string(&cfg->pass, var, val);
 	else if (!strcmp("imap.tunnel", var))
-		return git_config_string(&server.tunnel, var, val);
+		return git_config_string(&cfg->tunnel, var, val);
 	else if (!strcmp("imap.authmethod", var))
-		return git_config_string(&server.auth_method, var, val);
+		return git_config_string(&cfg->auth_method, var, val);
 	else if (!strcmp("imap.port", var))
-		server.port = git_config_int(var, val, ctx->kvi);
+		cfg->port = git_config_int(var, val, ctx->kvi);
 	else if (!strcmp("imap.host", var)) {
 		if (!val) {
 			return config_error_nonbool(var);
@@ -1324,11 +1324,11 @@ static int git_imap_config(const char *var, const char *val,
 				val += 5;
 			else if (starts_with(val, "imaps:")) {
 				val += 6;
-				server.use_ssl = 1;
+				cfg->use_ssl = 1;
 			}
 			if (starts_with(val, "//"))
 				val += 2;
-			server.host = xstrdup(val);
+			cfg->host = xstrdup(val);
 		}
 	} else
 		return git_default_config(var, val, ctx, cb);
@@ -1497,12 +1497,15 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,
 
 int cmd_main(int argc, const char **argv)
 {
+	struct imap_server_conf server = {
+		.ssl_verify = 1,
+	};
 	struct strbuf all_msgs = STRBUF_INIT;
 	int total;
 	int nongit_ok;
 
 	setup_git_directory_gently(&nongit_ok);
-	git_config(git_imap_config, NULL);
+	git_config(git_imap_config, &server);
 
 	argc = parse_options(argc, (const char **)argv, "", imap_send_options, imap_send_usage, 0);
 
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 6, 2024, 10:29 UTC · re: Patrick Steinhardt · lore

[PATCH v5 23/27] imap-send: fix leaking memory in `imap_server_conf`

We never free any of the config strings that we populate into the `struct imap_server_conf`. Fix this by creating a common exit path where we can free resources.

While at it, drop the unused member `imap_server_conf::name`.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 imap-send.c | 65 ++++++++++++++++++++++++++++++++++-------------------
 1 file changed, 42 insertions(+), 23 deletions(-)
Show changes to imap-send.c +42 −23
diff --git a/imap-send.c b/imap-send.c
index 67a7a6c456..da3e7ec17e 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -69,7 +69,6 @@ static void imap_warn(const char *, ...);
 static char *next_arg(char **);
 
 struct imap_server_conf {
-	const char *name;
 	char *tunnel;
 	char *host;
 	int port;
@@ -1300,23 +1299,28 @@ static int git_imap_config(const char *var, const char *val,
 {
 	struct imap_server_conf *cfg = cb;
 
-	if (!strcmp("imap.sslverify", var))
+	if (!strcmp("imap.sslverify", var)) {
 		cfg->ssl_verify = git_config_bool(var, val);
-	else if (!strcmp("imap.preformattedhtml", var))
+	} else if (!strcmp("imap.preformattedhtml", var)) {
 		cfg->use_html = git_config_bool(var, val);
-	else if (!strcmp("imap.folder", var))
+	} else if (!strcmp("imap.folder", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->folder, var, val);
-	else if (!strcmp("imap.user", var))
+	} else if (!strcmp("imap.user", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->user, var, val);
-	else if (!strcmp("imap.pass", var))
+	} else if (!strcmp("imap.pass", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->pass, var, val);
-	else if (!strcmp("imap.tunnel", var))
+	} else if (!strcmp("imap.tunnel", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->tunnel, var, val);
-	else if (!strcmp("imap.authmethod", var))
+	} else if (!strcmp("imap.authmethod", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->auth_method, var, val);
-	else if (!strcmp("imap.port", var))
+	} else if (!strcmp("imap.port", var)) {
 		cfg->port = git_config_int(var, val, ctx->kvi);
-	else if (!strcmp("imap.host", var)) {
+	} else if (!strcmp("imap.host", var)) {
 		if (!val) {
 			return config_error_nonbool(var);
 		} else {
@@ -1330,8 +1334,9 @@ static int git_imap_config(const char *var, const char *val,
 				val += 2;
 			cfg->host = xstrdup(val);
 		}
-	} else
+	} else {
 		return git_default_config(var, val, ctx, cb);
+	}
 
 	return 0;
 }
@@ -1503,6 +1508,7 @@ int cmd_main(int argc, const char **argv)
 	struct strbuf all_msgs = STRBUF_INIT;
 	int total;
 	int nongit_ok;
+	int ret;
 
 	setup_git_directory_gently(&nongit_ok);
 	git_config(git_imap_config, &server);
@@ -1529,42 +1535,55 @@ int cmd_main(int argc, const char **argv)
 
 	if (!server.folder) {
 		fprintf(stderr, "no imap store specified\n");
-		return 1;
+		ret = 1;
+		goto out;
 	}
 	if (!server.host) {
 		if (!server.tunnel) {
 			fprintf(stderr, "no imap host specified\n");
-			return 1;
+			ret = 1;
+			goto out;
 		}
-		server.host = "tunnel";
+		server.host = xstrdup("tunnel");
 	}
 
 	/* read the messages */
 	if (strbuf_read(&all_msgs, 0, 0) < 0) {
 		error_errno(_("could not read from stdin"));
-		return 1;
+		ret = 1;
+		goto out;
 	}
 
 	if (all_msgs.len == 0) {
 		fprintf(stderr, "nothing to send\n");
-		return 1;
+		ret = 1;
+		goto out;
 	}
 
 	total = count_messages(&all_msgs);
 	if (!total) {
 		fprintf(stderr, "no messages to send\n");
-		return 1;
+		ret = 1;
+		goto out;
 	}
 
 	/* write it to the imap server */
 
 	if (server.tunnel)
-		return append_msgs_to_imap(&server, &all_msgs, total);
-
+		ret = append_msgs_to_imap(&server, &all_msgs, total);
 #ifdef USE_CURL_FOR_IMAP_SEND
-	if (use_curl)
-		return curl_append_msgs_to_imap(&server, &all_msgs, total);
+	else if (use_curl)
+		ret = curl_append_msgs_to_imap(&server, &all_msgs, total);
 #endif
-
-	return append_msgs_to_imap(&server, &all_msgs, total);
+	else
+		ret = append_msgs_to_imap(&server, &all_msgs, total);
+
+out:
+	free(server.tunnel);
+	free(server.host);
+	free(server.folder);
+	free(server.user);
+	free(server.pass);
+	free(server.auth_method);
+	return ret;
 }
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 6, 2024, 10:29 UTC · re: Patrick Steinhardt · lore

[PATCH v5 24/27] builtin/rebase: do not assign default backend to non-constant field

The `struct rebase_options::default_backend` field is a non-constant string, but is being assigned a constant via `REBASE_OPTIONS_INIT`. Fix this by using `xstrdup()` to assign the variable and introduce a new function `rebase_options_release()` that releases memory held by the structure, including the newly-allocated variable.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rebase.c | 25 ++++++++++++++++---------
 1 file changed, 16 insertions(+), 9 deletions(-)
Show changes to builtin/rebase.c +16 −9
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 14d4f0a5e6..adc990b55e 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -135,7 +135,7 @@ struct rebase_options {
 		.type = REBASE_UNSPECIFIED,	  	\
 		.empty = EMPTY_UNSPECIFIED,	  	\
 		.keep_empty = 1,			\
-		.default_backend = "merge",	  	\
+		.default_backend = xstrdup("merge"),  	\
 		.flags = REBASE_NO_QUIET, 		\
 		.git_am_opts = STRVEC_INIT,		\
 		.exec = STRING_LIST_INIT_NODUP,		\
@@ -151,6 +151,19 @@ struct rebase_options {
 		.strategy_opts = STRING_LIST_INIT_NODUP,\
 	}
 
+static void rebase_options_release(struct rebase_options *opts)
+{
+	free(opts->default_backend);
+	free(opts->reflog_action);
+	free(opts->head_name);
+	strvec_clear(&opts->git_am_opts);
+	free(opts->gpg_sign_opt);
+	string_list_clear(&opts->exec, 0);
+	free(opts->strategy);
+	string_list_clear(&opts->strategy_opts, 0);
+	strbuf_release(&opts->git_format_patch_opt);
+}
+
 static struct replay_opts get_replay_opts(const struct rebase_options *opts)
 {
 	struct replay_opts replay = REPLAY_OPTS_INIT;
@@ -796,6 +809,7 @@ static int rebase_config(const char *var, const char *value,
 	}
 
 	if (!strcmp(var, "rebase.backend")) {
+		FREE_AND_NULL(opts->default_backend);
 		return git_config_string(&opts->default_backend, var, value);
 	}
 
@@ -1833,14 +1847,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 cleanup:
 	strbuf_release(&buf);
 	strbuf_release(&revisions);
-	free(options.reflog_action);
-	free(options.head_name);
-	strvec_clear(&options.git_am_opts);
-	free(options.gpg_sign_opt);
-	string_list_clear(&options.exec, 0);
-	free(options.strategy);
-	string_list_clear(&options.strategy_opts, 0);
-	strbuf_release(&options.git_format_patch_opt);
+	rebase_options_release(&options);
 	free(squash_onto_name);
 	free(keep_base_onto_name);
 	return !!ret;
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 6, 2024, 10:29 UTC · re: Patrick Steinhardt · lore

[PATCH v5 25/27] builtin/rebase: always store allocated string in `options.strategy`

The `struct rebase_options::strategy` field is a `char *`, but we do end up assigning string constants to it in two cases:

  - When being passed a `--strategy=` option via the command line.
  - When being passed a strategy option via `--strategy-option=`, but
    not a strategy.
This will cause warnings once we enable `-Wwrite-strings`.

Ideally, we'd just convert the field to be a `const char *`. But we also assign to this field via the GIT_TEST_MERGE_ALGORITHM envvar, which we have to strdup(3P) into it.

Instead, refactor the code to make sure that we only ever assign allocated strings to this field.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rebase.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)
Show changes to builtin/rebase.c +7 −7
diff --git a/builtin/rebase.c b/builtin/rebase.c
index adc990b55e..4506bae768 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1061,6 +1061,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 {
 	struct rebase_options options = REBASE_OPTIONS_INIT;
 	const char *branch_name;
+	const char *strategy_opt = NULL;
 	int ret, flags, total_argc, in_progress = 0;
 	int keep_base = 0;
 	int ok_to_skip_pre_rebase = 0;
@@ -1175,7 +1176,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			PARSE_OPT_OPTARG, parse_opt_rebase_merges),
 		OPT_BOOL(0, "fork-point", &options.fork_point,
 			 N_("use 'merge-base --fork-point' to refine upstream")),
-		OPT_STRING('s', "strategy", &options.strategy,
+		OPT_STRING('s', "strategy", &strategy_opt,
 			   N_("strategy"), N_("use the given merge strategy")),
 		OPT_STRING_LIST('X', "strategy-option", &options.strategy_opts,
 				N_("option"),
@@ -1484,13 +1485,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		}
 	}
 
-	if (options.strategy_opts.nr && !options.strategy)
-		options.strategy = "ort";
-
-	if (options.strategy) {
-		options.strategy = xstrdup(options.strategy);
+	if (strategy_opt)
+		options.strategy = xstrdup(strategy_opt);
+	else if (options.strategy_opts.nr && !options.strategy)
+		options.strategy = xstrdup("ort");
+	if (options.strategy)
 		imply_merge(&options, "--strategy");
-	}
 
 	if (options.root && !options.onto_name)
 		imply_merge(&options, "--root without --onto");
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 6, 2024, 10:29 UTC · re: Patrick Steinhardt · lore

[PATCH v5 26/27] builtin/merge: always store allocated strings in `pull_twohead`

The `pull_twohead` configuration may sometimes contain an allocated string, and sometimes it may contain a string constant. Refactor this to instead always store an allocated string such that we can release its resources without risk.

While at it, manage the lifetime of other config strings, as well. Note that we explicitly don't free `cleanup_arg` here. This is because the variable may be assigned a string constant via command line options.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/merge.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)
Show changes to builtin/merge.c +11 −7
diff --git a/builtin/merge.c b/builtin/merge.c
index daed2d4e1e..fb3eb15b89 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -611,17 +611,19 @@ static int git_merge_config(const char *k, const char *v,
 		return 0;
 	}
 
-	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
+	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat")) {
 		show_diffstat = git_config_bool(k, v);
-	else if (!strcmp(k, "merge.verifysignatures"))
+	} else if (!strcmp(k, "merge.verifysignatures")) {
 		verify_signatures = git_config_bool(k, v);
-	else if (!strcmp(k, "pull.twohead"))
+	} else if (!strcmp(k, "pull.twohead")) {
+		FREE_AND_NULL(pull_twohead);
 		return git_config_string(&pull_twohead, k, v);
-	else if (!strcmp(k, "pull.octopus"))
+	} else if (!strcmp(k, "pull.octopus")) {
+		FREE_AND_NULL(pull_octopus);
 		return git_config_string(&pull_octopus, k, v);
-	else if (!strcmp(k, "commit.cleanup"))
+	} else if (!strcmp(k, "commit.cleanup")) {
 		return git_config_string(&cleanup_arg, k, v);
-	else if (!strcmp(k, "merge.ff")) {
+	} else if (!strcmp(k, "merge.ff")) {
 		int boolval = git_parse_maybe_bool(v);
 		if (0 <= boolval) {
 			fast_forward = boolval ? FF_ALLOW : FF_NO;
@@ -1294,7 +1296,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 	if (!pull_twohead) {
 		char *default_strategy = getenv("GIT_TEST_MERGE_ALGORITHM");
 		if (default_strategy && !strcmp(default_strategy, "ort"))
-			pull_twohead = "ort";
+			pull_twohead = xstrdup("ort");
 	}
 
 	init_diff_ui_defaults();
@@ -1793,6 +1795,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 	}
 	strbuf_release(&buf);
 	free(branch_to_free);
+	free(pull_twohead);
+	free(pull_octopus);
 	discard_index(the_repository->index);
 	return ret;
 }
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 6, 2024, 10:29 UTC · re: Patrick Steinhardt · lore

[PATCH v5 27/27] config.mak.dev: enable `-Wwrite-strings` warning

Writing to string constants is undefined behaviour and must be avoided in C. Even so, the compiler does not help us with this by default because those constants are not in fact marked as `const`. This makes it rather easy to accidentally assign a constant to a non-const variable or field and then later on try to either free it or write to it.

Enable `-Wwrite-strings` to catch such mistakes. With this warning enabled, the type of string constants is changed to `const char[]` and will thus cause compiler warnings when being assigned to non-const fields and variables.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 config.mak.dev | 1 +
 1 file changed, 1 insertion(+)
Show changes to config.mak.dev +1 −0
diff --git a/config.mak.dev b/config.mak.dev
index 981304727c..1ce4c70613 100644
--- a/config.mak.dev
+++ b/config.mak.dev
@@ -37,6 +37,7 @@ DEVELOPER_CFLAGS += -Wpointer-arith
 DEVELOPER_CFLAGS += -Wstrict-prototypes
 DEVELOPER_CFLAGS += -Wunused
 DEVELOPER_CFLAGS += -Wvla
+DEVELOPER_CFLAGS += -Wwrite-strings
 DEVELOPER_CFLAGS += -fno-common
 
 ifneq ($(filter clang4,$(COMPILER_FEATURES)),)
-- 
2.45.2.409.g7b0defb391.dirty
Patrick Steinhardt· Jun 7, 2024, 06:37 UTC · re: Patrick Steinhardt · lore

[PATCH v6 00/27] Compile with `-Wwrite-strings`

Hi,

this is v6 of my patch series that starts to compile our codebase with `-Wwrite-strings`. This warning turns the type of string constants from `char []` to `const char []` and should thus help us to detect cases where we may accidentally write to or free such a string constant.

The only change is in patch 12, where we now allocate a buffer later in time to avoid a useless allocation and the need for a call to free.

Patrick
Patrick Steinhardt (27):
  global: improve const correctness when assigning string constants
  global: convert intentionally-leaking config strings to consts
  refs/reftable: stop micro-optimizing refname allocations on copy
  reftable: cast away constness when assigning constants to records
  refspec: remove global tag refspec structure
  builtin/remote: cast away constness in `get_head_names()`
  diff: cast string constant in `fill_textconv()`
  line-log: stop assigning string constant to file parent buffer
  line-log: always allocate the output prefix
  entry: refactor how we remove items for delayed checkouts
  ident: add casts for fallback name and GECOS
  object-file: mark cached object buffers as const
  object-file: make `buf` parameter of `index_mem()` a constant
  pretty: add casts for decoration option pointers
  compat/win32: fix const-correctness with string constants
  http: do not assign string constant to non-const field
  parse-options: cast long name for OPTION_ALIAS
  send-pack: always allocate receive status
  remote-curl: avoid assigning string constant to non-const variable
  revision: always store allocated strings in output encoding
  mailmap: always store allocated strings in mailmap blob
  imap-send: drop global `imap_server_conf` variable
  imap-send: fix leaking memory in `imap_server_conf`
  builtin/rebase: do not assign default backend to non-constant field
  builtin/rebase: always store allocated string in `options.strategy`
  builtin/merge: always store allocated strings in `pull_twohead`
  config.mak.dev: enable `-Wwrite-strings` warning
 builtin/bisect.c             |   3 +-
 builtin/blame.c              |   2 +-
 builtin/bugreport.c          |   2 +-
 builtin/check-ignore.c       |   4 +-
 builtin/clone.c              |  14 ++--
 builtin/commit.c             |   6 +-
 builtin/diagnose.c           |   2 +-
 builtin/fetch.c              |  11 ++-
 builtin/log.c                |   2 +-
 builtin/mailsplit.c          |   4 +-
 builtin/merge.c              |  18 +++--
 builtin/pull.c               |  52 +++++++-------
 builtin/rebase.c             |  39 ++++++-----
 builtin/receive-pack.c       |   4 +-
 builtin/remote.c             |  12 ++--
 builtin/revert.c             |   2 +-
 builtin/send-pack.c          |   2 +
 compat/basename.c            |  16 ++++-
 compat/mingw.c               |  28 ++++----
 compat/regex/regcomp.c       |   2 +-
 compat/winansi.c             |   2 +-
 config.mak.dev               |   1 +
 diff.c                       |   6 +-
 diffcore-rename.c            |   6 +-
 entry.c                      |  14 ++--
 fmt-merge-msg.c              |   2 +-
 fsck.c                       |   2 +-
 fsck.h                       |   2 +-
 gpg-interface.c              |   6 +-
 http-backend.c               |   2 +-
 http.c                       |   5 +-
 ident.c                      |   4 +-
 imap-send.c                  | 130 ++++++++++++++++++++---------------
 line-log.c                   |  22 +++---
 mailmap.c                    |   2 +-
 merge-ll.c                   |  11 ++-
 object-file.c                |  22 +++---
 parse-options.h              |   2 +-
 pretty.c                     |   6 +-
 refs.c                       |   2 +-
 refs.h                       |   2 +-
 refs/reftable-backend.c      |  28 ++++----
 refspec.c                    |  13 ----
 refspec.h                    |   1 -
 reftable/basics.c            |  15 ++--
 reftable/basics.h            |   4 +-
 reftable/basics_test.c       |   4 +-
 reftable/block_test.c        |   2 +-
 reftable/merged_test.c       |  44 ++++++------
 reftable/readwrite_test.c    |  32 ++++-----
 reftable/record.c            |   6 +-
 reftable/stack.c             |  10 +--
 reftable/stack_test.c        |  56 +++++++--------
 remote-curl.c                |  53 +++++++-------
 revision.c                   |   3 +-
 run-command.c                |   2 +-
 send-pack.c                  |   2 +-
 t/helper/test-hashmap.c      |   3 +-
 t/helper/test-json-writer.c  |  10 +--
 t/helper/test-regex.c        |   4 +-
 t/helper/test-rot13-filter.c |   5 +-
 t/t3900-i18n-commit.sh       |   1 +
 t/t3901-i18n-patch.sh        |   1 +
 t/unit-tests/t-strbuf.c      |  10 +--
 trailer.c                    |   2 +-
 userdiff.c                   |  10 +--
 userdiff.h                   |  12 ++--
 wt-status.c                  |   2 +-
 68 files changed, 444 insertions(+), 367 deletions(-)
Range-diff against v5:
 1:  e01fde88fe =  1:  ba50d96081 global: improve const correctness when assigning string constants
 2:  92cb0b28c6 =  2:  4769bdb893 global: convert intentionally-leaking config strings to consts
 3:  379145478c =  3:  90613bd582 refs/reftable: stop micro-optimizing refname allocations on copy
 4:  d0a2a2f6c5 =  4:  b126783ba3 reftable: cast away constness when assigning constants to records
 5:  ead27d3d97 =  5:  d10566e0a9 refspec: remove global tag refspec structure
 6:  7cb5df9182 =  6:  e9b8f9eaef builtin/remote: cast away constness in `get_head_names()`
 7:  6e631a9ea4 =  7:  8ea984128d diff: cast string constant in `fill_textconv()`
 8:  ac164651a3 =  8:  777b93a89a line-log: stop assigning string constant to file parent buffer
 9:  b717af02f0 =  9:  527533540e line-log: always allocate the output prefix
10:  b46dd3210d = 10:  4d0b8dcbaf entry: refactor how we remove items for delayed checkouts
11:  030dbd0288 = 11:  0d36b6e5d7 ident: add casts for fallback name and GECOS
12:  5cd014c22c ! 12:  bfd632fea7 object-file: mark cached object buffers as const
    @@ object-file.c: int pretend_object_file(void *buf, unsigned long len, enum object
      {
      	struct cached_object *co;
     +	char *co_buf;
    -+
    -+	co_buf = xmalloc(len);
    -+	memcpy(co_buf, buf, len);
      
      	hash_object_file(the_hash_algo, buf, len, type, oid);
      	if (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||
    --	    find_cached_object(oid))
    -+	    find_cached_object(oid)) {
    -+		free(co_buf);
    - 		return 0;
    -+	}
    - 	ALLOC_GROW(cached_objects, cached_object_nr + 1, cached_object_alloc);
    +@@ object-file.c: int pretend_object_file(void *buf, unsigned long len, enum object_type type,
      	co = &cached_objects[cached_object_nr++];
      	co->size = len;
      	co->type = type;
     -	co->buf = xmalloc(len);
     -	memcpy(co->buf, buf, len);
    ++	co_buf = xmalloc(len);
    ++	memcpy(co_buf, buf, len);
     +	co->buf = co_buf;
      	oidcpy(&co->oid, oid);
      	return 0;
13:  69d904ddce = 13:  4770470a84 object-file: make `buf` parameter of `index_mem()` a constant
14:  ed8f07aa59 = 14:  02e79d030b pretty: add casts for decoration option pointers
15:  5953ae1dac = 15:  57402de20a compat/win32: fix const-correctness with string constants
16:  c80f6eff8c = 16:  71c83468f1 http: do not assign string constant to non-const field
17:  3afd012a88 = 17:  f3e02df0ca parse-options: cast long name for OPTION_ALIAS
18:  527755b648 = 18:  a46708b898 send-pack: always allocate receive status
19:  4598592d2f = 19:  99c88897be remote-curl: avoid assigning string constant to non-const variable
20:  38fcea2845 = 20:  03556233d6 revision: always store allocated strings in output encoding
21:  f990bbeb85 = 21:  2bb96449e2 mailmap: always store allocated strings in mailmap blob
22:  fff2379832 = 22:  8059e013a1 imap-send: drop global `imap_server_conf` variable
23:  9ab84e459a = 23:  04c9cfd34d imap-send: fix leaking memory in `imap_server_conf`
24:  81c69da2e8 = 24:  b2b4a01a2e builtin/rebase: do not assign default backend to non-constant field
25:  6819bf6116 = 25:  09980d211b builtin/rebase: always store allocated string in `options.strategy`
26:  a1d2149429 = 26:  d5dc1453f2 builtin/merge: always store allocated strings in `pull_twohead`
27:  c714b67199 = 27:  2c051d1fe6 config.mak.dev: enable `-Wwrite-strings` warning
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:37 UTC · re: Patrick Steinhardt · lore

[PATCH v6 01/27] global: improve const correctness when assigning string constants

We're about to enable `-Wwrite-strings`, which changes the type of string constants to `const char[]`. Fix various sites where we assign such constants to non-const variables.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/bisect.c             |  3 ++-
 builtin/blame.c              |  2 +-
 builtin/bugreport.c          |  2 +-
 builtin/check-ignore.c       |  4 +--
 builtin/clone.c              |  6 ++---
 builtin/commit.c             |  6 ++---
 builtin/diagnose.c           |  2 +-
 builtin/log.c                |  2 +-
 builtin/mailsplit.c          |  4 +--
 builtin/pull.c               | 52 ++++++++++++++++++------------------
 builtin/receive-pack.c       |  4 +--
 builtin/revert.c             |  2 +-
 compat/regex/regcomp.c       |  2 +-
 diff.c                       |  4 +--
 diffcore-rename.c            |  6 ++---
 fmt-merge-msg.c              |  2 +-
 fsck.c                       |  2 +-
 fsck.h                       |  2 +-
 gpg-interface.c              |  2 +-
 http-backend.c               |  2 +-
 imap-send.c                  |  6 ++---
 pretty.c                     |  2 +-
 refs.c                       |  2 +-
 refs.h                       |  2 +-
 reftable/basics.c            | 15 +++++------
 reftable/basics.h            |  4 +--
 reftable/basics_test.c       |  4 +--
 reftable/record.c            |  6 ++---
 reftable/stack.c             | 10 ++++---
 reftable/stack_test.c        |  8 +++---
 run-command.c                |  2 +-
 t/helper/test-hashmap.c      |  3 ++-
 t/helper/test-json-writer.c  | 10 +++----
 t/helper/test-regex.c        |  4 +--
 t/helper/test-rot13-filter.c |  5 ++--
 t/unit-tests/t-strbuf.c      | 10 ++++---
 trailer.c                    |  2 +-
 wt-status.c                  |  2 +-
 38 files changed, 106 insertions(+), 102 deletions(-)
Show changes to 38 files +106 −102

builtin/bisect.c, builtin/blame.c, builtin/bugreport.c, builtin/check-ignore.c, builtin/clone.c, builtin/commit.c, builtin/diagnose.c, builtin/log.c, builtin/mailsplit.c, builtin/pull.c, builtin/receive-pack.c, builtin/revert.c, compat/regex/regcomp.c, diff.c, diffcore-rename.c, fmt-merge-msg.c, fsck.c, fsck.h, gpg-interface.c, http-backend.c, imap-send.c, pretty.c, refs.c, refs.h, reftable/basics.c, reftable/basics.h, reftable/basics_test.c, reftable/record.c, reftable/stack.c, reftable/stack_test.c, run-command.c, t/helper/test-hashmap.c, t/helper/test-json-writer.c, t/helper/test-regex.c, t/helper/test-rot13-filter.c, t/unit-tests/t-strbuf.c, trailer.c, wt-status.c

diff --git a/builtin/bisect.c b/builtin/bisect.c
index a58432b9d9..dabce9b542 100644
--- a/builtin/bisect.c
+++ b/builtin/bisect.c
@@ -262,7 +262,8 @@ static int bisect_reset(const char *commit)
 	return bisect_clean_state();
 }
 
-static void log_commit(FILE *fp, char *fmt, const char *state,
+static void log_commit(FILE *fp,
+		       const char *fmt, const char *state,
 		       struct commit *commit)
 {
 	struct pretty_print_context pp = {0};
diff --git a/builtin/blame.c b/builtin/blame.c
index 838cd476be..98c7629b6a 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -134,7 +134,7 @@ static void get_ac_line(const char *inbuf, const char *what,
 {
 	struct ident_split ident;
 	size_t len, maillen, namelen;
-	char *tmp, *endp;
+	const char *tmp, *endp;
 	const char *namebuf, *mailbuf;
 
 	tmp = strstr(inbuf, what);
diff --git a/builtin/bugreport.c b/builtin/bugreport.c
index 25f860a0d9..b3cc77af53 100644
--- a/builtin/bugreport.c
+++ b/builtin/bugreport.c
@@ -107,7 +107,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix)
 	struct tm tm;
 	enum diagnose_mode diagnose = DIAGNOSE_NONE;
 	char *option_output = NULL;
-	char *option_suffix = "%Y-%m-%d-%H%M";
+	const char *option_suffix = "%Y-%m-%d-%H%M";
 	const char *user_relative_path = NULL;
 	char *prefixed_filename;
 	size_t output_path_len;
diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c
index 6c43430ec4..2bda6a1d46 100644
--- a/builtin/check-ignore.c
+++ b/builtin/check-ignore.c
@@ -35,8 +35,8 @@ static const struct option check_ignore_options[] = {
 
 static void output_pattern(const char *path, struct path_pattern *pattern)
 {
-	char *bang  = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE)  ? "!" : "";
-	char *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? "/" : "";
+	const char *bang  = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE)  ? "!" : "";
+	const char *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? "/" : "";
 	if (!nul_term_line) {
 		if (!verbose) {
 			write_name_quoted(path, stdout, '\n');
diff --git a/builtin/clone.c b/builtin/clone.c
index 23993b905b..92ab7d7165 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -71,7 +71,7 @@ static char *option_branch = NULL;
 static struct string_list option_not = STRING_LIST_INIT_NODUP;
 static const char *real_git_dir;
 static const char *ref_format;
-static char *option_upload_pack = "git-upload-pack";
+static const char *option_upload_pack = "git-upload-pack";
 static int option_verbosity;
 static int option_progress = -1;
 static int option_sparse_checkout;
@@ -177,8 +177,8 @@ static struct option builtin_clone_options[] = {
 
 static const char *get_repo_path_1(struct strbuf *path, int *is_bundle)
 {
-	static char *suffix[] = { "/.git", "", ".git/.git", ".git" };
-	static char *bundle_suffix[] = { ".bundle", "" };
+	static const char *suffix[] = { "/.git", "", ".git/.git", ".git" };
+	static const char *bundle_suffix[] = { ".bundle", "" };
 	size_t baselen = path->len;
 	struct stat st;
 	int i;
diff --git a/builtin/commit.c b/builtin/commit.c
index f53e7e86ff..75c741173e 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,7 +113,7 @@ static char *template_file;
  * the commit message and/or authorship.
  */
 static const char *author_message, *author_message_buffer;
-static char *edit_message, *use_message;
+static const char *edit_message, *use_message;
 static char *fixup_message, *fixup_commit, *squash_message;
 static const char *fixup_prefix;
 static int all, also, interactive, patch_interactive, only, amend, signoff;
@@ -121,8 +121,8 @@ static int edit_flag = -1; /* unspecified */
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
-static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
-static char *sign_commit, *pathspec_from_file;
+static const char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
+static const char *sign_commit, *pathspec_from_file;
 static struct strvec trailer_args = STRVEC_INIT;
 
 /*
diff --git a/builtin/diagnose.c b/builtin/diagnose.c
index 4f22eb2b55..4857a4395b 100644
--- a/builtin/diagnose.c
+++ b/builtin/diagnose.c
@@ -18,7 +18,7 @@ int cmd_diagnose(int argc, const char **argv, const char *prefix)
 	struct tm tm;
 	enum diagnose_mode mode = DIAGNOSE_STATS;
 	char *option_output = NULL;
-	char *option_suffix = "%Y-%m-%d-%H%M";
+	const char *option_suffix = "%Y-%m-%d-%H%M";
 	char *prefixed_filename;
 
 	const struct option diagnose_options[] = {
diff --git a/builtin/log.c b/builtin/log.c
index 78a247d8a9..b8846a9458 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1283,7 +1283,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids)
 	o2->flags = flags2;
 }
 
-static void gen_message_id(struct rev_info *info, char *base)
+static void gen_message_id(struct rev_info *info, const char *base)
 {
 	struct strbuf buf = STRBUF_INIT;
 	strbuf_addf(&buf, "%s.%"PRItime".git.%s", base,
diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c
index 3af9ddb8ae..fe6dbc5d05 100644
--- a/builtin/mailsplit.c
+++ b/builtin/mailsplit.c
@@ -113,8 +113,8 @@ static int populate_maildir_list(struct string_list *list, const char *path)
 	DIR *dir;
 	struct dirent *dent;
 	char *name = NULL;
-	char *subs[] = { "cur", "new", NULL };
-	char **sub;
+	const char *subs[] = { "cur", "new", NULL };
+	const char **sub;
 	int ret = -1;
 
 	for (sub = subs; *sub; ++sub) {
diff --git a/builtin/pull.c b/builtin/pull.c
index d622202bce..2d0429f14f 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -71,48 +71,48 @@ static const char * const pull_usage[] = {
 
 /* Shared options */
 static int opt_verbosity;
-static char *opt_progress;
+static const char *opt_progress;
 static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
 static int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT;
 
 /* Options passed to git-merge or git-rebase */
 static enum rebase_type opt_rebase = -1;
-static char *opt_diffstat;
-static char *opt_log;
-static char *opt_signoff;
-static char *opt_squash;
-static char *opt_commit;
-static char *opt_edit;
-static char *cleanup_arg;
-static char *opt_ff;
-static char *opt_verify_signatures;
-static char *opt_verify;
+static const char *opt_diffstat;
+static const char *opt_log;
+static const char *opt_signoff;
+static const char *opt_squash;
+static const char *opt_commit;
+static const char *opt_edit;
+static const char *cleanup_arg;
+static const char *opt_ff;
+static const char *opt_verify_signatures;
+static const char *opt_verify;
 static int opt_autostash = -1;
 static int config_autostash;
 static int check_trust_level = 1;
 static struct strvec opt_strategies = STRVEC_INIT;
 static struct strvec opt_strategy_opts = STRVEC_INIT;
-static char *opt_gpg_sign;
+static const char *opt_gpg_sign;
 static int opt_allow_unrelated_histories;
 
 /* Options passed to git-fetch */
-static char *opt_all;
-static char *opt_append;
-static char *opt_upload_pack;
+static const char *opt_all;
+static const char *opt_append;
+static const char *opt_upload_pack;
 static int opt_force;
-static char *opt_tags;
-static char *opt_prune;
-static char *max_children;
+static const char *opt_tags;
+static const char *opt_prune;
+static const char *max_children;
 static int opt_dry_run;
-static char *opt_keep;
-static char *opt_depth;
-static char *opt_unshallow;
-static char *opt_update_shallow;
-static char *opt_refmap;
-static char *opt_ipv4;
-static char *opt_ipv6;
+static const char *opt_keep;
+static const char *opt_depth;
+static const char *opt_unshallow;
+static const char *opt_update_shallow;
+static const char *opt_refmap;
+static const char *opt_ipv4;
+static const char *opt_ipv6;
 static int opt_show_forced_updates = -1;
-static char *set_upstream;
+static const char *set_upstream;
 static struct strvec opt_fetch = STRVEC_INIT;
 
 static struct option pull_options[] = {
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 01c1f04ece..c8d12ee0a7 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1249,7 +1249,7 @@ static int run_proc_receive_hook(struct command *commands,
 	return code;
 }
 
-static char *refuse_unconfigured_deny_msg =
+static const char *refuse_unconfigured_deny_msg =
 	N_("By default, updating the current branch in a non-bare repository\n"
 	   "is denied, because it will make the index and work tree inconsistent\n"
 	   "with what you pushed, and will require 'git reset --hard' to match\n"
@@ -1269,7 +1269,7 @@ static void refuse_unconfigured_deny(void)
 	rp_error("%s", _(refuse_unconfigured_deny_msg));
 }
 
-static char *refuse_unconfigured_deny_delete_current_msg =
+static const char *refuse_unconfigured_deny_delete_current_msg =
 	N_("By default, deleting the current branch is denied, because the next\n"
 	   "'git clone' won't result in any file checked out, causing confusion.\n"
 	   "\n"
diff --git a/builtin/revert.c b/builtin/revert.c
index 53935d2c68..7bf2b4e11d 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -179,7 +179,7 @@ static int run_sequencer(int argc, const char **argv, const char *prefix,
 
 	/* Check for incompatible command line arguments */
 	if (cmd) {
-		char *this_operation;
+		const char *this_operation;
 		if (cmd == 'q')
 			this_operation = "--quit";
 		else if (cmd == 'c')
diff --git a/compat/regex/regcomp.c b/compat/regex/regcomp.c
index 2bc0f1187a..6c5d455e92 100644
--- a/compat/regex/regcomp.c
+++ b/compat/regex/regcomp.c
@@ -848,7 +848,7 @@ init_dfa (re_dfa_t *dfa, size_t pat_len)
 {
   unsigned int table_size;
 #ifndef _LIBC
-  char *codeset_name;
+  const char *codeset_name;
 #endif
 
   memset (dfa, '\0', sizeof (re_dfa_t));
diff --git a/diff.c b/diff.c
index e70301df76..ffd867ef6c 100644
--- a/diff.c
+++ b/diff.c
@@ -3764,7 +3764,7 @@ static void builtin_diff(const char *name_a,
 	return;
 }
 
-static char *get_compact_summary(const struct diff_filepair *p, int is_renamed)
+static const char *get_compact_summary(const struct diff_filepair *p, int is_renamed)
 {
 	if (!is_renamed) {
 		if (p->status == DIFF_STATUS_ADDED) {
@@ -4076,7 +4076,7 @@ static int reuse_worktree_file(struct index_state *istate,
 static int diff_populate_gitlink(struct diff_filespec *s, int size_only)
 {
 	struct strbuf buf = STRBUF_INIT;
-	char *dirty = "";
+	const char *dirty = "";
 
 	/* Are we looking at the work tree? */
 	if (s->dirty_submodule)
diff --git a/diffcore-rename.c b/diffcore-rename.c
index 5a6e2bcac7..0e1adb87df 100644
--- a/diffcore-rename.c
+++ b/diffcore-rename.c
@@ -406,7 +406,7 @@ static const char *get_highest_rename_path(struct strintmap *counts)
 	return highest_destination_dir;
 }
 
-static char *UNKNOWN_DIR = "/";  /* placeholder -- short, illegal directory */
+static const char *UNKNOWN_DIR = "/";  /* placeholder -- short, illegal directory */
 
 static int dir_rename_already_determinable(struct strintmap *counts)
 {
@@ -429,8 +429,8 @@ static int dir_rename_already_determinable(struct strintmap *counts)
 }
 
 static void increment_count(struct dir_rename_info *info,
-			    char *old_dir,
-			    char *new_dir)
+			    const char *old_dir,
+			    const char *new_dir)
 {
 	struct strintmap *counts;
 	struct strmap_entry *e;
diff --git a/fmt-merge-msg.c b/fmt-merge-msg.c
index 7d144b803a..5af63ab5ab 100644
--- a/fmt-merge-msg.c
+++ b/fmt-merge-msg.c
@@ -447,7 +447,7 @@ static void fmt_merge_msg_title(struct strbuf *out,
 				const char *current_branch)
 {
 	int i = 0;
-	char *sep = "";
+	const char *sep = "";
 
 	strbuf_addstr(out, "Merge ");
 	for (i = 0; i < srcs.nr; i++) {
diff --git a/fsck.c b/fsck.c
index 7dff41413e..61cd48aa25 100644
--- a/fsck.c
+++ b/fsck.c
@@ -1231,7 +1231,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size,
 }
 
 int fsck_buffer(const struct object_id *oid, enum object_type type,
-		void *data, unsigned long size,
+		const void *data, unsigned long size,
 		struct fsck_options *options)
 {
 	if (type == OBJ_BLOB)
diff --git a/fsck.h b/fsck.h
index 17fa2dda5d..4f0c4e6479 100644
--- a/fsck.h
+++ b/fsck.h
@@ -202,7 +202,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size,
  * struct.
  */
 int fsck_buffer(const struct object_id *oid, enum object_type,
-		void *data, unsigned long size,
+		const void *data, unsigned long size,
 		struct fsck_options *options);
 
 /*
diff --git a/gpg-interface.c b/gpg-interface.c
index 5193223714..71a9382a61 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -727,7 +727,7 @@ static int git_gpg_config(const char *var, const char *value,
 			  void *cb UNUSED)
 {
 	struct gpg_format *fmt = NULL;
-	char *fmtname = NULL;
+	const char *fmtname = NULL;
 	char *trust;
 	int ret;
 
diff --git a/http-backend.c b/http-backend.c
index 5b65287ac9..5b4dca65ed 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -753,7 +753,7 @@ static int bad_request(struct strbuf *hdr, const struct service_cmd *c)
 
 int cmd_main(int argc UNUSED, const char **argv UNUSED)
 {
-	char *method = getenv("REQUEST_METHOD");
+	const char *method = getenv("REQUEST_METHOD");
 	const char *proto_header;
 	char *dir;
 	struct service_cmd *cmd = NULL;
diff --git a/imap-send.c b/imap-send.c
index a5d1510180..8b723b34a5 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -1215,9 +1215,9 @@ static int imap_store_msg(struct imap_store *ctx, struct strbuf *msg)
 static void wrap_in_html(struct strbuf *msg)
 {
 	struct strbuf buf = STRBUF_INIT;
-	static char *content_type = "Content-Type: text/html;\n";
-	static char *pre_open = "<pre>\n";
-	static char *pre_close = "</pre>\n";
+	static const char *content_type = "Content-Type: text/html;\n";
+	static const char *pre_open = "<pre>\n";
+	static const char *pre_close = "</pre>\n";
 	const char *body = strstr(msg->buf, "\n\n");
 
 	if (!body)
diff --git a/pretty.c b/pretty.c
index 22a81506b7..ec05db5655 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1325,7 +1325,7 @@ int format_set_trailers_options(struct process_trailer_options *opts,
 static size_t parse_describe_args(const char *start, struct strvec *args)
 {
 	struct {
-		char *name;
+		const char *name;
 		enum {
 			DESCRIBE_ARG_BOOL,
 			DESCRIBE_ARG_INTEGER,
diff --git a/refs.c b/refs.c
index 8260c27cde..292e8d947e 100644
--- a/refs.c
+++ b/refs.c
@@ -159,7 +159,7 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref)
 {
 	struct ref_namespace_info *info = &ref_namespace[namespace];
 	if (info->ref_updated)
-		free(info->ref);
+		free((char *)info->ref);
 	info->ref = ref;
 	info->ref_updated = 1;
 }
diff --git a/refs.h b/refs.h
index 34568ee1fb..923f751d18 100644
--- a/refs.h
+++ b/refs.h
@@ -975,7 +975,7 @@ struct ref_store *get_worktree_ref_store(const struct worktree *wt);
  */
 
 struct ref_namespace_info {
-	char *ref;
+	const char *ref;
 	enum decoration_type decoration;
 
 	/*
diff --git a/reftable/basics.c b/reftable/basics.c
index fea711db7e..0058619ca6 100644
--- a/reftable/basics.c
+++ b/reftable/basics.c
@@ -67,9 +67,9 @@ void free_names(char **a)
 	reftable_free(a);
 }
 
-size_t names_length(char **names)
+size_t names_length(const char **names)
 {
-	char **p = names;
+	const char **p = names;
 	while (*p)
 		p++;
 	return p - names;
@@ -102,15 +102,12 @@ void parse_names(char *buf, int size, char ***namesp)
 	*namesp = names;
 }
 
-int names_equal(char **a, char **b)
+int names_equal(const char **a, const char **b)
 {
-	int i = 0;
-	for (; a[i] && b[i]; i++) {
-		if (strcmp(a[i], b[i])) {
+	size_t i = 0;
+	for (; a[i] && b[i]; i++)
+		if (strcmp(a[i], b[i]))
 			return 0;
-		}
-	}
-
 	return a[i] == b[i];
 }
 
diff --git a/reftable/basics.h b/reftable/basics.h
index 523ecd5307..c8fec68d4e 100644
--- a/reftable/basics.h
+++ b/reftable/basics.h
@@ -42,10 +42,10 @@ void free_names(char **a);
 void parse_names(char *buf, int size, char ***namesp);
 
 /* compares two NULL-terminated arrays of strings. */
-int names_equal(char **a, char **b);
+int names_equal(const char **a, const char **b);
 
 /* returns the array size of a NULL-terminated array of strings. */
-size_t names_length(char **names);
+size_t names_length(const char **names);
 
 /* Allocation routines; they invoke the functions set through
  * reftable_set_alloc() */
diff --git a/reftable/basics_test.c b/reftable/basics_test.c
index 997c4d9e01..13bc761817 100644
--- a/reftable/basics_test.c
+++ b/reftable/basics_test.c
@@ -58,8 +58,8 @@ static void test_binsearch(void)
 
 static void test_names_length(void)
 {
-	char *a[] = { "a", "b", NULL };
-	EXPECT(names_length(a) == 2);
+	const char *names[] = { "a", "b", NULL };
+	EXPECT(names_length(names) == 2);
 }
 
 static void test_parse_names_normal(void)
diff --git a/reftable/record.c b/reftable/record.c
index 5506f3e913..a2cba5ef74 100644
--- a/reftable/record.c
+++ b/reftable/record.c
@@ -116,7 +116,7 @@ static int decode_string(struct strbuf *dest, struct string_view in)
 	return start_len - in.len;
 }
 
-static int encode_string(char *str, struct string_view s)
+static int encode_string(const char *str, struct string_view s)
 {
 	struct string_view start = s;
 	int l = strlen(str);
@@ -969,9 +969,9 @@ static int reftable_log_record_decode(void *rec, struct strbuf key,
 	return REFTABLE_FORMAT_ERROR;
 }
 
-static int null_streq(char *a, char *b)
+static int null_streq(const char *a, const char *b)
 {
-	char *empty = "";
+	const char *empty = "";
 	if (!a)
 		a = empty;
 
diff --git a/reftable/stack.c b/reftable/stack.c
index a59ebe038d..09549c51c9 100644
--- a/reftable/stack.c
+++ b/reftable/stack.c
@@ -204,7 +204,8 @@ static struct reftable_reader **stack_copy_readers(struct reftable_stack *st,
 	return cur;
 }
 
-static int reftable_stack_reload_once(struct reftable_stack *st, char **names,
+static int reftable_stack_reload_once(struct reftable_stack *st,
+				      const char **names,
 				      int reuse_open)
 {
 	size_t cur_len = !st->merged ? 0 : st->merged->stack_len;
@@ -222,7 +223,7 @@ static int reftable_stack_reload_once(struct reftable_stack *st, char **names,
 
 	while (*names) {
 		struct reftable_reader *rd = NULL;
-		char *name = *names++;
+		const char *name = *names++;
 
 		/* this is linear; we assume compaction keeps the number of
 		   tables under control so this is not quadratic. */
@@ -354,7 +355,7 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
 				goto out;
 		}
 
-		err = reftable_stack_reload_once(st, names, reuse_open);
+		err = reftable_stack_reload_once(st, (const char **) names, reuse_open);
 		if (!err)
 			break;
 		if (err != REFTABLE_NOT_EXIST_ERROR)
@@ -368,7 +369,8 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
 		err = read_lines(st->list_file, &names_after);
 		if (err < 0)
 			goto out;
-		if (names_equal(names_after, names)) {
+		if (names_equal((const char **) names_after,
+				(const char **) names)) {
 			err = REFTABLE_NOT_EXIST_ERROR;
 			goto out;
 		}
diff --git a/reftable/stack_test.c b/reftable/stack_test.c
index 7889f818d1..07d89b45da 100644
--- a/reftable/stack_test.c
+++ b/reftable/stack_test.c
@@ -83,7 +83,7 @@ static void test_read_file(void)
 	char out[1024] = "line1\n\nline2\nline3";
 	int n, err;
 	char **names = NULL;
-	char *want[] = { "line1", "line2", "line3" };
+	const char *want[] = { "line1", "line2", "line3" };
 	int i = 0;
 
 	EXPECT(fd > 0);
@@ -116,9 +116,9 @@ static void test_parse_names(void)
 
 static void test_names_equal(void)
 {
-	char *a[] = { "a", "b", "c", NULL };
-	char *b[] = { "a", "b", "d", NULL };
-	char *c[] = { "a", "b", NULL };
+	const char *a[] = { "a", "b", "c", NULL };
+	const char *b[] = { "a", "b", "d", NULL };
+	const char *c[] = { "a", "b", NULL };
 
 	EXPECT(names_equal(a, a));
 	EXPECT(!names_equal(a, b));
diff --git a/run-command.c b/run-command.c
index 1b821042b4..7600531fb6 100644
--- a/run-command.c
+++ b/run-command.c
@@ -663,7 +663,7 @@ int start_command(struct child_process *cmd)
 	int need_in, need_out, need_err;
 	int fdin[2], fdout[2], fderr[2];
 	int failed_errno;
-	char *str;
+	const char *str;
 
 	/*
 	 * In case of errors we must keep the promise to close FDs
diff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c
index 0eb0b3d49c..2912899558 100644
--- a/t/helper/test-hashmap.c
+++ b/t/helper/test-hashmap.c
@@ -36,7 +36,8 @@ static int test_entry_cmp(const void *cmp_data,
 }
 
 static struct test_entry *alloc_test_entry(unsigned int hash,
-					   char *key, char *value)
+					   const char *key,
+					   const char *value)
 {
 	size_t klen = strlen(key);
 	size_t vlen = strlen(value);
diff --git a/t/helper/test-json-writer.c b/t/helper/test-json-writer.c
index afe393f597..ed52eb76bf 100644
--- a/t/helper/test-json-writer.c
+++ b/t/helper/test-json-writer.c
@@ -174,7 +174,7 @@ static void make_arr4(int pretty)
 	jw_end(&arr4);
 }
 
-static char *expect_nest1 =
+static const char *expect_nest1 =
 	"{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true},\"arr1\":[\"abc\",42,true]}";
 
 static struct json_writer nest1 = JSON_WRITER_INIT;
@@ -195,10 +195,10 @@ static void make_nest1(int pretty)
 	jw_release(&arr1);
 }
 
-static char *expect_inline1 =
+static const char *expect_inline1 =
 	"{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true},\"arr1\":[\"abc\",42,true]}";
 
-static char *pretty_inline1 =
+static const char *pretty_inline1 =
 	("{\n"
 	 "  \"obj1\": {\n"
 	 "    \"a\": \"abc\",\n"
@@ -236,10 +236,10 @@ static void make_inline1(int pretty)
 	jw_end(&inline1);
 }
 
-static char *expect_inline2 =
+static const char *expect_inline2 =
 	"[[1,2],[3,4],{\"a\":\"abc\"}]";
 
-static char *pretty_inline2 =
+static const char *pretty_inline2 =
 	("[\n"
 	 "  [\n"
 	 "    1,\n"
diff --git a/t/helper/test-regex.c b/t/helper/test-regex.c
index 80042eafc2..366bd70976 100644
--- a/t/helper/test-regex.c
+++ b/t/helper/test-regex.c
@@ -20,8 +20,8 @@ static struct reg_flag reg_flags[] = {
 
 static int test_regex_bug(void)
 {
-	char *pat = "[^={} \t]+";
-	char *str = "={}\nfred";
+	const char *pat = "[^={} \t]+";
+	const char *str = "={}\nfred";
 	regex_t r;
 	regmatch_t m[1];
 
diff --git a/t/helper/test-rot13-filter.c b/t/helper/test-rot13-filter.c
index f8d564c622..7e1d9e0ee4 100644
--- a/t/helper/test-rot13-filter.c
+++ b/t/helper/test-rot13-filter.c
@@ -136,7 +136,7 @@ static void free_delay_entries(void)
 	strmap_clear(&delay, 0);
 }
 
-static void add_delay_entry(char *pathname, int count, int requested)
+static void add_delay_entry(const char *pathname, int count, int requested)
 {
 	struct delay_entry *entry = xcalloc(1, sizeof(*entry));
 	entry->count = count;
@@ -189,7 +189,8 @@ static void reply_list_available_blobs_cmd(void)
 static void command_loop(void)
 {
 	for (;;) {
-		char *buf, *output;
+		char *buf;
+		const char *output;
 		char *pathname;
 		struct delay_entry *entry;
 		struct strbuf input = STRBUF_INIT;
diff --git a/t/unit-tests/t-strbuf.c b/t/unit-tests/t-strbuf.c
index de434a4441..6027dafef7 100644
--- a/t/unit-tests/t-strbuf.c
+++ b/t/unit-tests/t-strbuf.c
@@ -2,7 +2,8 @@
 #include "strbuf.h"
 
 /* wrapper that supplies tests with an empty, initialized strbuf */
-static void setup(void (*f)(struct strbuf*, void*), void *data)
+static void setup(void (*f)(struct strbuf*, const void*),
+		  const void *data)
 {
 	struct strbuf buf = STRBUF_INIT;
 
@@ -13,7 +14,8 @@ static void setup(void (*f)(struct strbuf*, void*), void *data)
 }
 
 /* wrapper that supplies tests with a populated, initialized strbuf */
-static void setup_populated(void (*f)(struct strbuf*, void*), char *init_str, void *data)
+static void setup_populated(void (*f)(struct strbuf*, const void*),
+			    const char *init_str, const void *data)
 {
 	struct strbuf buf = STRBUF_INIT;
 
@@ -64,7 +66,7 @@ static void t_dynamic_init(void)
 	strbuf_release(&buf);
 }
 
-static void t_addch(struct strbuf *buf, void *data)
+static void t_addch(struct strbuf *buf, const void *data)
 {
 	const char *p_ch = data;
 	const char ch = *p_ch;
@@ -83,7 +85,7 @@ static void t_addch(struct strbuf *buf, void *data)
 	check_char(buf->buf[buf->len], ==, '\0');
 }
 
-static void t_addstr(struct strbuf *buf, void *data)
+static void t_addstr(struct strbuf *buf, const void *data)
 {
 	const char *text = data;
 	size_t len = strlen(text);
diff --git a/trailer.c b/trailer.c
index 2bcb9ba8f7..72e5136c73 100644
--- a/trailer.c
+++ b/trailer.c
@@ -63,7 +63,7 @@ struct arg_item {
 
 static LIST_HEAD(conf_head);
 
-static char *separators = ":";
+static const char *separators = ":";
 
 static int configured;
 
diff --git a/wt-status.c b/wt-status.c
index ff4be071ca..7912545e4e 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -2408,7 +2408,7 @@ static void wt_porcelain_v2_print_unmerged_entry(
 		int mode;
 		struct object_id oid;
 	} stages[3];
-	char *key;
+	const char *key;
 	char submodule_token[5];
 	char unmerged_prefix = 'u';
 	char eol_char = s->null_termination ? '\0' : '\n';
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:37 UTC · re: Patrick Steinhardt · lore

[PATCH v6 02/27] global: convert intentionally-leaking config strings to consts

There are multiple cases where we intentionally leak config strings:
  - `struct gpg_format` is used to track programs that can be used for
    signing commits, either via gpg(1), gpgsm(1) or ssh-keygen(1). The
    user can override the commands via several config variables. As the
    array is populated once, only, and the struct memers are never
    written to or free'd.
  - `struct ll_merge_driver` is used to track merge drivers. Same as
    with the GPG format, these drivers are populated once and then
    reused. Its data is never written to or free'd, either.
  - `struct userdiff_funcname` and `struct userdiff_driver` can be
    configured via `diff.<driver>.*` to add additional drivers. Again,
    these have a global lifetime and are never written to or free'd.

All of these are intentionally kept alive and are never written to. Furthermore, all of these are being assigned both string constants in some places, and allocated strings in other places. This will cause warnings once we enable `-Wwrite-strings`, so let's mark the respective fields as `const char *` and cast away the constness when assigning those values.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 gpg-interface.c |  4 ++--
 merge-ll.c      | 11 ++++++++---
 userdiff.c      | 10 +++++-----
 userdiff.h      | 12 ++++++------
 4 files changed, 21 insertions(+), 16 deletions(-)
Show changes to 4 files +21 −16

gpg-interface.c, merge-ll.c, userdiff.c, userdiff.h

diff --git a/gpg-interface.c b/gpg-interface.c
index 71a9382a61..5c824aeb25 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -34,7 +34,7 @@ static enum signature_trust_level configured_min_trust_level = TRUST_UNDEFINED;
 
 struct gpg_format {
 	const char *name;
-	char *program;
+	const char *program;
 	const char **verify_args;
 	const char **sigs;
 	int (*verify_signed_buffer)(struct signature_check *sigc,
@@ -783,7 +783,7 @@ static int git_gpg_config(const char *var, const char *value,
 
 	if (fmtname) {
 		fmt = get_format_by_name(fmtname);
-		return git_config_string(&fmt->program, var, value);
+		return git_config_string((char **) &fmt->program, var, value);
 	}
 
 	return 0;
diff --git a/merge-ll.c b/merge-ll.c
index e29b15fa4a..180c19df67 100644
--- a/merge-ll.c
+++ b/merge-ll.c
@@ -27,7 +27,7 @@ typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *,
 
 struct ll_merge_driver {
 	const char *name;
-	char *description;
+	const char *description;
 	ll_merge_fn fn;
 	char *recursive;
 	struct ll_merge_driver *next;
@@ -304,8 +304,13 @@ static int read_merge_config(const char *var, const char *value,
 		ll_user_merge_tail = &(fn->next);
 	}
 
-	if (!strcmp("name", key))
-		return git_config_string(&fn->description, var, value);
+	if (!strcmp("name", key)) {
+		/*
+		 * The description is leaking, but that's okay as we want to
+		 * keep around the merge drivers anyway.
+		 */
+		return git_config_string((char **) &fn->description, var, value);
+	}
 
 	if (!strcmp("driver", key)) {
 		if (!value)
diff --git a/userdiff.c b/userdiff.c
index 82bc76b910..371032a413 100644
--- a/userdiff.c
+++ b/userdiff.c
@@ -399,7 +399,7 @@ static struct userdiff_driver *userdiff_find_by_namelen(const char *name, size_t
 static int parse_funcname(struct userdiff_funcname *f, const char *k,
 		const char *v, int cflags)
 {
-	if (git_config_string(&f->pattern, k, v) < 0)
+	if (git_config_string((char **) &f->pattern, k, v) < 0)
 		return -1;
 	f->cflags = cflags;
 	return 0;
@@ -445,15 +445,15 @@ int userdiff_config(const char *k, const char *v)
 	if (!strcmp(type, "binary"))
 		return parse_tristate(&drv->binary, k, v);
 	if (!strcmp(type, "command"))
-		return git_config_string(&drv->external, k, v);
+		return git_config_string((char **) &drv->external, k, v);
 	if (!strcmp(type, "textconv"))
-		return git_config_string(&drv->textconv, k, v);
+		return git_config_string((char **) &drv->textconv, k, v);
 	if (!strcmp(type, "cachetextconv"))
 		return parse_bool(&drv->textconv_want_cache, k, v);
 	if (!strcmp(type, "wordregex"))
-		return git_config_string(&drv->word_regex, k, v);
+		return git_config_string((char **) &drv->word_regex, k, v);
 	if (!strcmp(type, "algorithm"))
-		return git_config_string(&drv->algorithm, k, v);
+		return git_config_string((char **) &drv->algorithm, k, v);
 
 	return 0;
 }
diff --git a/userdiff.h b/userdiff.h
index cc8e5abfef..d726804c3e 100644
--- a/userdiff.h
+++ b/userdiff.h
@@ -7,19 +7,19 @@ struct index_state;
 struct repository;
 
 struct userdiff_funcname {
-	char *pattern;
+	const char *pattern;
 	int cflags;
 };
 
 struct userdiff_driver {
 	const char *name;
-	char *external;
-	char *algorithm;
+	const char *external;
+	const char *algorithm;
 	int binary;
 	struct userdiff_funcname funcname;
-	char *word_regex;
-	char *word_regex_multi_byte;
-	char *textconv;
+	const char *word_regex;
+	const char *word_regex_multi_byte;
+	const char *textconv;
 	struct notes_cache *textconv_cache;
 	int textconv_want_cache;
 };
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:37 UTC · re: Patrick Steinhardt · lore

[PATCH v6 03/27] refs/reftable: stop micro-optimizing refname allocations on copy

When copying refs, we execute `write_copy_table()` to write the new table. As the names are given to us via `arg->newname` and `arg->oldname`, respectively, we optimize away some allocations by assigning those fields to the reftable records we are about to write directly, without duplicating them. This requires us to cast the input to `char *` pointers as they are in fact constant strings. Later on, we then unset the refname for all of the records before calling `reftable_log_record_release()` on them.

We also do this when assigning the "HEAD" constant, but here we do not cast because its type is `char[]` by default. It's about to be turned into `const char *` though once we enable `-Wwrite-strings` and will thus cause another warning.

It's quite dubious whether this micro-optimization really helps. We're about to write to disk anyway, which is going to be way slower than a small handful of allocations. Let's drop the optimization altogther and instead copy arguments to simplify the code and avoid the future warning with `-Wwrite-strings`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 refs/reftable-backend.c | 28 ++++++++++++++++------------
 1 file changed, 16 insertions(+), 12 deletions(-)
Show changes to refs/reftable-backend.c +16 −12
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 1af86bbdec..e77faa2b9d 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1340,10 +1340,10 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 	 * old reference.
 	 */
 	refs[0] = old_ref;
-	refs[0].refname = (char *)arg->newname;
+	refs[0].refname = xstrdup(arg->newname);
 	refs[0].update_index = creation_ts;
 	if (arg->delete_old) {
-		refs[1].refname = (char *)arg->oldname;
+		refs[1].refname = xstrdup(arg->oldname);
 		refs[1].value_type = REFTABLE_REF_DELETION;
 		refs[1].update_index = deletion_ts;
 	}
@@ -1366,7 +1366,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 		ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 		memset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));
 		fill_reftable_log_record(&logs[logs_nr], &committer_ident);
-		logs[logs_nr].refname = (char *)arg->newname;
+		logs[logs_nr].refname = xstrdup(arg->newname);
 		logs[logs_nr].update_index = deletion_ts;
 		logs[logs_nr].value.update.message =
 			xstrndup(arg->logmsg, arg->refs->write_options.block_size / 2);
@@ -1387,7 +1387,13 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 		if (append_head_reflog) {
 			ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 			logs[logs_nr] = logs[logs_nr - 1];
-			logs[logs_nr].refname = "HEAD";
+			logs[logs_nr].refname = xstrdup("HEAD");
+			logs[logs_nr].value.update.name =
+				xstrdup(logs[logs_nr].value.update.name);
+			logs[logs_nr].value.update.email =
+				xstrdup(logs[logs_nr].value.update.email);
+			logs[logs_nr].value.update.message =
+				xstrdup(logs[logs_nr].value.update.message);
 			logs_nr++;
 		}
 	}
@@ -1398,7 +1404,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 	ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 	memset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));
 	fill_reftable_log_record(&logs[logs_nr], &committer_ident);
-	logs[logs_nr].refname = (char *)arg->newname;
+	logs[logs_nr].refname = xstrdup(arg->newname);
 	logs[logs_nr].update_index = creation_ts;
 	logs[logs_nr].value.update.message =
 		xstrndup(arg->logmsg, arg->refs->write_options.block_size / 2);
@@ -1430,7 +1436,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 		 */
 		ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 		logs[logs_nr] = old_log;
-		logs[logs_nr].refname = (char *)arg->newname;
+		logs[logs_nr].refname = xstrdup(arg->newname);
 		logs_nr++;
 
 		/*
@@ -1439,7 +1445,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 		if (arg->delete_old) {
 			ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
 			memset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));
-			logs[logs_nr].refname = (char *)arg->oldname;
+			logs[logs_nr].refname = xstrdup(arg->oldname);
 			logs[logs_nr].value_type = REFTABLE_LOG_DELETION;
 			logs[logs_nr].update_index = old_log.update_index;
 			logs_nr++;
@@ -1462,13 +1468,11 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
 	reftable_iterator_destroy(&it);
 	string_list_clear(&skip, 0);
 	strbuf_release(&errbuf);
-	for (i = 0; i < logs_nr; i++) {
-		if (!strcmp(logs[i].refname, "HEAD"))
-			continue;
-		logs[i].refname = NULL;
+	for (i = 0; i < logs_nr; i++)
 		reftable_log_record_release(&logs[i]);
-	}
 	free(logs);
+	for (i = 0; i < ARRAY_SIZE(refs); i++)
+		reftable_ref_record_release(&refs[i]);
 	reftable_ref_record_release(&old_ref);
 	reftable_log_record_release(&old_log);
 	return ret;
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:37 UTC · re: Patrick Steinhardt · lore

[PATCH v6 04/27] reftable: cast away constness when assigning constants to records

The reftable records are used in multiple ways throughout the reftable library. In many of those cases they merely act as input to a function without getting modified by it at all. Most importantly, this happens when writing records and when querying for records.

We rely on this in our tests and thus assign string constants to those fields, which is about to generate warnings as those fields are of type `char *`. While we could go through the process and instead allocate those strings in all of our tests, this feels quite unnecessary.

Instead, add casts to `char *` for all of those strings. As this is part of our tests, this also nicely serves as a demonstration that nothing writes or frees those string constants, which would otherwise lead to segfaults.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 reftable/block_test.c     |  2 +-
 reftable/merged_test.c    | 44 +++++++++++++++++------------------
 reftable/readwrite_test.c | 32 +++++++++++++-------------
 reftable/stack_test.c     | 48 +++++++++++++++++++--------------------
 4 files changed, 63 insertions(+), 63 deletions(-)
Show changes to 4 files +63 −63

reftable/block_test.c, reftable/merged_test.c, reftable/readwrite_test.c, reftable/stack_test.c

diff --git a/reftable/block_test.c b/reftable/block_test.c
index 26a9cfbc83..90aecd5a7c 100644
--- a/reftable/block_test.c
+++ b/reftable/block_test.c
@@ -42,7 +42,7 @@ static void test_block_read_write(void)
 	block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,
 			  header_off, hash_size(GIT_SHA1_FORMAT_ID));
 
-	rec.u.ref.refname = "";
+	rec.u.ref.refname = (char *) "";
 	rec.u.ref.value_type = REFTABLE_REF_DELETION;
 	n = block_writer_add(&bw, &rec);
 	EXPECT(n == REFTABLE_API_ERROR);
diff --git a/reftable/merged_test.c b/reftable/merged_test.c
index 530fc82d1c..6d1159d12d 100644
--- a/reftable/merged_test.c
+++ b/reftable/merged_test.c
@@ -124,13 +124,13 @@ static void readers_destroy(struct reftable_reader **readers, size_t n)
 static void test_merged_between(void)
 {
 	struct reftable_ref_record r1[] = { {
-		.refname = "b",
+		.refname = (char *) "b",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_VAL1,
 		.value.val1 = { 1, 2, 3, 0 },
 	} };
 	struct reftable_ref_record r2[] = { {
-		.refname = "a",
+		.refname = (char *) "a",
 		.update_index = 2,
 		.value_type = REFTABLE_REF_DELETION,
 	} };
@@ -165,38 +165,38 @@ static void test_merged(void)
 {
 	struct reftable_ref_record r1[] = {
 		{
-			.refname = "a",
+			.refname = (char *) "a",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
 		},
 		{
-			.refname = "b",
+			.refname = (char *) "b",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
 		},
 		{
-			.refname = "c",
+			.refname = (char *) "c",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
 		}
 	};
 	struct reftable_ref_record r2[] = { {
-		.refname = "a",
+		.refname = (char *) "a",
 		.update_index = 2,
 		.value_type = REFTABLE_REF_DELETION,
 	} };
 	struct reftable_ref_record r3[] = {
 		{
-			.refname = "c",
+			.refname = (char *) "c",
 			.update_index = 3,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 2 },
 		},
 		{
-			.refname = "d",
+			.refname = (char *) "d",
 			.update_index = 3,
 			.value_type = REFTABLE_REF_VAL1,
 			.value.val1 = { 1 },
@@ -291,46 +291,46 @@ static void test_merged_logs(void)
 {
 	struct reftable_log_record r1[] = {
 		{
-			.refname = "a",
+			.refname = (char *) "a",
 			.update_index = 2,
 			.value_type = REFTABLE_LOG_UPDATE,
 			.value.update = {
 				.old_hash = { 2 },
 				/* deletion */
-				.name = "jane doe",
-				.email = "jane@invalid",
-				.message = "message2",
+				.name = (char *) "jane doe",
+				.email = (char *) "jane@invalid",
+				.message = (char *) "message2",
 			}
 		},
 		{
-			.refname = "a",
+			.refname = (char *) "a",
 			.update_index = 1,
 			.value_type = REFTABLE_LOG_UPDATE,
 			.value.update = {
 				.old_hash = { 1 },
 				.new_hash = { 2 },
-				.name = "jane doe",
-				.email = "jane@invalid",
-				.message = "message1",
+				.name = (char *) "jane doe",
+				.email = (char *) "jane@invalid",
+				.message = (char *) "message1",
 			}
 		},
 	};
 	struct reftable_log_record r2[] = {
 		{
-			.refname = "a",
+			.refname = (char *) "a",
 			.update_index = 3,
 			.value_type = REFTABLE_LOG_UPDATE,
 			.value.update = {
 				.new_hash = { 3 },
-				.name = "jane doe",
-				.email = "jane@invalid",
-				.message = "message3",
+				.name = (char *) "jane doe",
+				.email = (char *) "jane@invalid",
+				.message = (char *) "message3",
 			}
 		},
 	};
 	struct reftable_log_record r3[] = {
 		{
-			.refname = "a",
+			.refname = (char *) "a",
 			.update_index = 2,
 			.value_type = REFTABLE_LOG_DELETION,
 		},
@@ -406,7 +406,7 @@ static void test_default_write_opts(void)
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
 
 	struct reftable_ref_record rec = {
-		.refname = "master",
+		.refname = (char *) "master",
 		.update_index = 1,
 	};
 	int err;
diff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c
index a6dbd214c5..c55019232b 100644
--- a/reftable/readwrite_test.c
+++ b/reftable/readwrite_test.c
@@ -86,7 +86,7 @@ static void write_table(char ***names, struct strbuf *buf, int N,
 		log.update_index = update_index;
 		log.value_type = REFTABLE_LOG_UPDATE;
 		set_test_hash(log.value.update.new_hash, i);
-		log.value.update.message = "message";
+		log.value.update.message = (char *) "message";
 
 		n = reftable_writer_add_log(w, &log);
 		EXPECT(n == 0);
@@ -118,15 +118,15 @@ static void test_log_buffer_size(void)
 	int err;
 	int i;
 	struct reftable_log_record
-		log = { .refname = "refs/heads/master",
+		log = { .refname = (char *) "refs/heads/master",
 			.update_index = 0xa,
 			.value_type = REFTABLE_LOG_UPDATE,
 			.value = { .update = {
-					   .name = "Han-Wen Nienhuys",
-					   .email = "hanwen@google.com",
+					   .name = (char *) "Han-Wen Nienhuys",
+					   .email = (char *) "hanwen@google.com",
 					   .tz_offset = 100,
 					   .time = 0x5e430672,
-					   .message = "commit: 9\n",
+					   .message = (char *) "commit: 9\n",
 				   } } };
 	struct reftable_writer *w =
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
@@ -156,15 +156,15 @@ static void test_log_overflow(void)
 	};
 	int err;
 	struct reftable_log_record log = {
-		.refname = "refs/heads/master",
+		.refname = (char *) "refs/heads/master",
 		.update_index = 0xa,
 		.value_type = REFTABLE_LOG_UPDATE,
 		.value = {
 			.update = {
 				.old_hash = { 1 },
 				.new_hash = { 2 },
-				.name = "Han-Wen Nienhuys",
-				.email = "hanwen@google.com",
+				.name = (char *) "Han-Wen Nienhuys",
+				.email = (char *) "hanwen@google.com",
 				.tz_offset = 100,
 				.time = 0x5e430672,
 				.message = msg,
@@ -293,14 +293,14 @@ static void test_log_zlib_corruption(void)
 	char message[100] = { 0 };
 	int err, i, n;
 	struct reftable_log_record log = {
-		.refname = "refname",
+		.refname = (char *) "refname",
 		.value_type = REFTABLE_LOG_UPDATE,
 		.value = {
 			.update = {
 				.new_hash = { 1 },
 				.old_hash = { 2 },
-				.name = "My Name",
-				.email = "myname@invalid",
+				.name = (char *) "My Name",
+				.email = (char *) "myname@invalid",
 				.message = message,
 			},
 		},
@@ -728,7 +728,7 @@ static void test_write_empty_key(void)
 	struct reftable_writer *w =
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
 	struct reftable_ref_record ref = {
-		.refname = "",
+		.refname = (char *) "",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_DELETION,
 	};
@@ -752,18 +752,18 @@ static void test_write_key_order(void)
 		reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
 	struct reftable_ref_record refs[2] = {
 		{
-			.refname = "b",
+			.refname = (char *) "b",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_SYMREF,
 			.value = {
-				.symref = "target",
+				.symref = (char *) "target",
 			},
 		}, {
-			.refname = "a",
+			.refname = (char *) "a",
 			.update_index = 1,
 			.value_type = REFTABLE_REF_SYMREF,
 			.value = {
-				.symref = "target",
+				.symref = (char *) "target",
 			},
 		}
 	};
diff --git a/reftable/stack_test.c b/reftable/stack_test.c
index 07d89b45da..4abf92636d 100644
--- a/reftable/stack_test.c
+++ b/reftable/stack_test.c
@@ -156,10 +156,10 @@ static void test_reftable_stack_add_one(void)
 	struct reftable_stack *st = NULL;
 	int err;
 	struct reftable_ref_record ref = {
-		.refname = "HEAD",
+		.refname = (char *) "HEAD",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *) "master",
 	};
 	struct reftable_ref_record dest = { NULL };
 	struct stat stat_result = { 0 };
@@ -216,16 +216,16 @@ static void test_reftable_stack_uptodate(void)
 
 	int err;
 	struct reftable_ref_record ref1 = {
-		.refname = "HEAD",
+		.refname = (char *) "HEAD",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *) "master",
 	};
 	struct reftable_ref_record ref2 = {
-		.refname = "branch2",
+		.refname = (char *) "branch2",
 		.update_index = 2,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *) "master",
 	};
 
 
@@ -264,10 +264,10 @@ static void test_reftable_stack_transaction_api(void)
 	struct reftable_addition *add = NULL;
 
 	struct reftable_ref_record ref = {
-		.refname = "HEAD",
+		.refname = (char *) "HEAD",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *) "master",
 	};
 	struct reftable_ref_record dest = { NULL };
 
@@ -313,7 +313,7 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)
 		struct reftable_ref_record ref = {
 			.update_index = reftable_stack_next_update_index(st),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = (char *) "master",
 		};
 		char name[100];
 
@@ -356,7 +356,7 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)
 static void test_reftable_stack_auto_compaction_fails_gracefully(void)
 {
 	struct reftable_ref_record ref = {
-		.refname = "refs/heads/master",
+		.refname = (char *) "refs/heads/master",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_VAL1,
 		.value.val1 = {0x01},
@@ -409,16 +409,16 @@ static void test_reftable_stack_update_index_check(void)
 	struct reftable_stack *st = NULL;
 	int err;
 	struct reftable_ref_record ref1 = {
-		.refname = "name1",
+		.refname = (char *) "name1",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *) "master",
 	};
 	struct reftable_ref_record ref2 = {
-		.refname = "name2",
+		.refname = (char *) "name2",
 		.update_index = 1,
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "master",
+		.value.symref = (char *) "master",
 	};
 
 	err = reftable_new_stack(&st, dir, cfg);
@@ -561,7 +561,7 @@ static void test_reftable_stack_log_normalize(void)
 	struct reftable_stack *st = NULL;
 	char *dir = get_tmp_dir(__LINE__);
 	struct reftable_log_record input = {
-		.refname = "branch",
+		.refname = (char *) "branch",
 		.update_index = 1,
 		.value_type = REFTABLE_LOG_UPDATE,
 		.value = {
@@ -582,11 +582,11 @@ static void test_reftable_stack_log_normalize(void)
 	err = reftable_new_stack(&st, dir, cfg);
 	EXPECT_ERR(err);
 
-	input.value.update.message = "one\ntwo";
+	input.value.update.message = (char *) "one\ntwo";
 	err = reftable_stack_add(st, &write_test_log, &arg);
 	EXPECT(err == REFTABLE_API_ERROR);
 
-	input.value.update.message = "one";
+	input.value.update.message = (char *) "one";
 	err = reftable_stack_add(st, &write_test_log, &arg);
 	EXPECT_ERR(err);
 
@@ -594,7 +594,7 @@ static void test_reftable_stack_log_normalize(void)
 	EXPECT_ERR(err);
 	EXPECT(0 == strcmp(dest.value.update.message, "one\n"));
 
-	input.value.update.message = "two\n";
+	input.value.update.message = (char *) "two\n";
 	arg.update_index = 2;
 	err = reftable_stack_add(st, &write_test_log, &arg);
 	EXPECT_ERR(err);
@@ -697,9 +697,9 @@ static void test_reftable_stack_hash_id(void)
 	int err;
 
 	struct reftable_ref_record ref = {
-		.refname = "master",
+		.refname = (char *) "master",
 		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = "target",
+		.value.symref = (char *) "target",
 		.update_index = 1,
 	};
 	struct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID };
@@ -879,7 +879,7 @@ static void test_reftable_stack_auto_compaction(void)
 			.refname = name,
 			.update_index = reftable_stack_next_update_index(st),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = (char *) "master",
 		};
 		snprintf(name, sizeof(name), "branch%04d", i);
 
@@ -913,7 +913,7 @@ static void test_reftable_stack_add_performs_auto_compaction(void)
 		struct reftable_ref_record ref = {
 			.update_index = reftable_stack_next_update_index(st),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = (char *) "master",
 		};
 
 		/*
@@ -964,7 +964,7 @@ static void test_reftable_stack_compaction_concurrent(void)
 			.refname = name,
 			.update_index = reftable_stack_next_update_index(st1),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = (char *) "master",
 		};
 		snprintf(name, sizeof(name), "branch%04d", i);
 
@@ -1014,7 +1014,7 @@ static void test_reftable_stack_compaction_concurrent_clean(void)
 			.refname = name,
 			.update_index = reftable_stack_next_update_index(st1),
 			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = "master",
+			.value.symref = (char *) "master",
 		};
 		snprintf(name, sizeof(name), "branch%04d", i);
 
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:37 UTC · re: Patrick Steinhardt · lore

[PATCH v6 05/27] refspec: remove global tag refspec structure

We have a global tag refspec structure that is used by both git-clone(1) and git-fetch(1). Initialization of the structure will break once we enable `-Wwrite-strings`, even though the breakage is harmless. While we could just add casts, the structure isn't really required in the first place as we can simply initialize the structures at the respective callsites.

Refactor the code accordingly.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c |  8 ++++++--
 builtin/fetch.c | 11 ++++++++---
 refspec.c       | 13 -------------
 refspec.h       |  1 -
 4 files changed, 14 insertions(+), 19 deletions(-)
Show changes to 4 files +14 −19

builtin/clone.c, builtin/fetch.c, refspec.c, refspec.h

diff --git a/builtin/clone.c b/builtin/clone.c
index 92ab7d7165..bde1d284a2 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -523,6 +523,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
 	struct ref *head = copy_ref(find_ref_by_name(refs, "HEAD"));
 	struct ref *local_refs = head;
 	struct ref **tail = head ? &head->next : &local_refs;
+	struct refspec_item tag_refspec;
+
+	refspec_item_init(&tag_refspec, TAG_REFSPEC, 0);
 
 	if (option_single_branch) {
 		struct ref *remote_head = NULL;
@@ -545,7 +548,7 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
 					      &tail, 0);
 
 			/* if --branch=tag, pull the requested tag explicitly */
-			get_fetch_map(remote_head, tag_refspec, &tail, 0);
+			get_fetch_map(remote_head, &tag_refspec, &tail, 0);
 		}
 		free_refs(remote_head);
 	} else {
@@ -555,8 +558,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
 	}
 
 	if (!option_mirror && !option_single_branch && !option_no_tags)
-		get_fetch_map(refs, tag_refspec, &tail, 0);
+		get_fetch_map(refs, &tag_refspec, &tail, 0);
 
+	refspec_item_clear(&tag_refspec);
 	return local_refs;
 }
 
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 75255dc600..06b60867f5 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -582,11 +582,16 @@ static struct ref *get_ref_map(struct remote *remote,
 		}
 	}
 
-	if (tags == TAGS_SET)
+	if (tags == TAGS_SET) {
+		struct refspec_item tag_refspec;
+
 		/* also fetch all tags */
-		get_fetch_map(remote_refs, tag_refspec, &tail, 0);
-	else if (tags == TAGS_DEFAULT && *autotags)
+		refspec_item_init(&tag_refspec, TAG_REFSPEC, 0);
+		get_fetch_map(remote_refs, &tag_refspec, &tail, 0);
+		refspec_item_clear(&tag_refspec);
+	} else if (tags == TAGS_DEFAULT && *autotags) {
 		find_non_local_tags(remote_refs, NULL, &ref_map, &tail);
+	}
 
 	/* Now append any refs to be updated opportunistically: */
 	*tail = orefs;
diff --git a/refspec.c b/refspec.c
index d60932f4de..1df5de6c2f 100644
--- a/refspec.c
+++ b/refspec.c
@@ -7,19 +7,6 @@
 #include "refspec.h"
 #include "strbuf.h"
 
-static struct refspec_item s_tag_refspec = {
-	.force = 0,
-	.pattern = 1,
-	.matching = 0,
-	.exact_sha1 = 0,
-	.negative = 0,
-	.src = "refs/tags/*",
-	.dst = "refs/tags/*",
-};
-
-/* See TAG_REFSPEC for the string version */
-const struct refspec_item *tag_refspec = &s_tag_refspec;
-
 /*
  * Parses the provided refspec 'refspec' and populates the refspec_item 'item'.
  * Returns 1 if successful and 0 if the refspec is invalid.
diff --git a/refspec.h b/refspec.h
index 8c0c446993..754be45cee 100644
--- a/refspec.h
+++ b/refspec.h
@@ -2,7 +2,6 @@
 #define REFSPEC_H
 
 #define TAG_REFSPEC "refs/tags/*:refs/tags/*"
-extern const struct refspec_item *tag_refspec;
 
 /**
  * A struct refspec_item holds the parsed interpretation of a refspec.  If it
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:38 UTC · re: Patrick Steinhardt · lore

[PATCH v6 06/27] builtin/remote: cast away constness in `get_head_names()`

In `get_head_names()`, we assign the "refs/heads/*" string constant to `struct refspec_item::{src,dst}`, which are both non-constant pointers. Ideally, we'd refactor the code such that both of these fields were constant. But `struct refspec_item` is used for two different usecases with conflicting requirements:

  - To query for a source or destination based on the given refspec. The
    caller either sets `src` or `dst` as the branch that we want to
    search for, and the respective other field gets populated. The
    fields should be constant when being used as a query parameter,
    which is owned by the caller, and non-constant when being used as an
    out parameter, which is owned by the refspec item. This is is
    contradictory in itself already.
  - To store refspec items with their respective source and destination
    branches, in which case both fields should be owned by the struct.

Ideally, we'd split up this interface to clearly separate between querying and storing, which would enable us to clarify lifetimes of the strings. This would be a much bigger undertaking though.

Instead, accept the status quo for now and cast away the constness of the source and destination patterns. We know that those are not being written to or freed, so while this is ugly it certainly is fine for now.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/remote.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)
Show changes to builtin/remote.c +6 −6
diff --git a/builtin/remote.c b/builtin/remote.c
index d52b1c0e10..b44f580b8c 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -493,12 +493,13 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat
 {
 	struct ref *ref, *matches;
 	struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;
-	struct refspec_item refspec;
+	struct refspec_item refspec = {
+		.force = 0,
+		.pattern = 1,
+		.src = (char *) "refs/heads/*",
+		.dst = (char *) "refs/heads/*",
+	};
 
-	memset(&refspec, 0, sizeof(refspec));
-	refspec.force = 0;
-	refspec.pattern = 1;
-	refspec.src = refspec.dst = "refs/heads/*";
 	get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
 	matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
 				    fetch_map, 1);
@@ -507,7 +508,6 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat
 
 	free_refs(fetch_map);
 	free_refs(matches);
-
 	return 0;
 }
 
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:38 UTC · re: Patrick Steinhardt · lore

[PATCH v6 07/27] diff: cast string constant in `fill_textconv()`

The `fill_textconv()` function is responsible for converting an input file with a textconv driver, which is then passed to the caller. Weirdly though, the function also handles the case where there is no textconv driver at all. In that case, it will return either the contents of the populated filespec, or an empty string if the filespec is invalid.

These two cases have differing memory ownership semantics. When there is a textconv driver, then the result is an allocated string. Otherwise, the result is either a string constant or owned by the filespec struct. All callers are in fact aware of this weirdness and only end up freeing the output buffer when they had a textconv driver.

Ideally, we'd split up this interface to only perform the conversion via the textconv driver, and BUG in case the caller didn't provide one. This would make memory ownership semantics much more straight forward. For now though, let's simply cast the empty string constant to `char *` to avoid a warning with `-Wwrite-strings`. This is equivalent to the same cast that we already have in `fill_mmfile()`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 diff.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to diff.c +1 −1
diff --git a/diff.c b/diff.c
index ffd867ef6c..cecda216cf 100644
--- a/diff.c
+++ b/diff.c
@@ -7235,7 +7235,7 @@ size_t fill_textconv(struct repository *r,
 
 	if (!driver) {
 		if (!DIFF_FILE_VALID(df)) {
-			*outbuf = "";
+			*outbuf = (char *) "";
 			return 0;
 		}
 		if (diff_populate_filespec(r, df, NULL))
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:38 UTC · re: Patrick Steinhardt · lore

[PATCH v6 08/27] line-log: stop assigning string constant to file parent buffer

Stop assigning a string constant to the file parent buffer and instead assign an allocated string. While the code is fine in practice, it will break once we compile with `-Wwrite-strings`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 line-log.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
Show changes to line-log.c +3 −1
diff --git a/line-log.c b/line-log.c
index 8ff6ccb772..bd3e663c24 100644
--- a/line-log.c
+++ b/line-log.c
@@ -1032,6 +1032,7 @@ static int process_diff_filepair(struct rev_info *rev,
 	struct range_set tmp;
 	struct diff_ranges diff;
 	mmfile_t file_parent, file_target;
+	char *parent_data_to_free = NULL;
 
 	assert(pair->two->path);
 	while (rg) {
@@ -1056,7 +1057,7 @@ static int process_diff_filepair(struct rev_info *rev,
 		file_parent.ptr = pair->one->data;
 		file_parent.size = pair->one->size;
 	} else {
-		file_parent.ptr = "";
+		file_parent.ptr = parent_data_to_free = xstrdup("");
 		file_parent.size = 0;
 	}
 
@@ -1075,6 +1076,7 @@ static int process_diff_filepair(struct rev_info *rev,
 
 	diff_ranges_release(&diff);
 
+	free(parent_data_to_free);
 	return ((*diff_out)->parent.nr > 0);
 }
 
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:38 UTC · re: Patrick Steinhardt · lore

[PATCH v6 09/27] line-log: always allocate the output prefix

The returned string by `output_prefix()` is sometimes a string constant and sometimes an allocated string. This has been fine until now because we always leak the allocated strings, and thus we never tried to free the string constant.

Fix the code to always return an allocated string and free the returned value at all callsites.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 line-log.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)
Show changes to line-log.c +11 −7
diff --git a/line-log.c b/line-log.c
index bd3e663c24..67c80b39a0 100644
--- a/line-log.c
+++ b/line-log.c
@@ -899,14 +899,12 @@ static void print_line(const char *prefix, char first,
 
 static char *output_prefix(struct diff_options *opt)
 {
-	char *prefix = "";
-
 	if (opt->output_prefix) {
 		struct strbuf *sb = opt->output_prefix(opt, opt->output_prefix_data);
-		prefix = sb->buf;
+		return sb->buf;
+	} else {
+		return xstrdup("");
 	}
-
-	return prefix;
 }
 
 static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *range)
@@ -927,7 +925,7 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang
 	const char *c_context = diff_get_color(opt->use_color, DIFF_CONTEXT);
 
 	if (!pair || !diff)
-		return;
+		goto out;
 
 	if (pair->one->oid_valid)
 		fill_line_ends(rev->diffopt.repo, pair->one, &p_lines, &p_ends);
@@ -1002,8 +1000,10 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang
 				   c_context, c_reset, opt->file);
 	}
 
+out:
 	free(p_ends);
 	free(t_ends);
+	free(prefix);
 }
 
 /*
@@ -1012,7 +1012,11 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang
  */
 static void dump_diff_hacky(struct rev_info *rev, struct line_log_data *range)
 {
-	fprintf(rev->diffopt.file, "%s\n", output_prefix(&rev->diffopt));
+	char *prefix = output_prefix(&rev->diffopt);
+
+	fprintf(rev->diffopt.file, "%s\n", prefix);
+	free(prefix);
+
 	while (range) {
 		dump_diff_hacky_one(rev, range);
 		range = range->next;
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:38 UTC · re: Patrick Steinhardt · lore

[PATCH v6 10/27] entry: refactor how we remove items for delayed checkouts

When finalizing a delayed checkout, we sort out several strings from the passed-in string list by first assigning the empty string to those filters and then calling `string_list_remove_empty_items()`. Assigning the empty string will cause compiler warnings though as the string is a `char *` once we enable `-Wwrite-strings`.

Refactor the code to use a `NULL` pointer with `filter_string_list()` instead to avoid this warning.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 entry.c | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)
Show changes to entry.c +10 −4
diff --git a/entry.c b/entry.c
index b8c257f6f9..f291d8eee6 100644
--- a/entry.c
+++ b/entry.c
@@ -167,6 +167,11 @@ static int remove_available_paths(struct string_list_item *item, void *cb_data)
 	return !available;
 }
 
+static int string_is_not_null(struct string_list_item *item, void *data UNUSED)
+{
+	return !!item->string;
+}
+
 int finish_delayed_checkout(struct checkout *state, int show_progress)
 {
 	int errs = 0;
@@ -189,7 +194,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 			if (!async_query_available_blobs(filter->string, &available_paths)) {
 				/* Filter reported an error */
 				errs = 1;
-				filter->string = "";
+				filter->string = NULL;
 				continue;
 			}
 			if (available_paths.nr <= 0) {
@@ -199,7 +204,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 				 * filter from the list (see
 				 * "string_list_remove_empty_items" call below).
 				 */
-				filter->string = "";
+				filter->string = NULL;
 				continue;
 			}
 
@@ -225,7 +230,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 					 * Do not ask the filter for available blobs,
 					 * again, as the filter is likely buggy.
 					 */
-					filter->string = "";
+					filter->string = NULL;
 					continue;
 				}
 				ce = index_file_exists(state->istate, path->string,
@@ -239,7 +244,8 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)
 					errs = 1;
 			}
 		}
-		string_list_remove_empty_items(&dco->filters, 0);
+
+		filter_string_list(&dco->filters, 0, string_is_not_null, NULL);
 	}
 	stop_progress(&progress);
 	string_list_clear(&dco->filters, 0);
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:38 UTC · re: Patrick Steinhardt · lore

[PATCH v6 11/27] ident: add casts for fallback name and GECOS

In `xgetpwuid_self()`, we return a fallback identity when it was not possible to look up the current identity. This fallback identity needs to be internal and must never be written to by the calles as specified by getpwuid(3P). As both the `pw_name` and `pw_gecos` fields are marked as non-constant though, it will cause a warning to assign constant strings to them once compiling with `-Wwrite-strings`.

Add explicit casts to avoid the warning.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 ident.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
Show changes to ident.c +2 −2
diff --git a/ident.c b/ident.c
index cc7afdbf81..caf41fb2a9 100644
--- a/ident.c
+++ b/ident.c
@@ -46,9 +46,9 @@ static struct passwd *xgetpwuid_self(int *is_bogus)
 	pw = getpwuid(getuid());
 	if (!pw) {
 		static struct passwd fallback;
-		fallback.pw_name = "unknown";
+		fallback.pw_name = (char *) "unknown";
 #ifndef NO_GECOS_IN_PWENT
-		fallback.pw_gecos = "Unknown";
+		fallback.pw_gecos = (char *) "Unknown";
 #endif
 		pw = &fallback;
 		if (is_bogus)
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:38 UTC · re: Patrick Steinhardt · lore

[PATCH v6 12/27] object-file: mark cached object buffers as const

The buffers of cached objects are never modified, but are still stored as a non-constant pointer. This will cause a compiler warning once we enable the `-Wwrite-strings` compiler warning as we assign an empty constant string when initializing the static `empty_tree` cached object.

Convert the field to be constant. This requires us to shuffle around the code a bit because we memcpy(3P) into the allocated buffer in `pretend_object_file()`. This is easily fixed though by allocating the buffer into a temporary variable first.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 object-file.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)
Show changes to object-file.c +5 −3
diff --git a/object-file.c b/object-file.c
index 610b1f465c..08c00dcc02 100644
--- a/object-file.c
+++ b/object-file.c
@@ -277,7 +277,7 @@ int hash_algo_by_length(int len)
 static struct cached_object {
 	struct object_id oid;
 	enum object_type type;
-	void *buf;
+	const void *buf;
 	unsigned long size;
 } *cached_objects;
 static int cached_object_nr, cached_object_alloc;
@@ -1778,6 +1778,7 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,
 			struct object_id *oid)
 {
 	struct cached_object *co;
+	char *co_buf;
 
 	hash_object_file(the_hash_algo, buf, len, type, oid);
 	if (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||
@@ -1787,8 +1788,9 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,
 	co = &cached_objects[cached_object_nr++];
 	co->size = len;
 	co->type = type;
-	co->buf = xmalloc(len);
-	memcpy(co->buf, buf, len);
+	co_buf = xmalloc(len);
+	memcpy(co_buf, buf, len);
+	co->buf = co_buf;
 	oidcpy(&co->oid, oid);
 	return 0;
 }
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:38 UTC · re: Patrick Steinhardt · lore

[PATCH v6 13/27] object-file: make `buf` parameter of `index_mem()` a constant

The `buf` parameter of `index_mem()` is a non-constant string. This will break once we enable `-Wwrite-strings` because we also pass constants from at least one callsite.

Adapt the parameter to be a constant. As we cannot free the buffer without casting now, this also requires us to move the lifetime of the nested buffer around.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 object-file.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)
Show changes to object-file.c +7 −7
diff --git a/object-file.c b/object-file.c
index 08c00dcc02..0b58751f94 100644
--- a/object-file.c
+++ b/object-file.c
@@ -2484,12 +2484,13 @@ static int hash_format_check_report(struct fsck_options *opts UNUSED,
 }
 
 static int index_mem(struct index_state *istate,
-		     struct object_id *oid, void *buf, size_t size,
+		     struct object_id *oid,
+		     const void *buf, size_t size,
 		     enum object_type type,
 		     const char *path, unsigned flags)
 {
+	struct strbuf nbuf = STRBUF_INIT;
 	int ret = 0;
-	int re_allocated = 0;
 	int write_object = flags & HASH_WRITE_OBJECT;
 
 	if (!type)
@@ -2499,11 +2500,10 @@ static int index_mem(struct index_state *istate,
 	 * Convert blobs to git internal format
 	 */
 	if ((type == OBJ_BLOB) && path) {
-		struct strbuf nbuf = STRBUF_INIT;
 		if (convert_to_git(istate, path, buf, size, &nbuf,
 				   get_conv_flags(flags))) {
-			buf = strbuf_detach(&nbuf, &size);
-			re_allocated = 1;
+			buf = nbuf.buf;
+			size = nbuf.len;
 		}
 	}
 	if (flags & HASH_FORMAT_CHECK) {
@@ -2520,8 +2520,8 @@ static int index_mem(struct index_state *istate,
 		ret = write_object_file(buf, size, type, oid);
 	else
 		hash_object_file(the_hash_algo, buf, size, type, oid);
-	if (re_allocated)
-		free(buf);
+
+	strbuf_release(&nbuf);
 	return ret;
 }
 
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:38 UTC · re: Patrick Steinhardt · lore

[PATCH v6 14/27] pretty: add casts for decoration option pointers

The `struct decoration_options` have a prefix and suffix field which are both non-constant, but we assign a constant pointer to them. This is safe to do because we pass them to `format_decorations()`, which never modifies these pointers, and then immediately discard the structure. Add explicit casts to avoid compilation warnings with `-Wwrite-strings`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 pretty.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
Show changes to pretty.c +2 −2
diff --git a/pretty.c b/pretty.c
index ec05db5655..1df9d635fb 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1584,8 +1584,8 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
 	case 'D':
 		{
 			const struct decoration_options opts = {
-				.prefix = "",
-				.suffix = ""
+				.prefix = (char *) "",
+				.suffix = (char *) "",
 			};
 
 			format_decorations(sb, commit, c->auto_color, &opts);
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:38 UTC · re: Patrick Steinhardt · lore

[PATCH v6 15/27] compat/win32: fix const-correctness with string constants

Adjust various places in our Win32 compatibility layer where we are not assigning string constants to `const char *` variables.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 compat/basename.c | 16 ++++++++++++++--
 compat/mingw.c    | 28 ++++++++++++++++------------
 compat/winansi.c  |  2 +-
 3 files changed, 31 insertions(+), 15 deletions(-)
Show changes to 3 files +31 −15

compat/basename.c, compat/mingw.c, compat/winansi.c

diff --git a/compat/basename.c b/compat/basename.c
index 96bd9533b4..c33579ef61 100644
--- a/compat/basename.c
+++ b/compat/basename.c
@@ -10,7 +10,13 @@ char *gitbasename (char *path)
 		skip_dos_drive_prefix(&path);
 
 	if (!path || !*path)
-		return ".";
+		/*
+		 * basename(3P) is mis-specified because it returns a
+		 * non-constant pointer even though it is specified to return a
+		 * pointer to internal memory at times. The cast is a result of
+		 * that.
+		 */
+		return (char *) ".";
 
 	for (base = path; *path; path++) {
 		if (!is_dir_sep(*path))
@@ -34,7 +40,13 @@ char *gitdirname(char *path)
 	int dos_drive_prefix;
 
 	if (!p)
-		return ".";
+		/*
+		 * dirname(3P) is mis-specified because it returns a
+		 * non-constant pointer even though it is specified to return a
+		 * pointer to internal memory at times. The cast is a result of
+		 * that.
+		 */
+		return (char *) ".";
 
 	if ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p)
 		goto dot;
diff --git a/compat/mingw.c b/compat/mingw.c
index 6b06ea540f..d378cd04cb 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -2279,7 +2279,11 @@ struct passwd *getpwuid(int uid)
 	p->pw_name = user_name;
 	p->pw_gecos = get_extended_user_info(NameDisplay);
 	if (!p->pw_gecos)
-		p->pw_gecos = "unknown";
+		/*
+		 * Data returned by getpwuid(3P) is treated as internal and
+		 * must never be written to or freed.
+		 */
+		p->pw_gecos = (char *) "unknown";
 	p->pw_dir = NULL;
 
 	initialized = 1;
@@ -2800,16 +2804,16 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)
 			strbuf_addf(report, "'%s' is on a file system that does "
 				    "not record ownership\n", path);
 		} else if (report) {
-			LPSTR str1, str2, str3, str4, to_free1 = NULL,
-			    to_free3 = NULL, to_local_free2 = NULL,
-			    to_local_free4 = NULL;
+			PCSTR str1, str2, str3, str4;
+			LPSTR to_free1 = NULL, to_free3 = NULL,
+			    to_local_free2 = NULL, to_local_free4 = NULL;
 
-			if (user_sid_to_user_name(sid, &str1))
-				to_free1 = str1;
+			if (user_sid_to_user_name(sid, &to_free1))
+				str1 = to_free1;
 			else
 				str1 = "(inconvertible)";
-			if (ConvertSidToStringSidA(sid, &str2))
-				to_local_free2 = str2;
+			if (ConvertSidToStringSidA(sid, &to_local_free2))
+				str2 = to_local_free2;
 			else
 				str2 = "(inconvertible)";
 
@@ -2822,13 +2826,13 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)
 				str4 = "(invalid)";
 			} else {
 				if (user_sid_to_user_name(current_user_sid,
-							  &str3))
-					to_free3 = str3;
+							  &to_free3))
+					str3 = to_free3;
 				else
 					str3 = "(inconvertible)";
 				if (ConvertSidToStringSidA(current_user_sid,
-							   &str4))
-					to_local_free4 = str4;
+							   &to_local_free4))
+					str4 = to_local_free4;
 				else
 					str4 = "(inconvertible)";
 			}
diff --git a/compat/winansi.c b/compat/winansi.c
index f83610f684..575813bde8 100644
--- a/compat/winansi.c
+++ b/compat/winansi.c
@@ -139,7 +139,7 @@ static void write_console(unsigned char *str, size_t len)
 	/* convert utf-8 to utf-16 */
 	int wlen = xutftowcsn(wbuf, (char*) str, ARRAY_SIZE(wbuf), len);
 	if (wlen < 0) {
-		wchar_t *err = L"[invalid]";
+		const wchar_t *err = L"[invalid]";
 		WriteConsoleW(console, err, wcslen(err), &dummy, NULL);
 		return;
 	}
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:38 UTC · re: Patrick Steinhardt · lore

[PATCH v6 16/27] http: do not assign string constant to non-const field

In `write_accept_language()`, we put all acceptable languages into an array. While all entries in that array are allocated strings, the final entry in that array is a string constant. This is fine because we explicitly skip over the last entry when freeing the array, but will cause warnings once we enable `-Wwrite-strings`.

Adapt the code to also allocate the final entry.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 http.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)
Show changes to http.c +2 −3
diff --git a/http.c b/http.c
index 67cc47d28f..2dea2d03da 100644
--- a/http.c
+++ b/http.c
@@ -1974,7 +1974,7 @@ static void write_accept_language(struct strbuf *buf)
 
 		/* add '*' */
 		REALLOC_ARRAY(language_tags, num_langs + 1);
-		language_tags[num_langs++] = "*"; /* it's OK; this won't be freed */
+		language_tags[num_langs++] = xstrdup("*");
 
 		/* compute decimal_places */
 		for (max_q = 1, decimal_places = 0;
@@ -2004,8 +2004,7 @@ static void write_accept_language(struct strbuf *buf)
 		}
 	}
 
-	/* free language tags -- last one is a static '*' */
-	for (i = 0; i < num_langs - 1; i++)
+	for (i = 0; i < num_langs; i++)
 		free(language_tags[i]);
 	free(language_tags);
 }
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:38 UTC · re: Patrick Steinhardt · lore

[PATCH v6 17/27] parse-options: cast long name for OPTION_ALIAS

We assign the long name for OPTION_ALIAS options to a non-constant value field. We know that the variable will never be written to, but this will cause warnings once we enable `-Wwrite-strings`.

Cast away the constness to be prepared for this change.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 parse-options.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to parse-options.h +1 −1
diff --git a/parse-options.h b/parse-options.h
index bd62e20268..ae15342390 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -355,7 +355,7 @@ struct option {
 	.type = OPTION_ALIAS, \
 	.short_name = (s), \
 	.long_name = (l), \
-	.value = (source_long_name), \
+	.value = (char *)(source_long_name), \
 }
 
 #define OPT_SUBCOMMAND_F(l, v, fn, f) { \
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:38 UTC · re: Patrick Steinhardt · lore

[PATCH v6 18/27] send-pack: always allocate receive status

In `receive_status()`, we record the reason why ref updates have been rejected by the remote via the `remote_status`. But while we allocate the assigned string when a reason was given, we assign a string constant when no reason was given.

This has been working fine so far due to two reasons:
  - We don't ever free the refs in git-send-pack(1)'
  - Remotes always give a reason, at least as implemented by Git proper.

Adapt the code to always allocate the receive status string and free the refs.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/send-pack.c | 2 ++
 send-pack.c         | 2 +-
 2 files changed, 3 insertions(+), 1 deletion(-)
Show changes to 2 files +3 −1

builtin/send-pack.c, send-pack.c

diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index 3df9eaad09..17cae6bbbd 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -336,5 +336,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
 		/* stable plumbing output; do not modify or localize */
 		fprintf(stderr, "Everything up-to-date\n");
 
+	free_refs(remote_refs);
+	free_refs(local_refs);
 	return ret;
 }
diff --git a/send-pack.c b/send-pack.c
index 37f59d4f66..88e96d000b 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -259,7 +259,7 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)
 			if (p)
 				hint->remote_status = xstrdup(p);
 			else
-				hint->remote_status = "failed";
+				hint->remote_status = xstrdup("failed");
 		} else {
 			hint->status = REF_STATUS_OK;
 			hint->remote_status = xstrdup_or_null(p);
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:39 UTC · re: Patrick Steinhardt · lore

[PATCH v6 19/27] remote-curl: avoid assigning string constant to non-const variable

When processing remote options, we split the option line into two by searching for a space. If there is one, we replace the space with '\0', otherwise we implicitly assume that the value is "true" and thus assign a string constant.

As the return value of strchr(3P) weirdly enough is a `char *` even though it gets a `const char *` as input, the assigned-to variable also is a non-constant. This is fine though because the argument is in fact an allocated string, and thus we are allowed to modify it. But this will break once we enable `-Wwrite-strings`.

Refactor the code stop splitting the fields with '\0' altogether. Instead, we can pass the length of the option name to `set_option()` and then use strncmp(3P) instead of strcmp(3P).

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 remote-curl.c | 53 ++++++++++++++++++++++++++-------------------------
 1 file changed, 27 insertions(+), 26 deletions(-)
Show changes to remote-curl.c +27 −26
diff --git a/remote-curl.c b/remote-curl.c
index cae98384da..d0f767df8e 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -58,9 +58,9 @@ struct options {
 static struct options options;
 static struct string_list cas_options = STRING_LIST_INIT_DUP;
 
-static int set_option(const char *name, const char *value)
+static int set_option(const char *name, size_t namelen, const char *value)
 {
-	if (!strcmp(name, "verbosity")) {
+	if (!strncmp(name, "verbosity", namelen)) {
 		char *end;
 		int v = strtol(value, &end, 10);
 		if (value == end || *end)
@@ -68,7 +68,7 @@ static int set_option(const char *name, const char *value)
 		options.verbosity = v;
 		return 0;
 	}
-	else if (!strcmp(name, "progress")) {
+	else if (!strncmp(name, "progress", namelen)) {
 		if (!strcmp(value, "true"))
 			options.progress = 1;
 		else if (!strcmp(value, "false"))
@@ -77,7 +77,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "depth")) {
+	else if (!strncmp(name, "depth", namelen)) {
 		char *end;
 		unsigned long v = strtoul(value, &end, 10);
 		if (value == end || *end)
@@ -85,15 +85,15 @@ static int set_option(const char *name, const char *value)
 		options.depth = v;
 		return 0;
 	}
-	else if (!strcmp(name, "deepen-since")) {
+	else if (!strncmp(name, "deepen-since", namelen)) {
 		options.deepen_since = xstrdup(value);
 		return 0;
 	}
-	else if (!strcmp(name, "deepen-not")) {
+	else if (!strncmp(name, "deepen-not", namelen)) {
 		string_list_append(&options.deepen_not, value);
 		return 0;
 	}
-	else if (!strcmp(name, "deepen-relative")) {
+	else if (!strncmp(name, "deepen-relative", namelen)) {
 		if (!strcmp(value, "true"))
 			options.deepen_relative = 1;
 		else if (!strcmp(value, "false"))
@@ -102,7 +102,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "followtags")) {
+	else if (!strncmp(name, "followtags", namelen)) {
 		if (!strcmp(value, "true"))
 			options.followtags = 1;
 		else if (!strcmp(value, "false"))
@@ -111,7 +111,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "dry-run")) {
+	else if (!strncmp(name, "dry-run", namelen)) {
 		if (!strcmp(value, "true"))
 			options.dry_run = 1;
 		else if (!strcmp(value, "false"))
@@ -120,7 +120,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "check-connectivity")) {
+	else if (!strncmp(name, "check-connectivity", namelen)) {
 		if (!strcmp(value, "true"))
 			options.check_self_contained_and_connected = 1;
 		else if (!strcmp(value, "false"))
@@ -129,7 +129,7 @@ static int set_option(const char *name, const char *value)
 			return -1;
 		return 0;
 	}
-	else if (!strcmp(name, "cas")) {
+	else if (!strncmp(name, "cas", namelen)) {
 		struct strbuf val = STRBUF_INIT;
 		strbuf_addstr(&val, "--force-with-lease=");
 		if (*value != '"')
@@ -139,7 +139,7 @@ static int set_option(const char *name, const char *value)
 		string_list_append(&cas_options, val.buf);
 		strbuf_release(&val);
 		return 0;
-	} else if (!strcmp(name, TRANS_OPT_FORCE_IF_INCLUDES)) {
+	} else if (!strncmp(name, TRANS_OPT_FORCE_IF_INCLUDES, namelen)) {
 		if (!strcmp(value, "true"))
 			options.force_if_includes = 1;
 		else if (!strcmp(value, "false"))
@@ -147,7 +147,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "cloning")) {
+	} else if (!strncmp(name, "cloning", namelen)) {
 		if (!strcmp(value, "true"))
 			options.cloning = 1;
 		else if (!strcmp(value, "false"))
@@ -155,7 +155,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "update-shallow")) {
+	} else if (!strncmp(name, "update-shallow", namelen)) {
 		if (!strcmp(value, "true"))
 			options.update_shallow = 1;
 		else if (!strcmp(value, "false"))
@@ -163,7 +163,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "pushcert")) {
+	} else if (!strncmp(name, "pushcert", namelen)) {
 		if (!strcmp(value, "true"))
 			options.push_cert = SEND_PACK_PUSH_CERT_ALWAYS;
 		else if (!strcmp(value, "false"))
@@ -173,7 +173,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "atomic")) {
+	} else if (!strncmp(name, "atomic", namelen)) {
 		if (!strcmp(value, "true"))
 			options.atomic = 1;
 		else if (!strcmp(value, "false"))
@@ -181,7 +181,7 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "push-option")) {
+	} else if (!strncmp(name, "push-option", namelen)) {
 		if (*value != '"')
 			string_list_append(&options.push_options, value);
 		else {
@@ -192,7 +192,7 @@ static int set_option(const char *name, const char *value)
 						 strbuf_detach(&unquoted, NULL));
 		}
 		return 0;
-	} else if (!strcmp(name, "family")) {
+	} else if (!strncmp(name, "family", namelen)) {
 		if (!strcmp(value, "ipv4"))
 			git_curl_ipresolve = CURL_IPRESOLVE_V4;
 		else if (!strcmp(value, "ipv6"))
@@ -202,16 +202,16 @@ static int set_option(const char *name, const char *value)
 		else
 			return -1;
 		return 0;
-	} else if (!strcmp(name, "from-promisor")) {
+	} else if (!strncmp(name, "from-promisor", namelen)) {
 		options.from_promisor = 1;
 		return 0;
-	} else if (!strcmp(name, "refetch")) {
+	} else if (!strncmp(name, "refetch", namelen)) {
 		options.refetch = 1;
 		return 0;
-	} else if (!strcmp(name, "filter")) {
+	} else if (!strncmp(name, "filter", namelen)) {
 		options.filter = xstrdup(value);
 		return 0;
-	} else if (!strcmp(name, "object-format")) {
+	} else if (!strncmp(name, "object-format", namelen)) {
 		options.object_format = 1;
 		if (strcmp(value, "true"))
 			die(_("unknown value for object-format: %s"), value);
@@ -1588,15 +1588,16 @@ int cmd_main(int argc, const char **argv)
 			parse_push(&buf);
 
 		} else if (skip_prefix(buf.buf, "option ", &arg)) {
-			char *value = strchr(arg, ' ');
+			const char *value = strchrnul(arg, ' ');
+			size_t arglen = value - arg;
 			int result;
 
-			if (value)
-				*value++ = '\0';
+			if (*value)
+				value++; /* skip over SP */
 			else
 				value = "true";
 
-			result = set_option(arg, value);
+			result = set_option(arg, arglen, value);
 			if (!result)
 				printf("ok\n");
 			else if (result < 0)
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:39 UTC · re: Patrick Steinhardt · lore

[PATCH v6 20/27] revision: always store allocated strings in output encoding

The `git_log_output_encoding` variable can be set via the `--encoding=` option. When doing so, we conditionally either assign it to the passed value, or if the value is "none" we assign it the empty string. Depending on which of the both code paths we pick though, the variable may end up being assigned either an allocated string or a string constant.

This is somewhat risky and may easily lead to bugs when a different code path may want to reassign a new value to it, freeing the previous value. We already to this when parsing the "i18n.logoutputencoding" config in `git_default_i18n_config()`. But because the config is typically parsed before we parse command line options this has been fine so far.

Regardless of that, safeguard the code such that the variable always contains an allocated string. While at it, also free the old value in case there was any to plug a potential memory leak.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 revision.c             | 3 ++-
 t/t3900-i18n-commit.sh | 1 +
 t/t3901-i18n-patch.sh  | 1 +
 3 files changed, 4 insertions(+), 1 deletion(-)
Show changes to 3 files +4 −1

revision.c, t/t3900-i18n-commit.sh, t/t3901-i18n-patch.sh

diff --git a/revision.c b/revision.c
index 7ddf0f151a..2ee6886078 100644
--- a/revision.c
+++ b/revision.c
@@ -2650,10 +2650,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 	} else if (!strcmp(arg, "--invert-grep")) {
 		revs->grep_filter.no_body_match = 1;
 	} else if ((argcount = parse_long_opt("encoding", argv, &optarg))) {
+		free(git_log_output_encoding);
 		if (strcmp(optarg, "none"))
 			git_log_output_encoding = xstrdup(optarg);
 		else
-			git_log_output_encoding = "";
+			git_log_output_encoding = xstrdup("");
 		return argcount;
 	} else if (!strcmp(arg, "--reverse")) {
 		revs->reverse ^= 1;
diff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh
index f27d09cfd9..db7b403bc1 100755
--- a/t/t3900-i18n-commit.sh
+++ b/t/t3900-i18n-commit.sh
@@ -5,6 +5,7 @@
 
 test_description='commit and log output encodings'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 compare_with () {
diff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh
index 4b37f78829..5f0b9afc3f 100755
--- a/t/t3901-i18n-patch.sh
+++ b/t/t3901-i18n-patch.sh
@@ -8,6 +8,7 @@ test_description='i18n settings and format-patch | am pipe'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 check_encoding () {
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:39 UTC · re: Patrick Steinhardt · lore

[PATCH v6 21/27] mailmap: always store allocated strings in mailmap blob

Same as with the preceding commit, the `git_mailmap_blob` may sometimes contain an allocated string and sometimes it may contain a string constant. This is risky and can easily lead to bugs in case the variable is getting re-assigned, where the code may then try to free the previous value to avoid memory leaks.

Safeguard the code by always storing allocated strings in the variable.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 mailmap.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to mailmap.c +1 −1
diff --git a/mailmap.c b/mailmap.c
index b2efe29b3d..3d1e092fef 100644
--- a/mailmap.c
+++ b/mailmap.c
@@ -216,7 +216,7 @@ int read_mailmap(struct string_list *map)
 	map->cmp = namemap_cmp;
 
 	if (!git_mailmap_blob && is_bare_repository())
-		git_mailmap_blob = "HEAD:.mailmap";
+		git_mailmap_blob = xstrdup("HEAD:.mailmap");
 
 	if (!startup_info->have_repository || !is_bare_repository())
 		err |= read_mailmap_file(map, ".mailmap",
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:39 UTC · re: Patrick Steinhardt · lore

[PATCH v6 22/27] imap-send: drop global `imap_server_conf` variable

In "imap-send.c", we have a global `sturct imap_server_conf` variable that keeps track of the configuration of the IMAP server. This variable is being populated mostly via the Git configuration.

Refactor the code to allocate the structure on the stack instead of having it globally. This change allows us to track its lifetime more closely.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 imap-send.c | 57 ++++++++++++++++++++++++++++-------------------------
 1 file changed, 30 insertions(+), 27 deletions(-)
Show changes to imap-send.c +30 −27
diff --git a/imap-send.c b/imap-send.c
index 8b723b34a5..67a7a6c456 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -82,10 +82,6 @@ struct imap_server_conf {
 	char *auth_method;
 };
 
-static struct imap_server_conf server = {
-	.ssl_verify = 1,
-};
-
 struct imap_socket {
 	int fd[2];
 	SSL *ssl;
@@ -110,6 +106,7 @@ struct imap {
 };
 
 struct imap_store {
+	const struct imap_server_conf *cfg;
 	/* currently open mailbox */
 	const char *name; /* foreign! maybe preset? */
 	int uidvalidity;
@@ -194,8 +191,8 @@ static void socket_perror(const char *func, struct imap_socket *sock, int ret)
 
 #ifdef NO_OPENSSL
 static int ssl_socket_connect(struct imap_socket *sock UNUSED,
-			      int use_tls_only UNUSED,
-			      int verify UNUSED)
+			      const struct imap_server_conf *cfg,
+			      int use_tls_only UNUSED)
 {
 	fprintf(stderr, "SSL requested but SSL support not compiled in\n");
 	return -1;
@@ -250,7 +247,9 @@ static int verify_hostname(X509 *cert, const char *hostname)
 		     cname, hostname);
 }
 
-static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int verify)
+static int ssl_socket_connect(struct imap_socket *sock,
+			      const struct imap_server_conf *cfg,
+			      int use_tls_only)
 {
 #if (OPENSSL_VERSION_NUMBER >= 0x10000000L)
 	const SSL_METHOD *meth;
@@ -279,7 +278,7 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
 	if (use_tls_only)
 		SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
 
-	if (verify)
+	if (cfg->ssl_verify)
 		SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
 
 	if (!SSL_CTX_set_default_verify_paths(ctx)) {
@@ -306,9 +305,9 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
 	 * OpenSSL does not document this function, but the implementation
 	 * returns 1 on success, 0 on failure after calling SSLerr().
 	 */
-	ret = SSL_set_tlsext_host_name(sock->ssl, server.host);
+	ret = SSL_set_tlsext_host_name(sock->ssl, cfg->host);
 	if (ret != 1)
-		warning("SSL_set_tlsext_host_name(%s) failed.", server.host);
+		warning("SSL_set_tlsext_host_name(%s) failed.", cfg->host);
 #endif
 
 	ret = SSL_connect(sock->ssl);
@@ -317,12 +316,12 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
 		return -1;
 	}
 
-	if (verify) {
+	if (cfg->ssl_verify) {
 		/* make sure the hostname matches that of the certificate */
 		cert = SSL_get_peer_certificate(sock->ssl);
 		if (!cert)
 			return error("unable to get peer certificate.");
-		if (verify_hostname(cert, server.host) < 0)
+		if (verify_hostname(cert, cfg->host) < 0)
 			return -1;
 	}
 
@@ -895,7 +894,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)
 	int ret;
 	char *response;
 
-	response = cram(prompt, server.user, server.pass);
+	response = cram(prompt, ctx->cfg->user, ctx->cfg->pass);
 
 	ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));
 	if (ret != strlen(response))
@@ -935,6 +934,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
 
 	CALLOC_ARRAY(ctx, 1);
 
+	ctx->cfg = srvc;
 	ctx->imap = CALLOC_ARRAY(imap, 1);
 	imap->buf.sock.fd[0] = imap->buf.sock.fd[1] = -1;
 	imap->in_progress_append = &imap->in_progress;
@@ -1035,7 +1035,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
 		imap->buf.sock.fd[1] = dup(s);
 
 		if (srvc->use_ssl &&
-		    ssl_socket_connect(&imap->buf.sock, 0, srvc->ssl_verify)) {
+		    ssl_socket_connect(&imap->buf.sock, srvc, 0)) {
 			close(s);
 			goto bail;
 		}
@@ -1068,8 +1068,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
 		if (!srvc->use_ssl && CAP(STARTTLS)) {
 			if (imap_exec(ctx, NULL, "STARTTLS") != RESP_OK)
 				goto bail;
-			if (ssl_socket_connect(&imap->buf.sock, 1,
-					       srvc->ssl_verify))
+			if (ssl_socket_connect(&imap->buf.sock, srvc, 1))
 				goto bail;
 			/* capabilities may have changed, so get the new capabilities */
 			if (imap_exec(ctx, NULL, "CAPABILITY") != RESP_OK)
@@ -1299,23 +1298,24 @@ static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, int *ofs)
 static int git_imap_config(const char *var, const char *val,
 			   const struct config_context *ctx, void *cb)
 {
+	struct imap_server_conf *cfg = cb;
 
 	if (!strcmp("imap.sslverify", var))
-		server.ssl_verify = git_config_bool(var, val);
+		cfg->ssl_verify = git_config_bool(var, val);
 	else if (!strcmp("imap.preformattedhtml", var))
-		server.use_html = git_config_bool(var, val);
+		cfg->use_html = git_config_bool(var, val);
 	else if (!strcmp("imap.folder", var))
-		return git_config_string(&server.folder, var, val);
+		return git_config_string(&cfg->folder, var, val);
 	else if (!strcmp("imap.user", var))
-		return git_config_string(&server.user, var, val);
+		return git_config_string(&cfg->user, var, val);
 	else if (!strcmp("imap.pass", var))
-		return git_config_string(&server.pass, var, val);
+		return git_config_string(&cfg->pass, var, val);
 	else if (!strcmp("imap.tunnel", var))
-		return git_config_string(&server.tunnel, var, val);
+		return git_config_string(&cfg->tunnel, var, val);
 	else if (!strcmp("imap.authmethod", var))
-		return git_config_string(&server.auth_method, var, val);
+		return git_config_string(&cfg->auth_method, var, val);
 	else if (!strcmp("imap.port", var))
-		server.port = git_config_int(var, val, ctx->kvi);
+		cfg->port = git_config_int(var, val, ctx->kvi);
 	else if (!strcmp("imap.host", var)) {
 		if (!val) {
 			return config_error_nonbool(var);
@@ -1324,11 +1324,11 @@ static int git_imap_config(const char *var, const char *val,
 				val += 5;
 			else if (starts_with(val, "imaps:")) {
 				val += 6;
-				server.use_ssl = 1;
+				cfg->use_ssl = 1;
 			}
 			if (starts_with(val, "//"))
 				val += 2;
-			server.host = xstrdup(val);
+			cfg->host = xstrdup(val);
 		}
 	} else
 		return git_default_config(var, val, ctx, cb);
@@ -1497,12 +1497,15 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,
 
 int cmd_main(int argc, const char **argv)
 {
+	struct imap_server_conf server = {
+		.ssl_verify = 1,
+	};
 	struct strbuf all_msgs = STRBUF_INIT;
 	int total;
 	int nongit_ok;
 
 	setup_git_directory_gently(&nongit_ok);
-	git_config(git_imap_config, NULL);
+	git_config(git_imap_config, &server);
 
 	argc = parse_options(argc, (const char **)argv, "", imap_send_options, imap_send_usage, 0);
 
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:39 UTC · re: Patrick Steinhardt · lore

[PATCH v6 23/27] imap-send: fix leaking memory in `imap_server_conf`

We never free any of the config strings that we populate into the `struct imap_server_conf`. Fix this by creating a common exit path where we can free resources.

While at it, drop the unused member `imap_server_conf::name`.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 imap-send.c | 65 ++++++++++++++++++++++++++++++++++-------------------
 1 file changed, 42 insertions(+), 23 deletions(-)
Show changes to imap-send.c +42 −23
diff --git a/imap-send.c b/imap-send.c
index 67a7a6c456..da3e7ec17e 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -69,7 +69,6 @@ static void imap_warn(const char *, ...);
 static char *next_arg(char **);
 
 struct imap_server_conf {
-	const char *name;
 	char *tunnel;
 	char *host;
 	int port;
@@ -1300,23 +1299,28 @@ static int git_imap_config(const char *var, const char *val,
 {
 	struct imap_server_conf *cfg = cb;
 
-	if (!strcmp("imap.sslverify", var))
+	if (!strcmp("imap.sslverify", var)) {
 		cfg->ssl_verify = git_config_bool(var, val);
-	else if (!strcmp("imap.preformattedhtml", var))
+	} else if (!strcmp("imap.preformattedhtml", var)) {
 		cfg->use_html = git_config_bool(var, val);
-	else if (!strcmp("imap.folder", var))
+	} else if (!strcmp("imap.folder", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->folder, var, val);
-	else if (!strcmp("imap.user", var))
+	} else if (!strcmp("imap.user", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->user, var, val);
-	else if (!strcmp("imap.pass", var))
+	} else if (!strcmp("imap.pass", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->pass, var, val);
-	else if (!strcmp("imap.tunnel", var))
+	} else if (!strcmp("imap.tunnel", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->tunnel, var, val);
-	else if (!strcmp("imap.authmethod", var))
+	} else if (!strcmp("imap.authmethod", var)) {
+		FREE_AND_NULL(cfg->folder);
 		return git_config_string(&cfg->auth_method, var, val);
-	else if (!strcmp("imap.port", var))
+	} else if (!strcmp("imap.port", var)) {
 		cfg->port = git_config_int(var, val, ctx->kvi);
-	else if (!strcmp("imap.host", var)) {
+	} else if (!strcmp("imap.host", var)) {
 		if (!val) {
 			return config_error_nonbool(var);
 		} else {
@@ -1330,8 +1334,9 @@ static int git_imap_config(const char *var, const char *val,
 				val += 2;
 			cfg->host = xstrdup(val);
 		}
-	} else
+	} else {
 		return git_default_config(var, val, ctx, cb);
+	}
 
 	return 0;
 }
@@ -1503,6 +1508,7 @@ int cmd_main(int argc, const char **argv)
 	struct strbuf all_msgs = STRBUF_INIT;
 	int total;
 	int nongit_ok;
+	int ret;
 
 	setup_git_directory_gently(&nongit_ok);
 	git_config(git_imap_config, &server);
@@ -1529,42 +1535,55 @@ int cmd_main(int argc, const char **argv)
 
 	if (!server.folder) {
 		fprintf(stderr, "no imap store specified\n");
-		return 1;
+		ret = 1;
+		goto out;
 	}
 	if (!server.host) {
 		if (!server.tunnel) {
 			fprintf(stderr, "no imap host specified\n");
-			return 1;
+			ret = 1;
+			goto out;
 		}
-		server.host = "tunnel";
+		server.host = xstrdup("tunnel");
 	}
 
 	/* read the messages */
 	if (strbuf_read(&all_msgs, 0, 0) < 0) {
 		error_errno(_("could not read from stdin"));
-		return 1;
+		ret = 1;
+		goto out;
 	}
 
 	if (all_msgs.len == 0) {
 		fprintf(stderr, "nothing to send\n");
-		return 1;
+		ret = 1;
+		goto out;
 	}
 
 	total = count_messages(&all_msgs);
 	if (!total) {
 		fprintf(stderr, "no messages to send\n");
-		return 1;
+		ret = 1;
+		goto out;
 	}
 
 	/* write it to the imap server */
 
 	if (server.tunnel)
-		return append_msgs_to_imap(&server, &all_msgs, total);
-
+		ret = append_msgs_to_imap(&server, &all_msgs, total);
 #ifdef USE_CURL_FOR_IMAP_SEND
-	if (use_curl)
-		return curl_append_msgs_to_imap(&server, &all_msgs, total);
+	else if (use_curl)
+		ret = curl_append_msgs_to_imap(&server, &all_msgs, total);
 #endif
-
-	return append_msgs_to_imap(&server, &all_msgs, total);
+	else
+		ret = append_msgs_to_imap(&server, &all_msgs, total);
+
+out:
+	free(server.tunnel);
+	free(server.host);
+	free(server.folder);
+	free(server.user);
+	free(server.pass);
+	free(server.auth_method);
+	return ret;
 }
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:39 UTC · re: Patrick Steinhardt · lore

[PATCH v6 24/27] builtin/rebase: do not assign default backend to non-constant field

The `struct rebase_options::default_backend` field is a non-constant string, but is being assigned a constant via `REBASE_OPTIONS_INIT`. Fix this by using `xstrdup()` to assign the variable and introduce a new function `rebase_options_release()` that releases memory held by the structure, including the newly-allocated variable.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rebase.c | 25 ++++++++++++++++---------
 1 file changed, 16 insertions(+), 9 deletions(-)
Show changes to builtin/rebase.c +16 −9
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 14d4f0a5e6..adc990b55e 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -135,7 +135,7 @@ struct rebase_options {
 		.type = REBASE_UNSPECIFIED,	  	\
 		.empty = EMPTY_UNSPECIFIED,	  	\
 		.keep_empty = 1,			\
-		.default_backend = "merge",	  	\
+		.default_backend = xstrdup("merge"),  	\
 		.flags = REBASE_NO_QUIET, 		\
 		.git_am_opts = STRVEC_INIT,		\
 		.exec = STRING_LIST_INIT_NODUP,		\
@@ -151,6 +151,19 @@ struct rebase_options {
 		.strategy_opts = STRING_LIST_INIT_NODUP,\
 	}
 
+static void rebase_options_release(struct rebase_options *opts)
+{
+	free(opts->default_backend);
+	free(opts->reflog_action);
+	free(opts->head_name);
+	strvec_clear(&opts->git_am_opts);
+	free(opts->gpg_sign_opt);
+	string_list_clear(&opts->exec, 0);
+	free(opts->strategy);
+	string_list_clear(&opts->strategy_opts, 0);
+	strbuf_release(&opts->git_format_patch_opt);
+}
+
 static struct replay_opts get_replay_opts(const struct rebase_options *opts)
 {
 	struct replay_opts replay = REPLAY_OPTS_INIT;
@@ -796,6 +809,7 @@ static int rebase_config(const char *var, const char *value,
 	}
 
 	if (!strcmp(var, "rebase.backend")) {
+		FREE_AND_NULL(opts->default_backend);
 		return git_config_string(&opts->default_backend, var, value);
 	}
 
@@ -1833,14 +1847,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 cleanup:
 	strbuf_release(&buf);
 	strbuf_release(&revisions);
-	free(options.reflog_action);
-	free(options.head_name);
-	strvec_clear(&options.git_am_opts);
-	free(options.gpg_sign_opt);
-	string_list_clear(&options.exec, 0);
-	free(options.strategy);
-	string_list_clear(&options.strategy_opts, 0);
-	strbuf_release(&options.git_format_patch_opt);
+	rebase_options_release(&options);
 	free(squash_onto_name);
 	free(keep_base_onto_name);
 	return !!ret;
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:39 UTC · re: Patrick Steinhardt · lore

[PATCH v6 25/27] builtin/rebase: always store allocated string in `options.strategy`

The `struct rebase_options::strategy` field is a `char *`, but we do end up assigning string constants to it in two cases:

  - When being passed a `--strategy=` option via the command line.
  - When being passed a strategy option via `--strategy-option=`, but
    not a strategy.
This will cause warnings once we enable `-Wwrite-strings`.

Ideally, we'd just convert the field to be a `const char *`. But we also assign to this field via the GIT_TEST_MERGE_ALGORITHM envvar, which we have to strdup(3P) into it.

Instead, refactor the code to make sure that we only ever assign allocated strings to this field.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rebase.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)
Show changes to builtin/rebase.c +7 −7
diff --git a/builtin/rebase.c b/builtin/rebase.c
index adc990b55e..4506bae768 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1061,6 +1061,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 {
 	struct rebase_options options = REBASE_OPTIONS_INIT;
 	const char *branch_name;
+	const char *strategy_opt = NULL;
 	int ret, flags, total_argc, in_progress = 0;
 	int keep_base = 0;
 	int ok_to_skip_pre_rebase = 0;
@@ -1175,7 +1176,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			PARSE_OPT_OPTARG, parse_opt_rebase_merges),
 		OPT_BOOL(0, "fork-point", &options.fork_point,
 			 N_("use 'merge-base --fork-point' to refine upstream")),
-		OPT_STRING('s', "strategy", &options.strategy,
+		OPT_STRING('s', "strategy", &strategy_opt,
 			   N_("strategy"), N_("use the given merge strategy")),
 		OPT_STRING_LIST('X', "strategy-option", &options.strategy_opts,
 				N_("option"),
@@ -1484,13 +1485,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		}
 	}
 
-	if (options.strategy_opts.nr && !options.strategy)
-		options.strategy = "ort";
-
-	if (options.strategy) {
-		options.strategy = xstrdup(options.strategy);
+	if (strategy_opt)
+		options.strategy = xstrdup(strategy_opt);
+	else if (options.strategy_opts.nr && !options.strategy)
+		options.strategy = xstrdup("ort");
+	if (options.strategy)
 		imply_merge(&options, "--strategy");
-	}
 
 	if (options.root && !options.onto_name)
 		imply_merge(&options, "--root without --onto");
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:39 UTC · re: Patrick Steinhardt · lore

[PATCH v6 26/27] builtin/merge: always store allocated strings in `pull_twohead`

The `pull_twohead` configuration may sometimes contain an allocated string, and sometimes it may contain a string constant. Refactor this to instead always store an allocated string such that we can release its resources without risk.

While at it, manage the lifetime of other config strings, as well. Note that we explicitly don't free `cleanup_arg` here. This is because the variable may be assigned a string constant via command line options.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/merge.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)
Show changes to builtin/merge.c +11 −7
diff --git a/builtin/merge.c b/builtin/merge.c
index daed2d4e1e..fb3eb15b89 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -611,17 +611,19 @@ static int git_merge_config(const char *k, const char *v,
 		return 0;
 	}
 
-	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
+	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat")) {
 		show_diffstat = git_config_bool(k, v);
-	else if (!strcmp(k, "merge.verifysignatures"))
+	} else if (!strcmp(k, "merge.verifysignatures")) {
 		verify_signatures = git_config_bool(k, v);
-	else if (!strcmp(k, "pull.twohead"))
+	} else if (!strcmp(k, "pull.twohead")) {
+		FREE_AND_NULL(pull_twohead);
 		return git_config_string(&pull_twohead, k, v);
-	else if (!strcmp(k, "pull.octopus"))
+	} else if (!strcmp(k, "pull.octopus")) {
+		FREE_AND_NULL(pull_octopus);
 		return git_config_string(&pull_octopus, k, v);
-	else if (!strcmp(k, "commit.cleanup"))
+	} else if (!strcmp(k, "commit.cleanup")) {
 		return git_config_string(&cleanup_arg, k, v);
-	else if (!strcmp(k, "merge.ff")) {
+	} else if (!strcmp(k, "merge.ff")) {
 		int boolval = git_parse_maybe_bool(v);
 		if (0 <= boolval) {
 			fast_forward = boolval ? FF_ALLOW : FF_NO;
@@ -1294,7 +1296,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 	if (!pull_twohead) {
 		char *default_strategy = getenv("GIT_TEST_MERGE_ALGORITHM");
 		if (default_strategy && !strcmp(default_strategy, "ort"))
-			pull_twohead = "ort";
+			pull_twohead = xstrdup("ort");
 	}
 
 	init_diff_ui_defaults();
@@ -1793,6 +1795,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 	}
 	strbuf_release(&buf);
 	free(branch_to_free);
+	free(pull_twohead);
+	free(pull_octopus);
 	discard_index(the_repository->index);
 	return ret;
 }
-- 
2.45.2.436.gcd77e87115.dirty
Patrick Steinhardt· Jun 7, 2024, 06:39 UTC · re: Patrick Steinhardt · lore

[PATCH v6 27/27] config.mak.dev: enable `-Wwrite-strings` warning

Writing to string constants is undefined behaviour and must be avoided in C. Even so, the compiler does not help us with this by default because those constants are not in fact marked as `const`. This makes it rather easy to accidentally assign a constant to a non-const variable or field and then later on try to either free it or write to it.

Enable `-Wwrite-strings` to catch such mistakes. With this warning enabled, the type of string constants is changed to `const char[]` and will thus cause compiler warnings when being assigned to non-const fields and variables.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 config.mak.dev | 1 +
 1 file changed, 1 insertion(+)
Show changes to config.mak.dev +1 −0
diff --git a/config.mak.dev b/config.mak.dev
index 981304727c..1ce4c70613 100644
--- a/config.mak.dev
+++ b/config.mak.dev
@@ -37,6 +37,7 @@ DEVELOPER_CFLAGS += -Wpointer-arith
 DEVELOPER_CFLAGS += -Wstrict-prototypes
 DEVELOPER_CFLAGS += -Wunused
 DEVELOPER_CFLAGS += -Wvla
+DEVELOPER_CFLAGS += -Wwrite-strings
 DEVELOPER_CFLAGS += -fno-common
 
 ifneq ($(filter clang4,$(COMPILER_FEATURES)),)
-- 
2.45.2.436.gcd77e87115.dirty
Junio C Hamano· Jun 7, 2024, 17:34 UTC · re: Patrick Steinhardt · lore

Re: [PATCH v6 00/27] Compile with `-Wwrite-strings`

Patrick Steinhardt <ps@pks.im> writes:
> The only change is in patch 12, where we now allocate a buffer later in
> time to avoid a useless allocation and the need for a call to free.
That makes sense (and is closer to the original anyway).
Will queue.  Thanks.

← back to recent threads