gettext/gettext-runtime/intl/eval-plural.h
Bruno Haible 726bfb1d1c Avoid crash by SIGFPE during plural form expression evaluation.
Libraries (such as libintl and libgettextpo) should not raise signals, because
catching such signals cannot be done in a multithread-safe way.

* gettext-runtime/intl/plural-exp.h (enum eval_status): New enum items
PE_INTDIV, PE_INTOVF.
* gettext-runtime/intl/eval-plural.h (plural_eval_recurse): When a division
argument is zero, return status PE_INTDIV instead of raising SIGFPE.
* gettext-runtime/intl/dcigettext.c (INTDIV0_RAISES_SIGFPE): Remove macro.
Don't include <signal.h>.
* gettext-runtime/intl/configure.ac: Don't invoke gt_INTDIV0.
* gettext-runtime/intl/m4/intdiv0.m4: Remove file.
* gettext-runtime/intl/Makefile.am (ACLOCAL_AMFLAGS): Remove option '-I m4'.
(EXTRA_DIST): Remove m4/intdiv0.m4.
* autogen.sh: When invoking aclocal in gettext-runtime/intl, omit option
'-I m4'.

* gettext-tools/src/msgl-check.c: Don't include <setjmp.h>, <signal.h>.
(plural_expression_histogram): Don't invoke install_sigfpe_handler,
uninstall_sigfpe_handler.
(check_plural_eval): Don't invoke setjmp, install_sigfpe_handler,
uninstall_sigfpe_handler. Remove 'volatile' from array variable.
Produce error message when status PE_INTDIV or PE_INTOVF occurred.
* gettext-tools/src/plural-eval.h: Don't include <setjmp.h>.
(sigjmp_buf, sigsetjmp, siglongjmp): Remove definitions.
(USE_SIGINFO): Remove macro.
(sigfpe_exit, sigfpe_code, install_sigfpe_handler, uninstall_sigfpe_handler):
Remove declarations.
* gettext-tools/src/plural-eval.c: Don't include <signal.h>.
(sigfpe_exit, sigfpe_code): Remove variables.
(sigfpe_handler, install_sigfpe_handler, uninstall_sigfpe_handler): Remove
functions.
* gettext-tools/configure.ac: Don't invoke gt_SIGINFO.
* gettext-tools/m4/siginfo.m4: Remove file.
* gettext-tools/m4/Makefile.am (EXTRA_DIST): Remove it.
2023-10-06 16:25:03 +02:00

174 lines
5.6 KiB
C

/* Plural expression evaluation.
Copyright (C) 2000-2023 Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
#ifndef STATIC
#define STATIC static
#endif
/* While the bison parser is able to support expressions of a maximum depth
of YYMAXDEPTH = 10000, the runtime evaluation of a parsed plural expression
has a smaller maximum recursion depth.
If we did not limit the recursion depth, a program that just invokes
ngettext() on a thread other than the main thread could get a crash by
stack overflow in the following circumstances:
- On systems with glibc, after the stack size has been reduced,
e.g. on x86_64 systems after "ulimit -s 260".
This stack size is only sufficient for ca. 3919 recursions.
Cf. <https://unix.stackexchange.com/questions/620720/>
- On systems with musl libc, because there the thread stack size (for a
thread other than the main thread) by default is only 128 KB, see
<https://wiki.musl-libc.org/functional-differences-from-glibc.html#Thread-stack-size>.
- On AIX 7 systems, because there the thread stack size (for a thread
other than the main thread) by default is only 96 KB, see
<https://www.ibm.com/docs/en/aix/7.1?topic=programming-threads-library-options>.
This stack size is only sufficient for between 887 and 1363 recursions,
depending on the compiler and compiler optimization options.
A maximum depth of 100 is a large enough for all practical needs
and also small enough to avoid stack overflow even with small thread stack
sizes. */
#ifndef EVAL_MAXDEPTH
# define EVAL_MAXDEPTH 100
#endif
/* A shorthand that denotes a successful evaluation result with a value V. */
#define OK(v) (struct eval_result) { .status = PE_OK, .value = (v) }
/* Evaluates a plural expression PEXP for n=N, up to ALLOWED_DEPTH. */
static struct eval_result
plural_eval_recurse (const struct expression *pexp, unsigned long int n,
unsigned int allowed_depth)
{
if (allowed_depth == 0)
/* The allowed recursion depth is exhausted. */
return (struct eval_result) { .status = PE_STACKOVF };
allowed_depth--;
switch (pexp->nargs)
{
case 0:
switch (pexp->operation)
{
case var:
return OK (n);
case num:
return OK (pexp->val.num);
default:
break;
}
/* NOTREACHED */
break;
case 1:
{
/* pexp->operation must be lnot. */
struct eval_result arg =
plural_eval_recurse (pexp->val.args[0], n, allowed_depth);
if (arg.status != PE_OK)
return arg;
return OK (! arg.value);
}
case 2:
{
struct eval_result leftarg =
plural_eval_recurse (pexp->val.args[0], n, allowed_depth);
if (leftarg.status != PE_OK)
return leftarg;
if (pexp->operation == lor)
{
if (leftarg.value)
return OK (1);
struct eval_result rightarg =
plural_eval_recurse (pexp->val.args[1], n, allowed_depth);
if (rightarg.status != PE_OK)
return rightarg;
return OK (rightarg.value ? 1 : 0);
}
else if (pexp->operation == land)
{
if (!leftarg.value)
return OK (0);
struct eval_result rightarg =
plural_eval_recurse (pexp->val.args[1], n, allowed_depth);
if (rightarg.status != PE_OK)
return rightarg;
return OK (rightarg.value ? 1 : 0);
}
else
{
struct eval_result rightarg =
plural_eval_recurse (pexp->val.args[1], n, allowed_depth);
if (rightarg.status != PE_OK)
return rightarg;
switch (pexp->operation)
{
case mult:
return OK (leftarg.value * rightarg.value);
case divide:
if (rightarg.value == 0)
return (struct eval_result) { .status = PE_INTDIV };
return OK (leftarg.value / rightarg.value);
case module:
if (rightarg.value == 0)
return (struct eval_result) { .status = PE_INTDIV };
return OK (leftarg.value % rightarg.value);
case plus:
return OK (leftarg.value + rightarg.value);
case minus:
return OK (leftarg.value - rightarg.value);
case less_than:
return OK (leftarg.value < rightarg.value);
case greater_than:
return OK (leftarg.value > rightarg.value);
case less_or_equal:
return OK (leftarg.value <= rightarg.value);
case greater_or_equal:
return OK (leftarg.value >= rightarg.value);
case equal:
return OK (leftarg.value == rightarg.value);
case not_equal:
return OK (leftarg.value != rightarg.value);
default:
break;
}
}
/* NOTREACHED */
break;
}
case 3:
{
/* pexp->operation must be qmop. */
struct eval_result boolarg =
plural_eval_recurse (pexp->val.args[0], n, allowed_depth);
if (boolarg.status != PE_OK)
return boolarg;
return plural_eval_recurse (pexp->val.args[boolarg.value ? 1 : 2], n,
allowed_depth);
}
}
/* NOTREACHED */
return (struct eval_result) { .status = PE_ASSERT };
}
/* Evaluates a plural expression PEXP for n=N. */
STATIC
struct eval_result
plural_eval (const struct expression *pexp, unsigned long int n)
{
return plural_eval_recurse (pexp, n, EVAL_MAXDEPTH);
}
#undef OK