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

[PATCH v2 0/3] get rid of sscanf() when shortening refs

From
Jeff King <peff@peff.net>
Date
Feb 15, 2023, 15:16 UTC
Message-ID
<Y+z3MtgayoXsxaHA@coredump.intra.peff.net>
In-Reply-To
<Y+vVFFCRem6t4IGM@coredump.intra.peff.net>
On Tue, Feb 14, 2023 at 01:38:13PM -0500, Jeff King wrote:
Show 6 quoted lines
> OK, here it is. I split it into a few patches to hopefully make it a bit
> easier to follow.
> 
>   [1/3]: shorten_unambiguous_ref(): avoid integer truncation
>   [2/3]: shorten_unambiguous_ref(): use NUM_REV_PARSE_RULES constant
>   [3/3]: shorten_unambiguous_ref(): avoid sscanf()

And here's a v2 fixing the "refs/headsXfoo" problem from v1. I also added a few tests to cover that case along with some others, including the original problem that spawned this thread.

I hope setting LC_ALL is OK in the test suite (i.e., at worst it just becomes a noop on platforms that don't have that locale). We can drop it, but then the test itself becomes kind of meaningless, as I do not think it would even fail on macOS. I would be curious to see if the test as written does fail with the current code (I lack access to a system to test).

In writing these, I noticed that this patch is actually fixing another bug in the original. ;) The "%s" match in scanf is greedy, so we'd never turn "refs/remotes/foo/HEAD" into "foo" (instead we say "foo/HEAD"). I doubt anybody cares very much, and you might even argue that "foo/HEAD", while not the shortest possible answer, is preferable because it's more descriptive. But I think "foo" matches the intent of the code, and certainly it's an unambiguous shortening.

Anyway, here's the patches and the range diff.
  [1/3]: shorten_unambiguous_ref(): avoid integer truncation
  [2/3]: shorten_unambiguous_ref(): use NUM_REV_PARSE_RULES constant
  [3/3]: shorten_unambiguous_ref(): avoid sscanf()
 refs.c                  | 93 +++++++++++++++++++++--------------------
 t/t1401-symbolic-ref.sh | 34 +++++++++++++++
 2 files changed, 82 insertions(+), 45 deletions(-)
1:  56762dc84b = 1:  f84edd1791 shorten_unambiguous_ref(): avoid integer truncation
2:  83326a396a = 2:  9287afb50f shorten_unambiguous_ref(): use NUM_REV_PARSE_RULES constant
3:  754ea4eb40 ! 3:  7cc6a8b89d shorten_unambiguous_ref(): avoid sscanf()
    @@ Commit message
         that against the refname, pulling the "%s" content into a separate
         buffer.
     
    -    This has two downsides:
    +    This has a few downsides:
     
           - sscanf("%s") reportedly misbehaves on macOS with some input and
             locale combinations, returning a partial or garbled string. See
             this thread:
     
               https://lore.kernel.org/git/CAGF3oAcCi+fG12j-1U0hcrWwkF5K_9WhOi6ZPHBzUUzfkrZDxA@mail.gmail.com/
     
    +      - scanf's matching of "%s" is greedy. So the "refs/remotes/%s/HEAD"
    +        rule would never pull "origin" out of "refs/remotes/origin/HEAD".
    +        Instead it always produced "origin/HEAD", which is redundant with
    +        the "refs/remotes/%s" rule.
    +
           - scanf in general is an error-prone interface. For example, scanning
             for "%s" will copy bytes into a destination string, which must have
             been correctly sized ahead of time to avoid a buffer overflow. In
    @@ Commit message
         to the lookup rules with "%.*s"), and then copy it only when returning
         to the caller.
     
    +    There are a few new tests here, all using symbolic-ref (the code can be
    +    triggered in many ways, but symrefs are convenient in that we don't need
    +    to create a real ref, which avoids any complications from the filesystem
    +    munging the name):
    +
    +      - the first covers the real-world case which misbehaved on macOS.
    +        Setting LC_ALL is required to trigger the problem there (since
    +        otherwise our tests use LC_ALL=C), and hopefully is at worst simply
    +        ignored on other systems (and doesn't cause libc to complain, etc,
    +        on systems without that locale).
    +
    +      - the second covers the "origin/HEAD" case as discussed above, which
    +        is now fixed
    +
    +      - the remainder are for "weird" cases that work both before and after
    +        this patch, but would be easy to get wrong with off-by-one problems
    +        in the parsing (and came out of discussions and earlier iterations
    +        of the patch that did get them wrong).
    +
    +      - absent here are tests of boring, expected-to-work cases like
    +        "refs/heads/foo", etc. Those are covered all over the test suite
    +        both explicitly (for-each-ref's refname:short) and implicitly (in
    +        the output of git-status, etc).
    +
         Reported-by: 孟子易 <mengziyi540841@gmail.com>
         Helped-by: Eric Sunshine <sunshine@sunshineco.com>
         Signed-off-by: Jeff King <peff@peff.net>
    @@ refs.c: int update_ref(const char *msg, const char *refname,
     -		size_t total_len = 0;
     -		size_t offset = 0;
     +	/*
    -+	 * Check that rule matches refname up to the first percent
    -+	 * in the rule. This is basically skip_prefix(), but
    -+	 * ending at percent in the prefix, rather than end-of-string.
    ++	 * Check that rule matches refname up to the first percent in the rule.
    ++	 * We can bail immediately if not, but otherwise we leave "rule" at the
    ++	 * %-placeholder, and "refname" at the start of the potential matched
    ++	 * name.
     +	 */
    -+	do {
    ++	while (*rule != '%') {
     +		if (!*rule)
     +			BUG("rev-parse rule did not have percent");
    -+		if (*rule == '%')
    -+			break;
    -+	} while (*refname++ == *rule++);
    ++		if (*refname++ != *rule++)
    ++			return NULL;
    ++	}
      
     -		for (i = 0; i < NUM_REV_PARSE_RULES; i++)
     -			/* -2 for strlen("%.*s") - strlen("%s"); +1 for NUL */
     -			total_len += strlen(ref_rev_parse_rules[i]) - 2 + 1;
     +	/*
    -+	 * Check that we matched all the way to the "%" placeholder,
    -+	 * and skip past it within the rule string. If so, "refname" at
    -+	 * this point is the beginning of a potential match.
    ++	 * Check that our "%" is the expected placeholder. This assumes there
    ++	 * are no other percents (placeholder or quoted) in the string, but
    ++	 * that is sufficient for our rev-parse rules.
     +	 */
     +	if (!skip_prefix(rule, "%.*s", &rule))
     +		return NULL;
    @@ refs.c: char *refs_shorten_unambiguous_ref(struct ref_store *refs,
      	return xstrdup(refname);
      }
      
    +
    + ## t/t1401-symbolic-ref.sh ##
    +@@ t/t1401-symbolic-ref.sh: test_expect_success 'symbolic-ref pointing at another' '
    + 	test_cmp expect actual
    + '
    + 
    ++test_expect_success 'symbolic-ref --short handles complex utf8 case' '
    ++	name="测试-加-增加-加-增加" &&
    ++	git symbolic-ref TEST_SYMREF "refs/heads/$name" &&
    ++	# In the real world, we saw problems with this case only
    ++	# when the locale includes UTF-8. Set it here to try to make things as
    ++	# hard as possible for us to pass, but in practice we should do the
    ++	# right thing regardless (and of course some platforms may not even
    ++	# have this locale).
    ++	LC_ALL=en_US.UTF-8 git symbolic-ref --short TEST_SYMREF >actual &&
    ++	echo "$name" >expect &&
    ++	test_cmp expect actual
    ++'
    ++
    ++test_expect_success 'symbolic-ref --short handles name with suffix' '
    ++	git symbolic-ref TEST_SYMREF "refs/remotes/origin/HEAD" &&
    ++	git symbolic-ref --short TEST_SYMREF >actual &&
    ++	echo "origin" >expect &&
    ++	test_cmp expect actual
    ++'
    ++
    ++test_expect_success 'symbolic-ref --short handles almost-matching name' '
    ++	git symbolic-ref TEST_SYMREF "refs/headsXfoo" &&
    ++	git symbolic-ref --short TEST_SYMREF >actual &&
    ++	echo "headsXfoo" >expect &&
    ++	test_cmp expect actual
    ++'
    ++
    ++test_expect_success 'symbolic-ref --short handles name with percent' '
    ++	git symbolic-ref TEST_SYMREF "refs/heads/%foo" &&
    ++	git symbolic-ref --short TEST_SYMREF >actual &&
    ++	echo "%foo" >expect &&
    ++	test_cmp expect actual
    ++'
    ++
    + test_done
Previous: Eric SunshineNext: Jeff King
Message 29 of 46 in “bug report: symbolic-ref --short command echos the wrong text while use Chinese language”
  1. 孟子易Feb 13, 2023
  2. Jeff KingFeb 13, 2023
  3. Eric SunshineFeb 13, 2023
  4. Jeff KingFeb 14, 2023
  5. Eric SunshineFeb 14, 2023
  6. Eric SunshineFeb 14, 2023
  7. Junio C HamanoFeb 14, 2023
  8. Eric SunshineFeb 14, 2023
  9. Junio C HamanoFeb 14, 2023
  10. Eric SunshineFeb 14, 2023
  11. Jeff KingFeb 14, 2023
  12. Eric SunshineFeb 14, 2023
  13. Jeff KingFeb 14, 2023
  14. 0/3 get rid of sscanf() when shortening refsJeff King, Feb 14, 2023
  15. 1/3 shorten_unambiguous_ref(): avoid integer truncationJeff King, Feb 14, 2023
  16. 2/3 shorten_unambiguous_ref(): use NUM_REV_PARSE_RULES constantJeff King, Feb 14, 2023
  17. Junio C HamanoFeb 14, 2023
  18. Jeff KingFeb 14, 2023
  19. 3/3 shorten_unambiguous_ref(): avoid sscanf()Jeff King, Feb 14, 2023
  20. Junio C HamanoFeb 14, 2023
  21. Junio C HamanoFeb 14, 2023
  22. Jeff KingFeb 14, 2023
  23. Junio C HamanoFeb 14, 2023
  24. Jeff KingFeb 14, 2023
  25. Junio C HamanoFeb 15, 2023
  26. Jeff KingFeb 15, 2023
  27. Junio C HamanoFeb 15, 2023
  28. Eric SunshineFeb 14, 2023
  29. 0/3 get rid of sscanf() when shortening refsJeff King, Feb 15, 2023
  30. 1/3 shorten_unambiguous_ref(): avoid integer truncationJeff King, Feb 15, 2023
  31. 2/3 shorten_unambiguous_ref(): use NUM_REV_PARSE_RULES constantJeff King, Feb 15, 2023
  32. 3/3 shorten_unambiguous_ref(): avoid sscanf()Jeff King, Feb 15, 2023
  33. Torsten BögershausenFeb 16, 2023
  34. Eric SunshineFeb 16, 2023
  35. Junio C HamanoFeb 16, 2023
  36. Jeff KingFeb 16, 2023
  37. Junio C HamanoFeb 16, 2023
  38. Jeff KingFeb 16, 2023
  39. Torsten BögershausenFeb 17, 2023
  40. Junio C HamanoFeb 15, 2023
  41. Junio C HamanoFeb 14, 2023
  42. Jeff KingFeb 14, 2023
  43. Torsten BögershausenFeb 15, 2023
  44. Eric SunshineFeb 15, 2023
  45. Torsten BögershausenFeb 15, 2023
  46. Eric SunshineFeb 16, 2023

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.