#define STATIC static
#endif
-/* Evaluate the plural expression and return an index value. */
-STATIC
-unsigned long int
-plural_eval (const struct expression *pexp, unsigned long int n)
+/* 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 n;
+ return OK (n);
case num:
- return pexp->val.num;
+ return OK (pexp->val.num);
default:
break;
}
case 1:
{
/* pexp->operation must be lnot. */
- unsigned long int arg = plural_eval (pexp->val.args[0], n);
- return ! arg;
+ 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:
{
- unsigned long int leftarg = plural_eval (pexp->val.args[0], n);
+ 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)
- return leftarg || plural_eval (pexp->val.args[1], n);
+ {
+ 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)
- return leftarg && plural_eval (pexp->val.args[1], n);
+ {
+ 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
{
- unsigned long int rightarg = plural_eval (pexp->val.args[1], n);
+ 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 leftarg * rightarg;
+ return OK (leftarg.value * rightarg.value);
case divide:
#if !INTDIV0_RAISES_SIGFPE
- if (rightarg == 0)
+ if (rightarg.value == 0)
raise (SIGFPE);
#endif
- return leftarg / rightarg;
+ return OK (leftarg.value / rightarg.value);
case module:
#if !INTDIV0_RAISES_SIGFPE
- if (rightarg == 0)
+ if (rightarg.value == 0)
raise (SIGFPE);
#endif
- return leftarg % rightarg;
+ return OK (leftarg.value % rightarg.value);
case plus:
- return leftarg + rightarg;
+ return OK (leftarg.value + rightarg.value);
case minus:
- return leftarg - rightarg;
+ return OK (leftarg.value - rightarg.value);
case less_than:
- return leftarg < rightarg;
+ return OK (leftarg.value < rightarg.value);
case greater_than:
- return leftarg > rightarg;
+ return OK (leftarg.value > rightarg.value);
case less_or_equal:
- return leftarg <= rightarg;
+ return OK (leftarg.value <= rightarg.value);
case greater_or_equal:
- return leftarg >= rightarg;
+ return OK (leftarg.value >= rightarg.value);
case equal:
- return leftarg == rightarg;
+ return OK (leftarg.value == rightarg.value);
case not_equal:
- return leftarg != rightarg;
+ return OK (leftarg.value != rightarg.value);
default:
break;
}
case 3:
{
/* pexp->operation must be qmop. */
- unsigned long int boolarg = plural_eval (pexp->val.args[0], n);
- return plural_eval (pexp->val.args[boolarg ? 1 : 2], n);
+ 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 0;
+ 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
/* Checking of messages in PO files.
- Copyright (C) 1995-1998, 2000-2008, 2010-2016, 2019 Free Software Foundation, Inc.
+ Copyright (C) 1995-2023 Free Software Foundation, Inc.
Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, April 1995.
This program is free software: you can redistribute it and/or modify
count = 0;
for (n = min; n <= max; n++)
{
- unsigned long val = plural_eval (expr, n);
+ struct eval_result res = plural_eval (expr, n);
- if (val == j)
+ if (res.status == PE_OK && res.value == j)
count++;
}
for (n = 0; n <= 1000; n++)
{
- unsigned long val = plural_eval (plural_expr, n);
+ struct eval_result res = plural_eval (plural_expr, n);
+ if (res.status != PE_OK)
+ {
+ /* End of protection against arithmetic exceptions. */
+ uninstall_sigfpe_handler ();
+
+ if (res.status == PE_STACKOVF)
+ po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false,
+ _("plural expression can produce stack overflow"));
+ else
+ /* Other res.status values should not occur. */
+ abort ();
+
+ free (array);
+ return 1;
+ }
+
+ unsigned long val = res.value;
if ((long) val < 0)
{
# Test that ngettext() does not crash when the plural form rule leads to
# a stack overflow.
-# In fact, the stack overflow is caught by the bison parser (plural.c,
-# macro YYMAXDEPTH = 10000). Since the same bison parser is used by
-# glibc, libintl, and 'msgfmt -c', we can observe the behaviour by using
-# the msgfmt option '-c'.
+# In fact, a stack overflow at parsing time is caught by the bison parser
+# (plural.c, macro YYMAXDEPTH = 10000), and a stack overflow at evaluation
+# time is caught as well (eval-plural.h, macro EVAL_MAXDEPTH = 100).
test -d plural-3-dir || mkdir plural-3-dir
test -d plural-3-dir/ll || mkdir plural-3-dir/ll
EOF
: ${MSGFMT=msgfmt}
-${MSGFMT} -c -o plural-3-dir/ll/LC_MESSAGES/plural.mo plural-3-ll.po || Exit 1
+${MSGFMT} -o plural-3-dir/ll/LC_MESSAGES/plural.mo plural-3-ll.po || Exit 1
LANGUAGE= TEXTDOMAIN=plural TEXTDOMAINDIR=plural-3-dir \
$NGETTEXT --env LC_ALL=ll X Y 42 > dataout
test $? = 0 || Exit 1
-test y = "`cat dataout`" || Exit 1
+test x = "`cat dataout`" || Exit 1
LANGUAGE= TEXTDOMAIN=plural TEXTDOMAINDIR=plural-3-dir \
$NGETTEXT --env LC_ALL=ll --thread X Y 42 > thread-dataout
test $? = 0 || Exit 1
-test y = "`cat thread-dataout`" || Exit 1
+test x = "`cat thread-dataout`" || Exit 1
# This one is large enough that YYMAXDEPTH is exceeded, i.e. the parser fails.