{"thread":{"id":"59042","subject":"[PATCH 0/2] some minor starts_with()/skip_prefix() cleanups","startedAt":"2023-01-07T13:24:24Z","lastAt":"2023-01-11T18:38:46Z","messageCount":6,"participants":["Jeff King","René Scharfe"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"469871","messageId":"Y7lyga5g2leSmWQd@coredump.intra.peff.net","threadId":"59042","inReplyTo":null,"subject":"[PATCH 0/2] some minor starts_with()/skip_prefix() cleanups","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2023-01-07T13:24:17Z","receivedAt":"2023-01-07T13:24:24Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"I happened to notice a recently-introduced strncmp() that could have\nbeen a starts_with(). So I grepped and found a few more, and this is the\nresult.\n\n  [1/2]: convert trivial uses of strncmp() to starts_with()\n  [2/2]: convert trivial uses of strncmp() to skip_prefix()\n\n builtin/remote-ext.c | 6 ++++--\n builtin/remote-fd.c  | 2 +-\n bundle-uri.c         | 2 +-\n ref-filter.c         | 2 +-\n urlmatch.c           | 6 +++---\n ws.c                 | 7 ++++---\n 6 files changed, 14 insertions(+), 11 deletions(-)\n\n-Peff\n"},{"id":"469872","messageId":"Y7ly+h+pTaDB4TSF@coredump.intra.peff.net","threadId":"59042","inReplyTo":"Y7lyga5g2leSmWQd@coredump.intra.peff.net","subject":"[PATCH 1/2] convert trivial uses of strncmp() to starts_with()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2023-01-07T13:26:18Z","receivedAt":"2023-01-07T13:26:23Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"It's more readable to use starts_with() instead of strncmp() to match a\nprefix, as the latter requires a manually-computed length, and has the\nfunny \"matching is zero\" return value common to cmp functions.  This\npatch converts several cases which were found with:\n\n  git grep 'strncmp(.*, [0-9]*)'\n\nBut note that it doesn't convert all such cases. There are several where\nthe magic length number is repeated elsewhere in the code, like:\n\n  /* handle \"buf\" which isn't NUL-terminated and might be too small */\n  if (len >= 3 && !strncmp(buf, \"foo\", 3))\n\nor:\n\n  /* exact match for \"foo\", but within a larger string */\n  if (end - buf == 3 && !strncmp(buf, \"foo\", 3))\n\nWhile it would not produce the wrong outcome to use starts_with() in\nthese cases, we'd still be left with one instance of \"3\". We're better\nto leave them for now, as the repeated \"3\" makes it clear that the two\nare linked (there may be other refactorings that handle both, but\nthey're out of scope for this patch).\n\nA few things to note while reading the patch:\n\n  - all cases but one are trying to match, and so lose the extra \"!\".\n    The case in the first hunk of urlmatch.c is not-matching, and hence\n    gains a \"!\".\n\n  - the case in remote-fd.c is matching the beginning of \"connect foo\",\n    but we never look at str+8 to parse the \"foo\" part (which would make\n    this a candidate for skip_prefix(), not starts_with()). This seems\n    at first glance like a bug, but is a limitation of how remote-fd\n    works.\n\n  - the second hunk in urlmatch.c shows some cases adjacent to other\n    strncmp() calls that are left. These are of the \"exact match within\n    a larger string\" type, as described above.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\nNo coccinelle rule for the reason given above that we don't want to just\nconvert all cases.\n\nI do think a starts_with_mem() could help some of those, but I left that\nfor another day.\n\n builtin/remote-fd.c | 2 +-\n bundle-uri.c        | 2 +-\n ref-filter.c        | 2 +-\n urlmatch.c          | 6 +++---\n 4 files changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/remote-fd.c b/builtin/remote-fd.c\nindex 91dfe07e06..b2a3980b1d 100644\n--- a/builtin/remote-fd.c\n+++ b/builtin/remote-fd.c\n@@ -40,7 +40,7 @@ static void command_loop(int input_fd, int output_fd)\n \t\tif (!strcmp(buffer, \"capabilities\")) {\n \t\t\tprintf(\"*connect\\n\\n\");\n \t\t\tfflush(stdout);\n-\t\t} else if (!strncmp(buffer, \"connect \", 8)) {\n+\t\t} else if (starts_with(buffer, \"connect \")) {\n \t\t\tprintf(\"\\n\");\n \t\t\tfflush(stdout);\n \t\t\tif (bidirectional_transfer_loop(input_fd,\ndiff --git a/bundle-uri.c b/bundle-uri.c\nindex 36268dda17..6462ab6deb 100644\n--- a/bundle-uri.c\n+++ b/bundle-uri.c\n@@ -620,7 +620,7 @@ static int config_to_packet_line(const char *key, const char *value, void *data)\n {\n \tstruct packet_reader *writer = data;\n \n-\tif (!strncmp(key, \"bundle.\", 7))\n+\tif (starts_with(key, \"bundle.\"))\n \t\tpacket_write_fmt(writer->fd, \"%s=%s\", key, value);\n \n \treturn 0;\ndiff --git a/ref-filter.c b/ref-filter.c\nindex a24324123e..f8203c6b05 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -1209,7 +1209,7 @@ static const char *copy_name(const char *buf)\n {\n \tconst char *cp;\n \tfor (cp = buf; *cp && *cp != '\\n'; cp++) {\n-\t\tif (!strncmp(cp, \" <\", 2))\n+\t\tif (starts_with(cp, \" <\"))\n \t\t\treturn xmemdupz(buf, cp - buf);\n \t}\n \treturn xstrdup(\"\");\ndiff --git a/urlmatch.c b/urlmatch.c\nindex b615adc923..620a648efc 100644\n--- a/urlmatch.c\n+++ b/urlmatch.c\n@@ -209,7 +209,7 @@ static char *url_normalize_1(const char *url, struct url_info *out_info, char al\n \t */\n \tif (!url_len || strchr(\":/?#\", *url)) {\n \t\t/* Missing host invalid for all URL schemes except file */\n-\t\tif (strncmp(norm.buf, \"file:\", 5)) {\n+\t\tif (!starts_with(norm.buf, \"file:\")) {\n \t\t\tif (out_info) {\n \t\t\t\tout_info->url = NULL;\n \t\t\t\tout_info->err = _(\"missing host and scheme is not 'file:'\");\n@@ -268,11 +268,11 @@ static char *url_normalize_1(const char *url, struct url_info *out_info, char al\n \t\tif (url == slash_ptr) {\n \t\t\t/* Skip \":\" port with no number, it's same as default */\n \t\t} else if (slash_ptr - url == 2 &&\n-\t\t\t   !strncmp(norm.buf, \"http:\", 5) &&\n+\t\t\t   starts_with(norm.buf, \"http:\") &&\n \t\t\t   !strncmp(url, \"80\", 2)) {\n \t\t\t/* Skip http :80 as it's the default */\n \t\t} else if (slash_ptr - url == 3 &&\n-\t\t\t   !strncmp(norm.buf, \"https:\", 6) &&\n+\t\t\t   starts_with(norm.buf, \"https:\") &&\n \t\t\t   !strncmp(url, \"443\", 3)) {\n \t\t\t/* Skip https :443 as it's the default */\n \t\t} else {\n-- \n2.39.0.469.g9000b9c396\n\n"},{"id":"469873","messageId":"Y7lzFG9gyDrOE4Xt@coredump.intra.peff.net","threadId":"59042","inReplyTo":"Y7lyga5g2leSmWQd@coredump.intra.peff.net","subject":"[PATCH 2/2] convert trivial uses of strncmp() to skip_prefix()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2023-01-07T13:26:44Z","receivedAt":"2023-01-07T13:26:49Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"As with the previous patch, using skip_prefix() is more readable and\nless error-prone than a raw strncmp(), because it avoids a\nmanually-computed length. These cases differ from the previous patch\nthat uses starts_with() because they care about the value after the\nmatched prefix.\n\nWe can convert these to use skip_prefix() by introducing an extra\nvariable to hold the out-pointer.\n\nNote in the case in ws.c that to get rid of the magic number \"9\"\ncompletely, we also switch out \"len\" for recomputing the pointer\ndifference. These are equivalent because \"len\" is always \"ep - string\".\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n builtin/remote-ext.c | 6 ++++--\n ws.c                 | 7 ++++---\n 2 files changed, 8 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/remote-ext.c b/builtin/remote-ext.c\nindex fd3538d4f0..ee338bf440 100644\n--- a/builtin/remote-ext.c\n+++ b/builtin/remote-ext.c\n@@ -169,6 +169,8 @@ static int command_loop(const char *child)\n \n \twhile (1) {\n \t\tsize_t i;\n+\t\tconst char *arg;\n+\n \t\tif (!fgets(buffer, MAXCOMMAND - 1, stdin)) {\n \t\t\tif (ferror(stdin))\n \t\t\t\tdie(\"Command input error\");\n@@ -182,10 +184,10 @@ static int command_loop(const char *child)\n \t\tif (!strcmp(buffer, \"capabilities\")) {\n \t\t\tprintf(\"*connect\\n\\n\");\n \t\t\tfflush(stdout);\n-\t\t} else if (!strncmp(buffer, \"connect \", 8)) {\n+\t\t} else if (skip_prefix(buffer, \"connect \", &arg)) {\n \t\t\tprintf(\"\\n\");\n \t\t\tfflush(stdout);\n-\t\t\treturn run_child(child, buffer + 8);\n+\t\t\treturn run_child(child, arg);\n \t\t} else {\n \t\t\tfprintf(stderr, \"Bad command\");\n \t\t\treturn 1;\ndiff --git a/ws.c b/ws.c\nindex 46a77bcad6..903bfcd53e 100644\n--- a/ws.c\n+++ b/ws.c\n@@ -29,6 +29,7 @@ unsigned parse_whitespace_rule(const char *string)\n \t\tint i;\n \t\tsize_t len;\n \t\tconst char *ep;\n+\t\tconst char *arg;\n \t\tint negated = 0;\n \n \t\tstring = string + strspn(string, \", \\t\\n\\r\");\n@@ -52,15 +53,15 @@ unsigned parse_whitespace_rule(const char *string)\n \t\t\t\trule |= whitespace_rule_names[i].rule_bits;\n \t\t\tbreak;\n \t\t}\n-\t\tif (strncmp(string, \"tabwidth=\", 9) == 0) {\n-\t\t\tunsigned tabwidth = atoi(string + 9);\n+\t\tif (skip_prefix(string, \"tabwidth=\", &arg)) {\n+\t\t\tunsigned tabwidth = atoi(arg);\n \t\t\tif (0 < tabwidth && tabwidth < 0100) {\n \t\t\t\trule &= ~WS_TAB_WIDTH_MASK;\n \t\t\t\trule |= tabwidth;\n \t\t\t}\n \t\t\telse\n \t\t\t\twarning(\"tabwidth %.*s out of range\",\n-\t\t\t\t\t(int)(len - 9), string + 9);\n+\t\t\t\t\t(int)(ep - arg), arg);\n \t\t}\n \t\tstring = ep;\n \t}\n-- \n2.39.0.469.g9000b9c396\n"},{"id":"469874","messageId":"Y7l0mq6khtlgxGca@coredump.intra.peff.net","threadId":"59042","inReplyTo":"Y7lzFG9gyDrOE4Xt@coredump.intra.peff.net","subject":"Re: [PATCH 2/2] convert trivial uses of strncmp() to skip_prefix()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2023-01-07T13:33:14Z","receivedAt":"2023-01-07T13:33:51Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sat, Jan 07, 2023 at 08:26:45AM -0500, Jeff King wrote:\n\n> Note in the case in ws.c that to get rid of the magic number \"9\"\n> completely, we also switch out \"len\" for recomputing the pointer\n> difference. These are equivalent because \"len\" is always \"ep - string\".\n\nBy the way, one thing I noticed about this parse_whitespace_rule()\nfunction is that it's very loose about its matching. It does:\n\n        for (i = 0; i < ARRAY_SIZE(whitespace_rule_names); i++) {\n                if (strncmp(whitespace_rule_names[i].rule_name,\n                            string, len))\n                        continue;\n\t\t...we matched...\n\t\tbreak;\n\t}\n\nSo it will prefix-match any of the options, even if there are\nambiguities. E.g.:\n\n  git -c core.whitespace=-t show\n\nwill turn off \"trailing-space\", even though it would also match\n\"tab-in-indent\". It would be easy enough to fix it to require the whole\nname, but I wasn't sure if this prefix-matching was supposed to be a\nfeature (it doesn't seem to be documented anywhere, though).\n\n-Peff\n"},{"id":"469893","messageId":"e525342d-5900-1870-f176-da4eef083143@web.de","threadId":"59042","inReplyTo":"Y7l0mq6khtlgxGca@coredump.intra.peff.net","subject":"Re: [PATCH 2/2] convert trivial uses of strncmp() to skip_prefix()","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2023-01-07T21:29:28Z","receivedAt":"2023-01-07T21:31:14Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 07.01.23 um 14:33 schrieb Jeff King:\n> On Sat, Jan 07, 2023 at 08:26:45AM -0500, Jeff King wrote:\n>\n>> Note in the case in ws.c that to get rid of the magic number \"9\"\n>> completely, we also switch out \"len\" for recomputing the pointer\n>> difference. These are equivalent because \"len\" is always \"ep - string\".\n>\n> By the way, one thing I noticed about this parse_whitespace_rule()\n> function is that it's very loose about its matching. It does:\n>\n>         for (i = 0; i < ARRAY_SIZE(whitespace_rule_names); i++) {\n>                 if (strncmp(whitespace_rule_names[i].rule_name,\n>                             string, len))\n>                         continue;\n> \t\t...we matched...\n> \t\tbreak;\n> \t}\n>\n> So it will prefix-match any of the options, even if there are\n> ambiguities. E.g.:\n>\n>   git -c core.whitespace=-t show\n>\n> will turn off \"trailing-space\", even though it would also match\n> \"tab-in-indent\". It would be easy enough to fix it to require the whole\n> name, but I wasn't sure if this prefix-matching was supposed to be a\n> feature (it doesn't seem to be documented anywhere, though).\n\nAbbreviations are being used:\n\n   $ git grep whitespace= .gitattributes\n   .gitattributes:* whitespace=!indent,trail,space\n   .gitattributes:*.[ch] whitespace=indent,trail,space diff=cpp\n   .gitattributes:*.sh whitespace=indent,trail,space eol=lf\n\n(Full names: trailing-space, space-before-tab, indent-with-non-tab.)\n\na9cc857ada (War on whitespace: first, a bit of retreat., 2007-11-02)\nadded this function.  Its commit message says:\n\n   \"You can specify the desired types of errors to be detected by\n    listing their names (unique abbreviations are accepted)\n    separated by comma.\"\n\nAnd the part about abbreviations needing to be unique was true at that\ntime, because the names of the only two types back then started with\ndifferent characters.  aeb84b05ae (core.whitespace: split trailing-space\ninto blank-at-{eol,eof}, 2009-09-05) broke that property first.\n\nThe current behavior of picking the first match from a list with a\nbasically random order is not ideal, but at least stable.  Can we still\nchange it?  The intent seems to have been to ignore ambiguous prefix\nmatches.\n\nRené\n\n"},{"id":"470097","messageId":"Y78CLLu9YN+MyY2/@coredump.intra.peff.net","threadId":"59042","inReplyTo":"e525342d-5900-1870-f176-da4eef083143@web.de","subject":"Re: [PATCH 2/2] convert trivial uses of strncmp() to skip_prefix()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2023-01-11T18:38:36Z","receivedAt":"2023-01-11T18:38:46Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sat, Jan 07, 2023 at 10:29:28PM +0100, René Scharfe wrote:\n\n> > So it will prefix-match any of the options, even if there are\n> > ambiguities. E.g.:\n> >\n> >   git -c core.whitespace=-t show\n> >\n> > will turn off \"trailing-space\", even though it would also match\n> > \"tab-in-indent\". It would be easy enough to fix it to require the whole\n> > name, but I wasn't sure if this prefix-matching was supposed to be a\n> > feature (it doesn't seem to be documented anywhere, though).\n> \n> Abbreviations are being used:\n> \n>    $ git grep whitespace= .gitattributes\n>    .gitattributes:* whitespace=!indent,trail,space\n>    .gitattributes:*.[ch] whitespace=indent,trail,space diff=cpp\n>    .gitattributes:*.sh whitespace=indent,trail,space eol=lf\n> \n> (Full names: trailing-space, space-before-tab, indent-with-non-tab.)\n\nAh, right, I should have checked to see if _we_ are using them before\nguessing whether anyone else might be.\n\n> a9cc857ada (War on whitespace: first, a bit of retreat., 2007-11-02)\n> added this function.  Its commit message says:\n> \n>    \"You can specify the desired types of errors to be detected by\n>     listing their names (unique abbreviations are accepted)\n>     separated by comma.\"\n\nThanks, I dug around for something like that but somehow missed it.\n\nSo yeah, we definitely want to keep this abbreviation feature working.\nThe only question is whether we ought to detect ambiguous ones. I think\nsomething like this would work, though I wonder if is even worth\nbothering about. I did not even see this in the wild, but it was just a\ncuriosity while I was adjusting something else in the function:\n\ndiff --git a/ws.c b/ws.c\nindex 46a77bcad6..f4efd66209 100644\n--- a/ws.c\n+++ b/ws.c\n@@ -29,6 +29,7 @@ unsigned parse_whitespace_rule(const char *string)\n \t\tint i;\n \t\tsize_t len;\n \t\tconst char *ep;\n+\t\tstruct whitespace_rule *matched = NULL;\n \t\tint negated = 0;\n \n \t\tstring = string + strspn(string, \", \\t\\n\\r\");\n@@ -43,15 +44,27 @@ unsigned parse_whitespace_rule(const char *string)\n \t\tif (!len)\n \t\t\tbreak;\n \t\tfor (i = 0; i < ARRAY_SIZE(whitespace_rule_names); i++) {\n-\t\t\tif (strncmp(whitespace_rule_names[i].rule_name,\n-\t\t\t\t    string, len))\n+\t\t\tstruct whitespace_rule *cur = &whitespace_rule_names[i];\n+\t\t\tif (strncmp(cur->rule_name, string, len))\n \t\t\t\tcontinue;\n+\t\t\tif (matched) {\n+\t\t\t\twarning(\"ignoring ambiguous whitespace rule '%.*s'\"\n+\t\t\t\t\t\" (matches '%s' and '%s')\",\n+\t\t\t\t\t(int)len, string,\n+\t\t\t\t\tmatched->rule_name, cur->rule_name);\n+\t\t\t\tmatched = NULL;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\tmatched = cur;\n+\t\t}\n+\n+\t\tif (matched) {\n \t\t\tif (negated)\n-\t\t\t\trule &= ~whitespace_rule_names[i].rule_bits;\n+\t\t\t\trule &= ~matched->rule_bits;\n \t\t\telse\n-\t\t\t\trule |= whitespace_rule_names[i].rule_bits;\n-\t\t\tbreak;\n+\t\t\t\trule |= matched->rule_bits;\n \t\t}\n+\n \t\tif (strncmp(string, \"tabwidth=\", 9) == 0) {\n \t\t\tunsigned tabwidth = atoi(string + 9);\n \t\t\tif (0 < tabwidth && tabwidth < 0100) {\n\n-Peff\n"}]}