diff mbox series

[2/9] ref-filter: avoid extra copies of payload/signature

Message ID 20240909231228.GB921834@coredump.intra.peff.net (mailing list archive)
State Accepted
Commit 72916999288d7cd40a1f82d7878b31fa15c4fdb2
Headers show
Series ref-filter %(trailer) fixes | expand

Commit Message

Jeff King Sept. 9, 2024, 11:12 p.m. UTC
When we know we're going to show the subject or body of a tag or commit,
we call find_subpos(), which returns pointers and lengths for the three
parts: subject, body, signature.

Oddly, the function finds the signature twice: once by calling
parse_signature() at the start, which copies the signature into a
separate strbuf, and then again by calling parse_signed_buffer() after
we've parsed past the subject.

This is due to 482c119186 (gpg-interface: improve interface for parsing
tags, 2021-02-11) and 88bce0e24c (ref-filter: hoist signature parsing,
2021-02-11). The idea is that in a multi-hash world, tag signatures may
appear in the header, rather than at the end of the body, in which case
we need to extract them into a separate buffer.

But parse_signature() would never find such a buffer! It only looks for
signature lines (like "-----BEGIN PGP") at the start of each line,
without any header keyword. So this code will never find anything except
the usual in-body signature.

And the extra code has two downsides:

  1. We spend time copying the payload and signature into strbufs. That
     might even be useful if we ended up with a NUL-terminated copy of
     the payload data, but we throw it away immediately. And the
     signature, since it comes at the end of the message, is already its
     own NUL-terminated buffer.

     The overhead isn't huge, but I measured a pretty consistent 1-2%
     speedup running "git for-each-ref --format='%(subject)'" with this
     patch on a clone of linux.git.

  2. The output of find_subpos() is a set of three ptr/len combinations,
     but only two of them point into the original buffer. This makes the
     interface confusing: you can't do pointer comparisons between them,
     and you have to remember to free the signature buffer. Since
     there's only one caller, it's not too bad in practice, but it did
     bite me while working on the next patch (and simplifying it will
     pave the way for that).

In the long run we might have to go back to something like this
approach, if we do have multi-hash header signatures. But I would argue
that the extra buffer should kick in only for a header signature, and be
passed out of find_subpos() separately.

Signed-off-by: Jeff King <peff@peff.net>
---
This should produce no behavior change.

It does seem funny to me that we do this signature parsing for commits
as well as tags, even through the former would have an in-header
signature. So arguably we are wrongly cutting in-body signatures from
commits, and not showing their signatures with %(contents:signature).
(But note that %(signature) is its own thing and does handle commits
correctly).

So I don't know if we'd want to fix that or not, but I left it as-is in
this series. And as I said above, if we did want to go that way, I think
we'd still want to build it on top of this, so that find_subpos()
returns the in-header and in-body signatures separately.

 ref-filter.c | 11 +++--------
 1 file changed, 3 insertions(+), 8 deletions(-)

Comments

Patrick Steinhardt Sept. 10, 2024, 6:09 a.m. UTC | #1
On Mon, Sep 09, 2024 at 07:12:28PM -0400, Jeff King wrote:
> When we know we're going to show the subject or body of a tag or commit,
> we call find_subpos(), which returns pointers and lengths for the three
> parts: subject, body, signature.
> 
> Oddly, the function finds the signature twice: once by calling
> parse_signature() at the start, which copies the signature into a
> separate strbuf, and then again by calling parse_signed_buffer() after
> we've parsed past the subject.
> 
> This is due to 482c119186 (gpg-interface: improve interface for parsing
> tags, 2021-02-11) and 88bce0e24c (ref-filter: hoist signature parsing,
> 2021-02-11). The idea is that in a multi-hash world, tag signatures may
> appear in the header, rather than at the end of the body, in which case
> we need to extract them into a separate buffer.
> 
> But parse_signature() would never find such a buffer! It only looks for
> signature lines (like "-----BEGIN PGP") at the start of each line,
> without any header keyword. So this code will never find anything except
> the usual in-body signature.

Okay. So in other words the intent was to parse in-header signatures,
but the code failed to do so correctly and thus this never worked in the
first place?

In any case, `parse_signature()` is only a glorified wrapper around
`parse_signed_buffer()` in the first place, so in the end they would
both parse the buffer in the same way.

Nice cleanup, even though it leaves one wondering why the in-header
signatures have only been wired up partially.

Patrick
Jeff King Sept. 10, 2024, 6:26 a.m. UTC | #2
On Tue, Sep 10, 2024 at 08:09:22AM +0200, Patrick Steinhardt wrote:

> > But parse_signature() would never find such a buffer! It only looks for
> > signature lines (like "-----BEGIN PGP") at the start of each line,
> > without any header keyword. So this code will never find anything except
> > the usual in-body signature.
> 
> Okay. So in other words the intent was to parse in-header signatures,
> but the code failed to do so correctly and thus this never worked in the
> first place?
> 
> In any case, `parse_signature()` is only a glorified wrapper around
> `parse_signed_buffer()` in the first place, so in the end they would
> both parse the buffer in the same way.
> 
> Nice cleanup, even though it leaves one wondering why the in-header
> signatures have only been wired up partially.

I, too, was confused. See this exchange with brian:

  https://lore.kernel.org/git/20240908233636.GA4026999@coredump.intra.peff.net/

-Peff
diff mbox series

Patch

diff --git a/ref-filter.c b/ref-filter.c
index b6c6c10127..0f5513ba7e 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -1833,16 +1833,10 @@  static void find_subpos(const char *buf,
 			size_t *nonsiglen,
 			const char **sig, size_t *siglen)
 {
-	struct strbuf payload = STRBUF_INIT;
-	struct strbuf signature = STRBUF_INIT;
 	const char *eol;
 	const char *end = buf + strlen(buf);
 	const char *sigstart;
 
-	/* parse signature first; we might not even have a subject line */
-	parse_signature(buf, end - buf, &payload, &signature);
-	strbuf_release(&payload);
-
 	/* skip past header until we hit empty line */
 	while (*buf && *buf != '\n') {
 		eol = strchrnul(buf, '\n');
@@ -1853,8 +1847,10 @@  static void find_subpos(const char *buf,
 	/* skip any empty lines */
 	while (*buf == '\n')
 		buf++;
-	*sig = strbuf_detach(&signature, siglen);
+	/* parse signature first; we might not even have a subject line */
 	sigstart = buf + parse_signed_buffer(buf, strlen(buf));
+	*sig = sigstart;
+	*siglen = end - *sig;
 
 	/* subject is first non-empty line */
 	*sub = buf;
@@ -2021,7 +2017,6 @@  static void grab_sub_body_contents(struct atom_value *val, int deref, struct exp
 			v->s = xstrdup(subpos);
 
 	}
-	free((void *)sigpos);
 }
 
 /*