git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH 1/3] log-tree: take ownership of pointer

From
Jeff King <peff@peff.net>
Date
Mar 13, 2024, 06:54 UTC
Message-ID
<20240313065454.GB125150@coredump.intra.peff.net>
In-Reply-To
<73a4cb87-2800-4ad1-b7a2-33c6465fcc50@app.fastmail.com>
On Tue, Mar 12, 2024 at 06:43:55PM +0100, Kristoffer Haugsbakk wrote:
Show 6 quoted lines
> > Hmm, OK. This patch by itself introduces a memory leak. It would be nice
> > if we could couple it with the matching free() so that we can see that
> > the issue is fixed. It sounds like your patch 2 is going to introduce
> > such a free, but I'm not sure it's complete.
> 
> Is it okay if it is done in patch 2?

I don't think it's the end of the world to do it in patch 2, as long as we end up in a good spot. But IMHO it's really hard for reviewers to understand what is going on, because it's intermingled with so many other changes. It would be much easier to read if we had a preparatory patch that switched the memory ownership of the field, and then built on top of that.

But I recognize that sometimes that's hard to do, because the state is so tangled that the functional change is what untangles it. I'm not sure if that's the case here or not; you'd probably have a better idea as somebody who looked carefully at it recently.

Show 14 quoted lines
> > It frees the old extra_headers before reassigning it, but nobody
> > cleans it up after handling the final commit.
> 
> I didn’t get any leak errors from the CI. `extra_headers` in `show_log`
> is populated by calling `log_write_email_headers`. Then later it is
> assigned to
> 
>     ctx.after_subject = extra_headers;
> 
> Then `ctx.after_subject is freed later
> 
>     free((char *)ctx.after_subject);
> 
> Am I missing something?

Ah, I see. I was confused by looking for a free of an extra_headers field. We have rev_info.extra_headers, and that is _not_ owned by rev_info. We used to assign that to a variable in log_write_email_headers(), but now we actually make a copy of it. And so the copy is freed in that function when we replace it with a version containing extra mime headers here:

                  strbuf_addf(&subject_buffer,
                           "%s"
                           "MIME-Version: 1.0\n"
                           "Content-Type: multipart/mixed;"
                           " boundary=\"%s%s\"\n"
                           "\n"
                           "This is a multi-part message in MIME "
                           "format.\n"
                           "--%s%s\n"
                           "Content-Type: text/plain; "
                           "charset=UTF-8; format=fixed\n"
                           "Content-Transfer-Encoding: 8bit\n\n",
                           extra_headers ? extra_headers : "",
                           mime_boundary_leader, opt->mime_boundary,
                           mime_boundary_leader, opt->mime_boundary);
                  free((char *)extra_headers);
                  extra_headers = strbuf_detach(&subject_buffer, NULL);

But the actual ownership is passed out via the extra_headers_p variable, and that is what is assigned to ctx.after_subject (which now takes ownership).

I think in the snippet I quoted above that extra_headers could never be NULL now, right? We'll always return at least an empty string. But moreover, we are formatting it into a strbuf, only to potentially copy it it another strbuf. Couldn't we just do it all in one strbuf?

Something like this:
 log-tree.c | 29 ++++++++---------------------
 1 file changed, 8 insertions(+), 21 deletions(-)
diff --git a/log-tree.c b/log-tree.c
index 9196b4f1d4..0a703a0303 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -469,29 +469,22 @@ void fmt_output_email_subject(struct strbuf *sb, struct rev_info *opt)
 	}
 }
 
-static char *extra_and_pe_headers(const char *extra_headers, const char *pe_headers) {
-	struct strbuf all_headers = STRBUF_INIT;
-
-	if (extra_headers)
-		strbuf_addstr(&all_headers, extra_headers);
-	if (pe_headers) {
-		strbuf_addstr(&all_headers, pe_headers);
-	}
-	return strbuf_detach(&all_headers, NULL);
-}
-
 void log_write_email_headers(struct rev_info *opt, struct commit *commit,
 			     const char **extra_headers_p,
 			     int *need_8bit_cte_p,
 			     int maybe_multipart)
 {
-	const char *extra_headers =
-		extra_and_pe_headers(opt->extra_headers, opt->pe_headers);
+	struct strbuf headers = STRBUF_INIT;
 	const char *name = oid_to_hex(opt->zero_commit ?
 				      null_oid() : &commit->object.oid);
 
 	*need_8bit_cte_p = 0; /* unknown */
 
+	if (opt->extra_headers)
+		strbuf_addstr(&headers, opt->extra_headers);
+	if (opt->pe_headers)
+		strbuf_addstr(&headers, opt->pe_headers);
+
 	fprintf(opt->diffopt.file, "From %s Mon Sep 17 00:00:00 2001\n", name);
 	graph_show_oneline(opt->graph);
 	if (opt->message_id) {
@@ -508,16 +501,13 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit,
 		graph_show_oneline(opt->graph);
 	}
 	if (opt->mime_boundary && maybe_multipart) {
-		static struct strbuf subject_buffer = STRBUF_INIT;
 		static struct strbuf buffer = STRBUF_INIT;
 		struct strbuf filename =  STRBUF_INIT;
 		*need_8bit_cte_p = -1; /* NEVER */
 
-		strbuf_reset(&subject_buffer);
 		strbuf_reset(&buffer);
 
-		strbuf_addf(&subject_buffer,
-			 "%s"
+		strbuf_addf(&headers,
 			 "MIME-Version: 1.0\n"
 			 "Content-Type: multipart/mixed;"
 			 " boundary=\"%s%s\"\n"
@@ -528,11 +518,8 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit,
 			 "Content-Type: text/plain; "
 			 "charset=UTF-8; format=fixed\n"
 			 "Content-Transfer-Encoding: 8bit\n\n",
-			 extra_headers ? extra_headers : "",
 			 mime_boundary_leader, opt->mime_boundary,
 			 mime_boundary_leader, opt->mime_boundary);
-		free((char *)extra_headers);
-		extra_headers = strbuf_detach(&subject_buffer, NULL);
 
 		if (opt->numbered_files)
 			strbuf_addf(&filename, "%d", opt->nr);
@@ -552,7 +539,7 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit,
 		opt->diffopt.stat_sep = buffer.buf;
 		strbuf_release(&filename);
 	}
-	*extra_headers_p = extra_headers;
+	*extra_headers_p = headers.len ? strbuf_detach(&headers, NULL) : NULL;
 }
 
 static void show_sig_lines(struct rev_info *opt, int status, const char *bol)


The resulting code is shorter and (IMHO) easier to understand. It
avoids an extra allocation and copy when using mime. It also avoids the
allocation of an empty string when opt->extra_headers and
opt->pe_headers are both NULL. It does make an extra copy when
extra_headers is non-NULL but pe_headers is NULL (and you're not using
MIME), as we could just use opt->extra_headers as-is, then. But since
the caller needs to take ownership, we can't avoid that copy.

I think you could even do this cleanup before adding pe_headers,
especially if it was coupled with cleaning up the memory ownership
issues.

-Peff
Previous: Kristoffer HaugsbakkNext: Kristoffer Haugsbakk
Message 5 of 34 in “format-patch: teach `--header-cmd`”
  1. 0/3 format-patch: teach `--header-cmd`Kristoffer Haugsbakk, Mar 7, 2024
  2. 1/3 log-tree: take ownership of pointerKristoffer Haugsbakk, Mar 7, 2024
  3. Jeff KingMar 12, 2024
  4. Kristoffer HaugsbakkMar 12, 2024
  5. Jeff KingMar 13, 2024
  6. Kristoffer HaugsbakkMar 13, 2024
  7. 2/3 format-patch: teach `--header-cmd`Kristoffer Haugsbakk, Mar 7, 2024
  8. Kristoffer HaugsbakkMar 8, 2024
  9. Jean-Noël AvilaMar 11, 2024
  10. Kristoffer HaugsbakkMar 12, 2024
  11. 3/3 format-patch: check if header output looks validKristoffer Haugsbakk, Mar 7, 2024
  12. 0/3 format-patch: teach `--header-cmd`Kristoffer Haugsbakk, Mar 19, 2024
  13. 1/3 revision: add a per-email field to rev-infoKristoffer Haugsbakk, Mar 19, 2024
  14. Jeff KingMar 19, 2024
  15. Kristoffer HaugsbakkMar 19, 2024
  16. Jeff KingMar 20, 2024
  17. 1/6 shortlog: stop setting pp.print_email_subjectJeff King, Mar 20, 2024
  18. 2/6 pretty: split oneline and email subject printingJeff King, Mar 20, 2024
  19. Kristoffer HaugsbakkMar 22, 2024
  20. 3/6 pretty: drop print_email_subject flagJeff King, Mar 20, 2024
  21. 4/6 log: do not set up extra_headers for non-email formatsJeff King, Mar 20, 2024
  22. Kristoffer HaugsbakkMar 22, 2024
  23. 5/6 format-patch: return an allocated string from log_write_email_headers()Jeff King, Mar 20, 2024
  24. Kristoffer HaugsbakkMar 22, 2024
  25. 6/6 format-patch: simplify after-subject MIME header handlingJeff King, Mar 20, 2024
  26. Kristoffer HaugsbakkMar 22, 2024
  27. Jeff KingMar 20, 2024
  28. Kristoffer HaugsbakkMar 22, 2024
  29. 7/6 format-patch: fix leak of empty header stringJeff King, Mar 22, 2024
  30. Kristoffer HaugsbakkMar 22, 2024
  31. Junio C HamanoMar 22, 2024
  32. Kristoffer HaugsbakkMar 22, 2024
  33. 2/3 format-patch: teach `--header-cmd`Kristoffer Haugsbakk, Mar 19, 2024
  34. 3/3 format-patch: check if header output looks validKristoffer Haugsbakk, Mar 19, 2024

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.