115 lines
2.8 KiB
C
115 lines
2.8 KiB
C
#include "stdlib_p.h"
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
|
|
int __strto_int(struct __scanf_input *input, int base, long *outl,
|
|
long long *outll, bool use_unsigned, int N)
|
|
{
|
|
/* Skip initial whitespace */
|
|
while(isspace(__scanf_peek(input))) __scanf_in(input);
|
|
|
|
if(N <= 0)
|
|
return EOF;
|
|
|
|
/* Accept a sign character */
|
|
bool negative = false;
|
|
int sign = __scanf_peek(input);
|
|
if(sign == '-') negative = true;
|
|
if(sign == '-' || sign == '+') __scanf_in_limit(input, &N);
|
|
|
|
/* Use unsigned variables as only these have defined overflow */
|
|
unsigned long xl = 0;
|
|
unsigned long long xll = 0;
|
|
|
|
int errno_value = 0;
|
|
bool valid = false;
|
|
|
|
/* Read prefixes and determine base */
|
|
if(__scanf_peek(input) == '0') {
|
|
__scanf_in_limit(input, &N);
|
|
if((base == 0 || base == 16) &&
|
|
tolower(__scanf_peek(input)) == 'x') {
|
|
__scanf_in_limit(input, &N);
|
|
base = 16;
|
|
}
|
|
/* If we don't consume the x then count the 0 as a digit */
|
|
else valid = true;
|
|
if(base == 0)
|
|
base = 8;
|
|
}
|
|
if(!valid && (N <= 0 || __scanf_peek(input) == EOF))
|
|
return EOF;
|
|
if(base == 0)
|
|
base = 10;
|
|
|
|
/* Read digits */
|
|
while(N > 0) {
|
|
int v = -1;
|
|
int c = __scanf_peek(input);
|
|
if(isdigit(c)) v = c - '0';
|
|
if(islower(c)) v = c - 'a' + 10;
|
|
if(v == -1 || v >= base) break;
|
|
|
|
/* The value is valid as long as there is at least one digit */
|
|
valid = true;
|
|
|
|
/* (x = base*x + v) but with overflow checks */
|
|
/* TODO: strto_int: We might fail to represent [L]LONG_MIN */
|
|
if(outl) {
|
|
if(__builtin_umull_overflow(xl, base, &xl))
|
|
errno_value = ERANGE;
|
|
if(__builtin_uaddl_overflow(xl, v, &xl))
|
|
errno_value = ERANGE;
|
|
}
|
|
if(outll) {
|
|
if(__builtin_umulll_overflow(xll, base, &xll))
|
|
errno_value = ERANGE;
|
|
if(__builtin_uaddll_overflow(xll, v, &xll))
|
|
errno_value = ERANGE;
|
|
}
|
|
|
|
__scanf_in_limit(input, &N);
|
|
}
|
|
|
|
/* Handle sign and range */
|
|
if(negative) {
|
|
/* Only -0 can be represented as unsigned */
|
|
if(outl && use_unsigned && xl != 0)
|
|
errno_value = ERANGE;
|
|
if(outll && use_unsigned && xll != 0)
|
|
errno_value = ERANGE;
|
|
|
|
/* Handle signed range. Don't use -[L]LONG_MIN, it overflows */
|
|
if(outl && !use_unsigned && xl > LONG_MAX * 1ul + 1)
|
|
errno_value = ERANGE;
|
|
if(outll && !use_unsigned && xll > LLONG_MAX * 1ull + 1)
|
|
errno_value = ERANGE;
|
|
|
|
xl = -xl;
|
|
xll = -xll;
|
|
}
|
|
else {
|
|
if(outl && !use_unsigned && xl > LONG_MAX * 1ul)
|
|
errno_value = ERANGE;
|
|
if(outll && !use_unsigned && xll > LLONG_MAX * 1ull)
|
|
errno_value = ERANGE;
|
|
}
|
|
|
|
/* Handle overflows */
|
|
if(outl && errno_value == ERANGE) {
|
|
if(use_unsigned) xl = ULONG_MAX;
|
|
else xl = negative ? LONG_MIN : LONG_MAX;
|
|
}
|
|
if(outll && errno_value == ERANGE) {
|
|
if(use_unsigned) xll = ULLONG_MAX;
|
|
else xll = negative ? LLONG_MIN : LLONG_MAX;
|
|
}
|
|
|
|
if(outl) *outl = xl;
|
|
if(outll) *outll = xll;
|
|
|
|
return valid ? errno_value : EINVAL;
|
|
}
|