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

Re: [PATCH v2] utf8: use size_t for string width methods and callee sites.

From
Junio C Hamano <gitster@pobox.com>
Date
Jul 27, 2026, 00:06 UTC
Message-ID
<xmqqpl09s3cc.fsf@gitster.g>
In-Reply-To
<20260726195718.1914131-1-hardikxk@gmail.com>
Hardik Kumar <hardikxk@gmail.com> writes:
Show 11 quoted lines
> utf8_strwidth() and utf8_strnwidth() return int, even though the
> return value is always non-negative:
>
> - utf8_strnwidth() accumulates the width into a size_t and otherwise
>   returns its size_t len parameter,
> - utf8_strwidth() just forwards its result.
>
> Change their signatures to return size_t instead.
>
> Update the types of the variables the said method is used to avoid
> potential UB caused by implicit conversion from size_t to int.

The goal looks attractive on the surface, and the change to make utf8_strwidth() and utf8_strnwidth() return 'size_t' clears an existing TODO. However, the updates to the call sites to support this change introduce several bugs due to unsigned integer underflow and incorrect mixed-sign comparisons.

Consider just one example:
Show 14 quoted lines
> diff --git a/diff.c b/diff.c
> index 589c196..4887958 100644
> --- a/diff.c
> +++ b/diff.c
> @@ -2952,7 +2952,8 @@ static int utf8_ish_width(const char **start)
>  
>  static void show_stats(struct diffstat_t *data, struct diff_options *options)
>  {
> -	int i, len, add, del, adds = 0, dels = 0;
> +	int i, add, del, adds = 0, dels = 0;
> +	size_t len;
>  	uintmax_t max_change = 0, max_len = 0;
>  	int total_files = data->nr, count;
>  	int width, name_width, graph_width, number_width = 0, bin_width = 0;

The above change impacts code later in the function (among other things):

		/*
		 * "scale" the filename
		 */
		len = name_width;
		name_len = utf8_strwidth(name);
		if (name_width < name_len) {
			char *slash;
			prefix = "...";
			len -= 3;
			if (len < 0)
				len = 0;

Here, 'len' used to be an 'int', but now it is 'size_t', which is unsigned. The safeguard to prevent 'len' from going down to an unacceptably low value by clipping it to 0 never triggers, because 'if (len < 0)' can never be true. If len is less than 3, len -= 3 will result in a fairly large value, and the subsequent computation would go bananas to see a value with little relation to name_len.

Another example.
Show 19 quoted lines
> diff --git a/pretty.c b/pretty.c
> index d8a9f37..f7d392d 100644
> --- a/pretty.c
> +++ b/pretty.c
> @@ -1805,11 +1805,12 @@ static size_t format_and_pad_commit(struct strbuf *sb, /* in UTF-8 */
>  {
>  	struct strbuf local_sb = STRBUF_INIT;
>  	size_t total_consumed = 0;
> -	int len, padding = c->padding;
> +	int padding = c->padding;
> +	size_t len;
>  
>  	if (padding < 0) {
>  		const char *start = strrchr(sb->buf, '\n');
> -		int occupied;
> +		size_t occupied;
>  		if (!start)
>  			start = sb->buf;
>  		occupied = utf8_strnwidth(start, strlen(start), 1);

After this post-context, 'occupied' is incremented, and then we have this:

		padding = (-padding) - occupied;

If 'occupied' is sufficiently large, 'padding' can become negative here. Because padding remains an 'int' and can become negative, it impacts code a bit further down in the same function (among other similar comparisons):

	if (c->flush_type == flush_left_and_steal) {
		const char *ch = sb->buf + sb->len - 1;
		while (len > padding && ch > sb->buf) {
			const char *p;
			if (*ch == ' ') {
				ch--;
				padding++;
				continue;
			}

We compare 'len' and 'padding', first promoting 'padding' to 'size_t', so when 'padding' is negative, we compare 'len' with a fairly large number due to unsigned wraparound. We will fail to "steal" spaces as we will not loop here.

I will stop here. What makes reviewing this change so unpleasant is that on the surface, changing variable definitions to flip int to size_t looks pretty, yet the real breakage appears in places that are not shown in the patch at all.

So, this needs more work to become acceptable, I am afraid.
Previous: Hardik KumarNext: Junio C Hamano
Message 7 of 23 in “change utf8_strwidth() return type to size_t”
  1. change utf8_strwidth() return type to size_tHardik Kumar, Jul 26, 2026
  2. René ScharfeJul 26, 2026
  3. Hardik KumarJul 26, 2026
  4. Pablo SabaterJul 26, 2026
  5. Hardik KumarJul 26, 2026
  6. utf8: use size_t for string width methods and callee sites.Hardik Kumar, Jul 26, 2026
  7. Junio C HamanoJul 27, 2026
  8. Junio C HamanoJul 27, 2026
  9. Hardik KumarJul 27, 2026
  10. Pablo SabaterJul 27, 2026
  11. Hardik KumarJul 27, 2026
  12. utf8: make utf8_strwidth() and utf8_strnwidth() return size_tHardik Kumar, Jul 27, 2026
  13. Hardik KumarJul 27, 2026
  14. Phillip WoodJul 27, 2026
  15. Junio C HamanoJul 27, 2026
  16. Hardik KumarJul 27, 2026
  17. Junio C HamanoJul 27, 2026
  18. Hardik KumarJul 27, 2026
  19. utf8: replace utf8_strwidth todo with descriptive commentHardik Kumar, Jul 27, 2026
  20. Phillip WoodJul 28, 2026
  21. Hardik KumarJul 28, 2026
  22. Junio C HamanoJul 28, 2026
  23. utf8: replace utf8_strwidth todo with descriptive commentHardik Kumar, Jul 28, 2026

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.