Remove arbitrary limits on intcmp integers

We don't need to parse strings into C integer values to compare them.

* src/function.c (parse_textint): Find boundaries of a numeric string.
(func_intcmp): Use parse_textint() to compare integers textually.
* tests/scripts/functions/intcmp: Test with extra-large numbers.
This commit is contained in:
Paul Eggert 2021-12-19 14:48:14 -05:00 committed by Paul Smith
parent 9230bfb9ae
commit 7192d0ec4a
2 changed files with 82 additions and 24 deletions

View File

@ -1276,6 +1276,49 @@ func_sort (char *o, char **argv, const char *funcname UNUSED)
return o;
}
/*
Traverse NUMBER consisting of optional leading white space, optional
sign, digits, and optional trailing white space.
If number is not of the proper form, diagnose with MSG. Otherwise,
return the address of of the first character after NUMBER, store
into *SIGN an integer consistent with the number's sign (-1, 0, or 1)
and store into *NUMSTART the address of NUMBER's first nonzero digit
(if NUMBER contains only zero digits, store the address of the first
character after NUMBER).
*/
static const char *
parse_textint (const char *number, const char *msg,
int *sign, const char **numstart)
{
const char *after_sign, *after_number;
const char *p = next_token (number);
int negative = *p == '-';
int nonzero = 0;
if (*p == '\0')
OS (fatal, *expanding_var, _("%s: empty value"), msg);
p += negative || *p == '+';
after_sign = p;
while (*p == '0')
p++;
*numstart = p;
while (ISDIGIT (*p))
if (*p++ == '0')
nonzero = 1;
after_number = p;
*sign = negative ? -nonzero : nonzero;
/* Check for extra non-whitespace stuff after the value. */
if (after_number == after_sign || *next_token (p) != '\0')
OSS (fatal, *expanding_var, "%s: '%s'", msg, number);
return after_number;
}
/*
$(intcmp lhs,rhs[,lt-part[,eq-part[,gt-part]]])
@ -1293,34 +1336,40 @@ func_sort (char *o, char **argv, const char *funcname UNUSED)
static char *
func_intcmp (char *o, char **argv, const char *funcname UNUSED)
{
int lsign, rsign;
const char *lnum, *rnum;
char *lhs_str = expand_argument (argv[0], NULL);
char *rhs_str = expand_argument (argv[1], NULL);
long long lhs, rhs;
const char *llim = parse_textint (lhs_str, _("non-numeric first argument to 'intcmp' function"), &lsign, &lnum);
const char *rlim = parse_textint (rhs_str, _("non-numeric second argument to 'intcmp' function"), &rsign, &rnum);
ptrdiff_t llen = llim - lnum;
ptrdiff_t rlen = rlim - rnum;
int cmp = lsign - rsign;
lhs = parse_numeric (lhs_str,
_("invalid first argument to 'intcmp' function"));
rhs = parse_numeric (rhs_str,
_("invalid second argument to 'intcmp' function"));
free (lhs_str);
free (rhs_str);
if (cmp == 0)
{
cmp = (llen > rlen) - (llen < rlen);
if (cmp == 0)
cmp = memcmp (lnum, rnum, llen);
}
argv += 2;
if (*argv == NULL)
/* Handle the special case where there are only two arguments. */
if (!*argv && cmp == 0)
{
if (lhs == rhs)
{
char buf[INTSTR_LENGTH+1];
sprintf (buf, "%lld", lhs);
o = variable_buffer_output(o, buf, strlen (buf));
}
return o;
if (lsign < 0)
o = variable_buffer_output (o, "-", 1);
o = variable_buffer_output(o, lnum - !lsign, llen + !lsign);
}
if (lhs >= rhs)
free (lhs_str);
free (rhs_str);
if (*argv && cmp >= 0)
{
++argv;
if (lhs > rhs && *argv && *(argv + 1))
if (cmp > 0 && *argv && *(argv + 1))
++argv;
}

View File

@ -12,6 +12,7 @@ z = 0
p = 1000000000
min = -9223372036854775808
max = 9223372036854775807
huge = 8857889956778499040639527525992734031025567913257255490371761260681427
.RECIPEPREFIX = >
all:
> @echo 0_1 $(intcmp $n,$n)
@ -31,28 +32,36 @@ all:
> @echo 4_1 $(intcmp $(max),$(min),lt,eq,gt)
> @echo 4_2 $(intcmp $(min),$(min),lt,eq,gt)
> @echo 4_3 $(intcmp $(max),$(max),lt,eq,gt)
', '', "0_1 -10\n0_2\n0_3\n1_1\n1_2 lt\n1_3\n2_1 lt\n2_2 ge\n2_3 ge\n3_0\n3_1 lt\n3_2 gt\n3_3 eq\n4_0 lt\n4_1 gt\n4_2 eq\n4_3 eq\n");
> @echo 5_0 $(intcmp -$(huge),$(huge),lt,eq,gt)
> @echo 5_1 $(intcmp $(huge),-$(huge),lt,eq,gt)
> @echo 5_2 $(intcmp -$(huge),-$(huge),lt,eq,gt)
> @echo 5_3 $(intcmp +$(huge),$(huge),lt,eq,gt)
', '', "0_1 -10\n0_2\n0_3\n1_1\n1_2 lt\n1_3\n2_1 lt\n2_2 ge\n2_3 ge\n3_0\n3_1 lt\n3_2 gt\n3_3 eq\n4_0 lt\n4_1 gt\n4_2 eq\n4_3 eq\n5_0 lt\n5_1 gt\n5_2 eq\n5_3 eq\n");
# Test error conditions
run_make_test('
intcmp-e1: ; @echo $(intcmp 12a,1,foo)
intcmp-e2: ; @echo $(intcmp 0,,foo)
intcmp-e3: ; @echo $(intcmp -1,9999999999999999999,foo)
intcmp-e4: ; @echo $(intcmp -1)
intcmp-e5: ; @echo $(intcmp ,55)',
intcmp-e3: ; @echo $(intcmp -1,)
intcmp-e4: ; @echo $(intcmp ,55)',
'intcmp-e1',
"#MAKEFILE#:2: *** invalid first argument to 'intcmp' function: '12a'. Stop.",
"#MAKEFILE#:2: *** non-numeric first argument to 'intcmp' function: '12a'. Stop.",
512);
run_make_test(undef,
'intcmp-e2',
"#MAKEFILE#:3: *** invalid second argument to 'intcmp' function: empty value. Stop.",
"#MAKEFILE#:3: *** non-numeric second argument to 'intcmp' function: empty value. Stop.",
512);
run_make_test(undef,
'intcmp-e3',
"#MAKEFILE#:4: *** invalid second argument to 'intcmp' function: '9999999999999999999' out of range. Stop.",
"#MAKEFILE#:4: *** non-numeric second argument to 'intcmp' function: empty value. Stop.",
512);
run_make_test(undef,
'intcmp-e4',
"#MAKEFILE#:5: *** non-numeric first argument to 'intcmp' function: empty value. Stop.",
512);