1
0
Fork 0
mirror of git://git.code.sf.net/p/cdesktopenv/code synced 2025-03-09 15:50:02 +00:00

arithmetic: Fix the octal leading zero mess (#337)

In C/POSIX arithmetic, a leading 0 denotes an octal number, e.g.
010 == 8. But this is not a desirable feature as it can cause
problems with processing things like dates with a leading zero.
In ksh, you should use 8#10 instead ("10" with base 8).

It would be tolerable if ksh at least implemented it consistently.
But AT&T made an incredible mess of it. For anyone who is not
intimately familiar with ksh internals, it is inscrutable where
arithmetic evaluation special-cases a leading 0 and where it
doesn't. Here are just some of the surprises/inconsistencies:

1. The AT&T maintainers tried to honour a leading 0 inside of
   ((...)) and $((...)) and not for arithmetic contexts outside it,
   but even that inconsistency was never quite consistent.

2. Since 2010-12-12, $((x)) and $(($x)) are different:
      $ /bin/ksh -c 'x=010; echo $((x)) $(($x))'
      10 8
   That's a clear violation of both POSIX and the principle of
   least astonishment. $((x)) and $(($x)) should be the same in
   all cases.

3. 'let' with '-o letoctal' acts in this bizarre way:
      $ set -o letoctal; x=010; let "y1=$x" "y2=010"; echo $y1 $y2
      10 8
   That's right, 'let y=$x' is different from 'let y=010' even
   when $x contains the same string value '010'! This violates
   established shell grammar on the most basic level.

This commit introduces consistency. By default, ksh now acts like
mksh and zsh: the octal leading zero is disabled in all arithmetic
contexts equally. In POSIX mode, it is enabled equally.

The one exception is the 'let' built-in, where this can still be
controlled independently with the letoctal option as before (but,
because letoctal is synched with posix when switching that on/off,
it's consistent by default).

We're also removing the hackery that causes variable expansions for
the 'let' builtin to be quietly altered, so that 'x=010; let y=$x'
now does the same as 'let y=010' even with letoctal on.

Various files:
- Get rid of now-redundant sh.inarith (shp->inarith) flag, as we're
  no longer distinguishing between being inside or outside ((...)).

src/cmd/ksh93/sh/arith.c:
- arith(): Let disabling POSIX octal constants by skipping leading
  zeros depend on either the letoctal option being off (if we're
  running the "let" built-in") or the posix option being off.
- sh_strnum(): Preset a base of 10 for strtonll(3) depending on the
  posix or letoctal option being off, not on the sh.inarith flag.

src/cmd/ksh93/include/argnod.h,
src/cmd/ksh93/sh/args.c,
src/cmd/ksh93/sh/macro.c:
- Remove astonishing hackery that violated shell grammar for 'let'.

src/cmd/ksh93/sh/name.c (nv_getnum()),
src/cmd/ksh93/sh/nvdisc.c (nv_getn()):
- Remove loops for skipping leading zeroes that included a broken
  check for justify/zerofill attributes, thereby fixing this bug:
	$ typeset -Z x=0x15; echo $((x))
	-ksh: x15: parameter not set
  Even if this code wasn't redundant before, it is now: sh_arith()
  is called immediately after the removed code and it ignores
  leading zeroes via sh_strnum() and strtonll(3).

Resolves: https://github.com/ksh93/ksh/issues/334
This commit is contained in:
Martijn Dekker 2021-11-17 03:24:18 +00:00
parent 257eea612a
commit c734568b02
14 changed files with 86 additions and 93 deletions

View file

@ -21,6 +21,9 @@
. "${SHTESTS_COMMON:-${0%/*}/_common}"
integer hasposix=0
(set -o posix) 2>/dev/null && ((hasposix++)) # not using [[ -o ?posix ]] as it's broken on 93v-
trap '' FPE # NOTE: osf.alpha requires this (no ieee math)
integer x=1 y=2 z=3
@ -346,8 +349,14 @@ do (( ipx = ip % 256 ))
done
unset x
x=010
(( x == 10 )) || err_exit 'leading zeros in x treated as octal arithmetic with $((x))'
(( $x == 8 )) || err_exit 'leading zeros not treated as octal arithmetic with $x'
(( x == 10 )) || err_exit 'leading zeros in x treated as octal arithmetic with ((x))'
(( $x == 10 )) || err_exit 'leading zeros in x treated as octal arithmetic with (($x))'
if ((hasposix))
then set --posix
((x == 8)) || err_exit 'posix: leading zeros in x not treated as octal arithmetic with ((x))'
(($x == 8)) || err_exit 'posix: leading zeros in x not treated as octal arithmetic with (($x))'
set --noposix
fi
unset x
typeset -Z x=010
(( x == 10 )) || err_exit 'leading zeros not ignored for arithmetic'
@ -728,15 +737,26 @@ unset A
unset r x
integer x
r=020
(($r == 16)) || err_exit 'leading 0 not treated as octal inside ((...))'
(($r == 20)) || err_exit 'leading 0 treated as octal inside ((...))'
x=$(($r))
(( x == 16 )) || err_exit 'leading 0 not treated as octal inside $((...))'
((x == 20)) || err_exit 'leading 0 treated as octal inside $((...))'
x=$r
((x == 20 )) || err_exit 'leading 0 should not be treated as octal outside ((...))'
((x == 20)) || err_exit 'leading 0 treated as octal outside ((...))'
print -- -020 | read x
((x == -20)) || err_exit 'numbers with leading -0 should not be treated as octal outside ((...))'
((x == -20)) || err_exit 'numbers with leading -0 treated as octal outside ((...))'
print -- -8#20 | read x
((x == -16)) || err_exit 'numbers with leading -8# should be treated as octal'
if ((hasposix))
then set --posix
(($r == 16)) || err_exit 'posix: leading 0 not treated as octal inside ((...))'
x=$(($r))
(( x == 16 )) || err_exit 'posix: leading 0 not treated as octal inside $((...))'
x=$r
((x == 16)) || err_exit 'posix: leading 0 not as octal outside ((...))'
print -- -020 | read x
((x == -16)) || err_exit 'posix: numbers with leading -0 should be treated as octal outside ((...))'
set --noposix
fi
unset x
x=0x1
@ -750,8 +770,13 @@ let "$x==10" || err_exit 'arithmetic with $x where $x is 010 should be decimal i
(( 9.$x == 9.01 )) || err_exit 'arithmetic with 9.$x where x=010 should be 9.01'
(( 9$x == 9010 )) || err_exit 'arithmetic with 9$x where x=010 should be 9010'
x010=99
((x$x == 99 )) || err_exit 'arithtmetic with x$x where x=010 should be $x010'
(( 3+$x == 11 )) || err_exit '3+$x where x=010 should be 11 in ((...))'
((x$x == 99 )) || err_exit 'arithmetic with x$x where x=010 should be $x010'
(( 3+$x == 13 )) || err_exit '3+$x where x=010 should be 13 in ((...))'
if ((hasposix))
then set --posix
(( 3+$x == 11 )) || err_exit 'posix: 3+$x where x=010 should be 11 in ((...))'
set --noposix
fi
let "(3+$x)==13" || err_exit 'let should not recognize leading 0 as octal'
unset x
typeset -RZ3 x=10
@ -878,8 +903,9 @@ unset got
# ======
# https://github.com/ksh93/ksh/issues/326
for m in u d i o x X
((hasposix)) && for m in u d i o x X
do
set --posix
case $m in
o) exp="10;21;32;" ;;
x) exp="8;11;1a;" ;;
@ -887,22 +913,16 @@ do
*) exp="8;17;26;" ;;
esac
got=${ printf "%$m;" 010 021 032; }
[[ $got == "$exp" ]] || err_exit "printf %$m does not recognize octal arguments" \
[[ $got == "$exp" ]] || err_exit "posix: printf %$m does not recognize octal arguments" \
"(expected $(printf %q "$exp"), got $(printf %q "$got"))"
set --noposix
done
# https://github.com/ksh93/ksh/issues/326#issuecomment-917707463
exp=18
got=$(( $(integer x; x=010; echo $x) + 010 ))
# ^^^ decimal ^^^ octal
[[ $got == "$exp" ]] || err_exit 'Integer with leading zero incorrectly interpreted as octal in non-POSIX arith context' \
"(expected $(printf %q "$exp"), got $(printf %q "$got"))"
# ======
# BUG_ARITHNAN: In ksh <= 93u+m 2021-11-15 and zsh 5.6 - 5.8, the case-insensitive
# floating point constants Inf and NaN are recognised in arithmetic evaluation,
# overriding any variables with the names Inf, NaN, INF, nan, etc.
if (set --posix) 2>/dev/null
if ((hasposix))
then set --posix
Inf=42 NaN=13
inf=421 nan=137
@ -920,5 +940,12 @@ then set --posix
set --noposix
fi
# ======
# https://github.com/ksh93/ksh/issues/334#issuecomment-968603087
exp=21
got=$(typeset -Z x=0x15; { echo $((x)); } 2>&1)
[[ $got == "$exp" ]] || err_exit "typeset -Z corrupts hexadecimal number in arithmetic context" \
"(expected $(printf %q "$exp"), got $(printf %q "$got"))"
# ======
exit $((Errors<125?Errors:125))