From: Jeff King <peff@peff.net>
To: Michael J Gruber <git@grubix.eu>
Cc: Junio C Hamano <gitster@pobox.com>, git@vger.kernel.org
Subject: [PATCH 3/4] revision: avoid writing to const string for parent marks
Date: Thu, 26 Mar 2026 15:13:18 -0400 [thread overview]
Message-ID: <20260326191318.GC415796@coredump.intra.peff.net> (raw)
In-Reply-To: <20260326190243.GA412983@coredump.intra.peff.net>
We take in a "const char *", but may write a NUL into it when parsing
parent marks like "foo^-", since we want to isolate "foo" as a string
for further parsing. This is usually OK, as our "const" strings are
often actually argv strings which are technically writeable, but we'd
segfault with a string literal like:
handle_revision_arg("HEAD^-", &revs, 0, 0);
Similar to how we handled dotdot in a previous commit, we can avoid this
by making a temporary copy of the left-hand side of the string. The cost
should negligible compared to the rest of the parsing (like actually
parsing commits to create their parent linked-lists).
There is one slightly tricky thing, though. We parse some of the marks
progressively, so that if we see "foo^!" for example, we'll strip that
down to "foo" not just for calling add_parents_only(), but also for the
rest of the function. That makes sense since we eventually want to pass
"foo" to get_oid_with_context(). But it also means that we'll keep
looking for other marks. In particular, "foo^-^!" is valid, though oddly
"foo^!^-" would ignore the "^-". I'm not sure if this is a weird
historical artifact of the implementation, or if there are important
corner cases.
So I've left the behavior unchanged. Each mark we find allocates a
string with the mark stripped, which means we could allocate multiple
times (and carry a free-able pointer for each to the end). But in
practice we won't, because of the three marks, "^@" jumps immediately to
the end without further parsing, and "^-^!" is nonsense that nobody
would pass. So you'd get one allocation in general, and never more than
two.
Another obvious option would be to just copy "arg" up front and be OK
with munging it. But that means we pay the cost even when we find no
marks. We could make a single copy upon finding a mark and then munge,
but that adds extra code to each site (checking whether somebody else
allocated, and if not, adjusting our "mark" pointer to be relative to
the copied string).
I aimed for something that was clear and obvious, if a bit verbose.
Signed-off-by: Jeff King <peff@peff.net>
---
Also one other weirdness I noticed while proof-reading: if we
successfully parse a mark, we never restore the original string! So if
you call:
char buf[] = "foo^!";
handle_revision_arg(buf, &revs, 0, 0);
Then "buf" would have "foo\0!" after it returns. I guess no callers
care, because they only look at the arg again if there was an error.
But it incidentally is fixed by this patch.
revision.c | 25 +++++++++++++++----------
1 file changed, 15 insertions(+), 10 deletions(-)
diff --git a/revision.c b/revision.c
index f61262436f..fda405bf65 100644
--- a/revision.c
+++ b/revision.c
@@ -2147,7 +2147,10 @@ static int handle_dotdot(const char *arg,
static int handle_revision_arg_1(const char *arg_, struct rev_info *revs, int flags, unsigned revarg_opt)
{
struct object_context oc = {0};
- char *mark;
+ const char *mark;
+ char *arg_minus_at = NULL;
+ char *arg_minus_excl = NULL;
+ char *arg_minus_dash = NULL;
struct object *object;
struct object_id oid;
int local_flags;
@@ -2174,18 +2177,17 @@ static int handle_revision_arg_1(const char *arg_, struct rev_info *revs, int fl
mark = strstr(arg, "^@");
if (mark && !mark[2]) {
- *mark = 0;
- if (add_parents_only(revs, arg, flags, 0)) {
+ arg_minus_at = xmemdupz(arg, mark - arg);
+ if (add_parents_only(revs, arg_minus_at, flags, 0)) {
ret = 0;
goto out;
}
- *mark = '^';
}
mark = strstr(arg, "^!");
if (mark && !mark[2]) {
- *mark = 0;
- if (!add_parents_only(revs, arg, flags ^ (UNINTERESTING | BOTTOM), 0))
- *mark = '^';
+ arg_minus_excl = xmemdupz(arg, mark - arg);
+ if (add_parents_only(revs, arg_minus_excl, flags ^ (UNINTERESTING | BOTTOM), 0))
+ arg = arg_minus_excl;
}
mark = strstr(arg, "^-");
if (mark) {
@@ -2199,9 +2201,9 @@ static int handle_revision_arg_1(const char *arg_, struct rev_info *revs, int fl
}
}
- *mark = 0;
- if (!add_parents_only(revs, arg, flags ^ (UNINTERESTING | BOTTOM), exclude_parent))
- *mark = '^';
+ arg_minus_dash = xmemdupz(arg, mark - arg);
+ if (add_parents_only(revs, arg_minus_dash, flags ^ (UNINTERESTING | BOTTOM), exclude_parent))
+ arg = arg_minus_dash;
}
local_flags = 0;
@@ -2236,6 +2238,9 @@ static int handle_revision_arg_1(const char *arg_, struct rev_info *revs, int fl
out:
object_context_release(&oc);
+ free(arg_minus_at);
+ free(arg_minus_excl);
+ free(arg_minus_dash);
return ret;
}
--
2.53.0.1081.gf77a8b8145
next prev parent reply other threads:[~2026-03-26 19:13 UTC|newest]
Thread overview: 24+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-26 15:22 [PATCH 0/6] ISOC23: quell warnings on discarding const Michael J Gruber
2026-03-26 15:22 ` [PATCH 1/6] do not discard const: the simple cases Michael J Gruber
2026-03-26 17:34 ` Jeff King
2026-03-26 17:45 ` Junio C Hamano
2026-03-26 19:23 ` [PATCH] config: store allocated string in non-const pointer Jeff King
2026-03-26 15:22 ` [PATCH 2/6] do not discard const: make git-compat-util ISOC23-like Michael J Gruber
2026-03-26 15:22 ` [PATCH 3/6] do not discard const: adjust to non-const data types Michael J Gruber
2026-03-26 17:28 ` Junio C Hamano
2026-03-26 15:22 ` [PATCH 4/6] do not discard const: declare const where we stay const Michael J Gruber
2026-03-26 15:22 ` [PATCH 5/6] do not discard const: keep signature Michael J Gruber
2026-03-26 17:18 ` Junio C Hamano
2026-03-26 15:22 ` [PATCH 6/6] do not discard const: the ugly truth Michael J Gruber
2026-03-26 17:07 ` Junio C Hamano
2026-03-26 17:42 ` Jeff King
2026-03-26 19:02 ` [PATCH 0/4] fix const issues in revision parser Jeff King
2026-03-26 19:04 ` [PATCH 1/4] revision: make handle_dotdot() interface less confusing Jeff King
2026-03-26 19:28 ` Junio C Hamano
2026-03-26 23:14 ` Jeff King
2026-03-27 15:55 ` Junio C Hamano
2026-03-26 19:05 ` [PATCH 2/4] rev-parse: simplify dotdot parsing Jeff King
2026-03-26 19:13 ` Jeff King [this message]
2026-03-26 19:14 ` [PATCH 4/4] rev-parse: avoid writing to const string for parent marks Jeff King
2026-03-26 16:26 ` [PATCH 0/6] ISOC23: quell warnings on discarding const D. Ben Knoble
2026-03-27 17:45 ` Michael J Gruber
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260326191318.GC415796@coredump.intra.peff.net \
--to=peff@peff.net \
--cc=git@grubix.eu \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox