reimplement strverscmp to fix corner cases
authorRich Felker <dalias@aerifal.cx>
Tue, 23 Jun 2015 00:12:25 +0000 (00:12 +0000)
committerRich Felker <dalias@aerifal.cx>
Tue, 23 Jun 2015 00:29:57 +0000 (00:29 +0000)
this interface is non-standardized and is a GNU invention, and as
such, our implementation should match the behavior of the GNU
function. one peculiarity the old implementation got wrong was the
handling of all-zero digit sequences: they are supposed to compare
greater than digit sequences of which they are a proper prefix, as in
009 < 00.

in addition, high bytes were treated with char signedness rather than
as unsigned. this was wrong regardless of what the GNU function does
since the resulting order relation varied by arch.

the new strverscmp implementation makes explicit the cases where the
order differs from what strcmp would produce, of which there are only
two.

src/string/strverscmp.c

index 6f37cc6801fa8094cbdbc9080cf8f3b74661502e..4daf276d0cfeb0725649141b8a16a52f89794710 100644 (file)
@@ -2,40 +2,33 @@
 #include <ctype.h>
 #include <string.h>
 
-int strverscmp(const char *l, const char *r)
+int strverscmp(const char *l0, const char *r0)
 {
-       int haszero=1;
-       while (*l==*r) {
-               if (!*l) return 0;
+       const unsigned char *l = (const void *)l0;
+       const unsigned char *r = (const void *)r0;
+       size_t i, dp, j;
+       int z = 1;
 
-               if (*l=='0') {
-                       if (haszero==1) {
-                               haszero=0;
-                       }
-               } else if (isdigit(*l)) {
-                       if (haszero==1) {
-                               haszero=2;
-                       }
-               } else {
-                       haszero=1;
-               }
-               l++; r++;
+       /* Find maximal matching prefix and track its maximal digit
+        * suffix and whether those digits are all zeros. */
+       for (dp=i=0; l[i]==r[i]; i++) {
+               int c = l[i];
+               if (!c) return 0;
+               if (!isdigit(c)) dp=i+1, z=1;
+               else if (c!='0') z=0;
        }
-       if (haszero==1 && (*l=='0' || *r=='0')) {
-               haszero=0;
-       }
-       if ((isdigit(*l) && isdigit(*r) ) && haszero) {
-               size_t lenl=0, lenr=0;
-               while (isdigit(l[lenl]) ) lenl++;
-               while (isdigit(r[lenr]) ) lenr++;
-               if (lenl==lenr) {
-                       return (*l -  *r);
-               } else if (lenl>lenr) {
-                       return 1;
-               } else {
-                       return -1;
-               }
-       } else {
-               return (*l -  *r);
+
+       if (l[dp]!='0' && r[dp]!='0') {
+               /* If we're not looking at a digit sequence that began
+                * with a zero, longest digit string is greater. */
+               for (j=i; isdigit(l[j]); j++)
+                       if (!isdigit(r[j])) return 1;
+               if (isdigit(r[j])) return -1;
+       } else if (z && dp<i && (isdigit(l[i]) || isdigit(r[i]))) {
+               /* Otherwise, if common prefix of digit sequence is
+                * all zeros, digits order less than non-digits. */
+               return (unsigned char)(l[i]-'0') - (unsigned char)(r[i]-'0');
        }
+
+       return l[i] - r[i];
 }