Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
arithmetic: fix divide by negative on ARM systems
*** BUG 1 *** Léonard Oest O'Leary (@leo-ard) reports: > Doing an arithmetic expansion with a division where the dividend > is a negative number returns a "division by zero error" : > > > ksh > $ echo $(( 10 / -10 )) > ksh: 10 / -10 : divide by zero > $ echo $(( 10 / -1 )) > ksh: 10 / -1 : divide by zero > $ echo $(( 10 / -2 )) > ksh: 10 / -2 : divide by zero > > Tested with version sh (AT&T Research) 93u+ 2012-08-01 of ksh. > Running on Macos M2 14.1.1 (23B81) Turns out this bug is reproducible on all ARM systems. It was inherited from 93u+ 2012-08-01. Testing my stash of compiled historic AT&T ksh versions (on a Debian arm64 VM) shows that the bug was introduced in 93q 2005-01-31 and fixed in 93v- 2013-06-28. Analysis: All shell arithmetic is internally done with the maximum-length float type that the system supports (Sfdouble_t). Since comparison with floats is inexact, ksh converts the numnber to Sfulong_t, the largest unsigned integer type supported by the system, before comparing it to zero and throwing a "divide by zero". In streval.c: 354: else if((Sfulong_t)(num)==0) 355: arith_error(e_divzero,ep->expr,ep->emode); However, converting a negative float value to an unsigned integer type is undefined behaviour in the C standard. On (at least) ARM systems and IBM POWER systems [*1], the result is always zero in that case, which causes this bug. src/cmd/ksh93/sh/streval.c: arith_exec(): - Backport the 93v- 2013-06-28 fix: make sure that the number is not negative before typecasting it and compating it to zero. *** BUG 2 *** I introduced a similar bug with the same undefined behaviour myself in 4592859 (see there for explanation). On ARM systems: $ typeset -ui i $ echo $((i = -5)) 0 $ echo $i 4294967291 The assignment worked fine (wrapped around as expected for an unsigned integer), but the value of the assignment was 0 and should have been the same as the variable's new value. So lines 272, 274 and 276 in arith.c have undefined behaviour for a negative n (which is of the Sfdouble_t float type): 271: else if((attr & NV_UINT64)==NV_UINT64) /* long unsigned integer */ 272: r = (uintmax_t)n; 273: else if((attr & NV_UINT16)==NV_UINT16) /* short unsigned integer */ 274: r = (uint16_t)n; 275: else if((attr & NV_UINT32)==NV_UINT32) /* normal unsigned integer */ 276: r = (uint32_t)n; src/cmd/ksh93/sh/arith.c: arith(): - Before doing those typecasts, check if n is negative. If so, invert the sign using the unary minus (yielding a positive float), do the typecast, and then do another unary minus operation on the resulting positive unsigned integer value -- which is well-defined behaviour in C [*2] and yields a positive wrapped-around unsigned integer value, which is fine to implicitly typecast back to r's float type. Resolves: #770 [*1] https://www.ibm.com/support/pages/assigning-negative-float-value-unsigned-integral-type-yields-undefined-value-stored-type [*2] https://stackoverflow.com/questions/8026694/c-unary-minus-operator-behavior-with-unsigned-operands
- Loading branch information