From ee4b77b8c0e42c382d5f200af19a03a4e7f2b235 Mon Sep 17 00:00:00 2001 From: Sang Woo Kim Date: Fri, 17 Jul 2026 00:41:14 +0900 Subject: [PATCH] Guard MODULO against the undefined INT_MIN % -1 (SIGFPE) The calc engines compute '%' as a signed integer remainder on values narrowed from double. Every MODULO case guards a zero divisor but none guards the one remaining case C leaves undefined: INT_MIN % -1 (and INT64_MIN % -1 on LP64). On x86 the idiv instruction that implements '%' traps the quotient overflow as #DE, delivered as SIGFPE, so a single calc/scalc/acalc expression whose dividend narrows to INT_MIN (readily produced by an out-of-range or NaN double, which casts to 0x80000000) against a -1 divisor takes down the whole IOC. Mathematically x % -1 == 0 for every x, so route each remainder through a small safeModulo() helper that returns 0 for a -1 divisor instead of executing the trapping idiv. This closes the family at every site with one rule rather than a per-site patch: sCalcPerform.c numeric path (int) and string path (long) aCalcPerform.c scalar, array%scalar, and array%array paths (int) The zero-divisor guards are unchanged; only the -1 case, previously undefined behaviour, is now defined to 0. Ordinary remainders are unaffected. Proved with a driver linking the real sCalcPerform/aCalcPerform translation units and driving each site through sCalcPostfix/aCalcPostfix + Perform, one fork per case (INT_MIN dividend, -1 divisor): site before after sCalc numeric (:562) SIGFPE 0 sCalc string (:1109) SIGFPE 0 (post0==USES_STRING) aCalc scalar (:701) SIGFPE 0 aCalc arr%sca (:674) SIGFPE 0 aCalc arr%arr (:650) SIGFPE 0 control 17 % 5 2 2 --- calcApp/src/aCalcPerform.c | 17 ++++++++++++++--- calcApp/src/sCalcPerform.c | 16 ++++++++++++++-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/calcApp/src/aCalcPerform.c b/calcApp/src/aCalcPerform.c index 66f5e60..b5c12b4 100644 --- a/calcApp/src/aCalcPerform.c +++ b/calcApp/src/aCalcPerform.c @@ -55,6 +55,17 @@ #define myMIN(a,b) (a)<(b)?(a):(b) #define SMALL 1.e-9 +/* Signed remainder that guards the one case C leaves undefined: INT_MIN % -1. + * On x86 the idiv used for '%' traps that quotient overflow as #DE, delivered + * as SIGFPE, killing the whole IOC. Mathematically x % -1 == 0 for every x, so + * return 0 for a -1 divisor instead of executing the trapping remainder. The + * divisor == 0 case is guarded separately at each call site. */ +static long safeModulo(long a, long b) +{ + if (b == -1) return 0; + return a % b; +} + static double local_random(); static int cond_search(const unsigned char **ppinst, int match); @@ -647,7 +658,7 @@ long aCalcPerform(double *p_dArg, int num_dArgs, double **pp_aArg, if ((int)ps1->a[i] == 0) { ps->a[i] = myMAXFLOAT; } else { - ps->a[i] = (double)((int)ps->a[i] % (int)ps1->a[i]); + ps->a[i] = (double)safeModulo((int)ps->a[i], (int)ps1->a[i]); } } break; @@ -671,7 +682,7 @@ long aCalcPerform(double *p_dArg, int num_dArgs, double **pp_aArg, if ((int)ps1->d == 0) { ps->a[i] = myMAXFLOAT; } else { - ps->a[i] = (double)((int)ps->a[i] % (int)ps1->d); + ps->a[i] = (double)safeModulo((int)ps->a[i], (int)ps1->d); } } break; @@ -698,7 +709,7 @@ long aCalcPerform(double *p_dArg, int num_dArgs, double **pp_aArg, if ((int)ps1->d == 0) { ps->d = myMAXFLOAT; } else { - ps->d = (double)((int)ps->d % (int)ps1->d); + ps->d = (double)safeModulo((int)ps->d, (int)ps1->d); } break; } diff --git a/calcApp/src/sCalcPerform.c b/calcApp/src/sCalcPerform.c index 31816ac..8b9f6ac 100644 --- a/calcApp/src/sCalcPerform.c +++ b/calcApp/src/sCalcPerform.c @@ -45,6 +45,18 @@ static int cond_search(const unsigned char **ppinst, int match); #define myMIN(a,b) (a)<(b)?(a):(b) #define SMALL 1.e-11 +/* Signed remainder that guards the one case C leaves undefined: INT_MIN % -1 + * (here also INT64_MIN % -1 on LP64). On x86 the idiv used for '%' traps that + * quotient overflow as #DE, delivered as SIGFPE, killing the whole IOC. + * Mathematically x % -1 == 0 for every x, so return 0 for a -1 divisor instead + * of executing the trapping remainder. The divisor == 0 case is guarded + * separately at each call site. */ +static long safeModulo(long a, long b) +{ + if (b == -1) return 0; + return a % b; +} + #define DEBUG 1 #define INIT_STACK 1 volatile int sCalcPerformDebug = 0; @@ -559,7 +571,7 @@ epicsShareFunc long --pd; if ((int)(pd[1]) == 0) return(-1); - *pd = (double)((int)(*pd) % (int)(pd[1])); + *pd = (double)safeModulo((int)(*pd), (int)(pd[1])); break; case REL_OR: @@ -1106,7 +1118,7 @@ epicsShareFunc long toDouble(ps); if ((long)ps1->d == 0) return(-1); - ps->d = (double)((long)ps->d % (long)ps1->d); + ps->d = (double)safeModulo((long)ps->d, (long)ps1->d); break; case REL_OR: