Usermode/libc - Fix over-use of 64-bit divide in strtoull
authorJohn Hodge <[email protected]>
Sat, 10 May 2014 07:38:58 +0000 (15:38 +0800)
committerJohn Hodge <[email protected]>
Sat, 10 May 2014 07:38:58 +0000 (15:38 +0800)
Usermode/Libraries/libc.so_src/TEST_strtoi.c
Usermode/Libraries/libc.so_src/strtoi.c

index 62f2e69..e065808 100644 (file)
@@ -41,6 +41,8 @@ int main(int argc, char *argv[])
        TST(unsigned long long, ull, 0, "01234567", 01234567, "%llo", 8, 0);
        TST(unsigned long long, ull, 0, "1234567", 1234567, "%lld", 7, 0);
        TST(long long, ll, 0, "-1", -1, "%lld", 2, 0);  // -1
+       TST(long long, ll, 0, "100113", 100113, "%lld", strlen(in), 0);
+       TST(long long, ll, 0, "0x101", 0x101, "0x%llx", strlen(in), 0);
        
        // Invalid strings
        TST(unsigned long long, ull, 0, "0x",  0, "%llx", 1, 0);        // Single 0
index db6684f..4fd2da5 100644 (file)
@@ -43,6 +43,10 @@ unsigned long long strtoull(const char *str, char **end, int base)
        if( base == 0 )
                base = 10;
 
+       // Value before getting within 1 digit of ULLONG_MAX
+       // - Used to avoid overflow in more accurate check
+       unsigned long long      max_before_ullong_max = ULLONG_MAX / base;
+       unsigned int    space_above = ULLONG_MAX - max_before_ullong_max * base;
        while( *str )
        {
                 int    next = -1;
@@ -58,22 +62,30 @@ unsigned long long strtoull(const char *str, char **end, int base)
                        if( 'a' <= *str && *str <= 'a'+base-10-1 )
                                next = *str - 'a' + 10;
                }
+               //_SysDebug("strtoull - ret=0x%llx,next=%i,str='%s'", ret, next, str);
                if( next < 0 )
                        break;
                
+               // If we're already out of range, keep eating
                if( ret == ULLONG_MAX ) {
                        errno = ERANGE;
                        str ++;
                        // Keep eating until first unrecognised character
                        continue;
                }
-               if( ret > (ULLONG_MAX-next)/base ) {
-                       //_SysDebug("strtoull - Out of range (0x%llx > 0x%llx)", ret, (ULLONG_MAX-next)/base);
-                       //_SysDebug("strtoull -              (%llu > %llu)", ret, (ULLONG_MAX-next)/base);
-                       ret = ULLONG_MAX;
-                       errno = ERANGE;
-                       str ++;
-                       continue;
+       
+               // Rough then accurate check against max value
+               if( ret >= max_before_ullong_max )
+               {
+                       //_SysDebug("strtoull - 0x%llx>0x%llx", ret, max_before_ullong_max);
+                       if( (ret - max_before_ullong_max) * base + next > space_above ) {
+                               //_SysDebug("strtoull - %u*%u+%u (%u) > %u",
+                               //      (unsigned int)(ret - max_before_ullong_max), base, next, space_above);
+                               ret = ULLONG_MAX;
+                               errno = ERANGE;
+                               str ++;
+                               continue;
+                       }
                }
                ret *= base;
                ret += next;

UCC git Repository :: git.ucc.asn.au