From a827b15d1e536377288b777a08bd46f0b9a52e85 Mon Sep 17 00:00:00 2001 From: Martijn Dekker Date: Tue, 20 Aug 2024 02:43:28 +0100 Subject: [PATCH] Fix "$@$@" corner case At , Steffen Nurpmeso (@sdaoden) writes: > twox() { one "$@$@"; } [...] > shells will give different results for the argument-less call to > twox(). (Here on my box bash(1) is the only one which expands > "$@$@" to nothing.) Geoff Clare responds: > The standard clearly requires "$@$@" to generate zero fields if > there are no positional parameters. The quoted text "if the > expansion is embedded within a word which contains one or more > other parts that expand to a quoted null string, ..." does not > apply because there are no other parts that expand to a quoted > null string. (The second $@ is not a candidate for being such a > part because "the expansion of '@' shall generate zero fields".) Simple reproducer: $ set -- # ensure zero PPs $ set -- "$@$@" $ echo $# 1 Expected output: 0 ksh has further inconsistent behaviour: e.g., for zero positional parameters, ''"$@" correctly yields one empty field, ''"$@$@" yields zero, and ''"$@$@$@" and on yield one again, but ''''"$@$@$@" yields zero -- etc. Analysis: In copyto() in macro.c:705, the mp->quoted variable is increased by one for each shell quote encountered in a word. In varsub() in macro.c:1888, this variable is decreased by two if a quoted $@ is encountered with zero positional parameters. For a simple "$@" which has two quotes, this results in a decrease of two and an mp->quoted value of 0 once both quotes have been parsed. Effectively, both quotes have "disappeared", which allows empty removal to take effect: correct behaviour. But if another $@ exists within the same set of quotes, that decrease by two is done again, causing incorrect behaviour. src/cmd/ksh93/sh/macro.c: - Add new 'atspecial' member to Mac_t struct. - varsub(): When applying the quote count reduction for quoted "$@" with zero PPs, remember the current mp->quoted count in mp->atspecial, and do not apply the reduction again unless mp->quoted has changed from mp->atspecial. This fixes the bug. --- NEWS | 5 +++++ src/cmd/ksh93/include/version.h | 2 +- src/cmd/ksh93/sh/macro.c | 7 ++++--- src/cmd/ksh93/tests/quoting.sh | 31 ++++++++++++++++++++++++++++++- 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/NEWS b/NEWS index d4427c5f9bb1..bbeb0cba5711 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,11 @@ This documents significant changes in the dev branch of ksh 93u+m. For full details, see the git log at: https://github.com/ksh93/ksh Uppercase BUG_* IDs are shell bug IDs as used by the Modernish shell library. +2024-08-19: + +- Fixed a corner-case bug: for an empty set of positional parameters, + "$@$@" would generate one empty field instead of zero fields like "$@". + 2024-07-31: - Fixed a bug where printf %T, after having printed the time in UTC once diff --git a/src/cmd/ksh93/include/version.h b/src/cmd/ksh93/include/version.h index a81d9a32cf93..c5fd7dae8244 100644 --- a/src/cmd/ksh93/include/version.h +++ b/src/cmd/ksh93/include/version.h @@ -18,7 +18,7 @@ #define SH_RELEASE_FORK "93u+m" /* only change if you develop a new ksh93 fork */ #define SH_RELEASE_SVER "1.1.0-alpha" /* semantic version number: https://semver.org */ -#define SH_RELEASE_DATE "2024-08-01" /* must be in this format for $((.sh.version)) */ +#define SH_RELEASE_DATE "2024-08-19" /* must be in this format for $((.sh.version)) */ #define SH_RELEASE_CPYR "(c) 2020-2024 Contributors to ksh " SH_RELEASE_FORK /* Scripts sometimes field-split ${.sh.version}, so don't change amount of whitespace. */ diff --git a/src/cmd/ksh93/sh/macro.c b/src/cmd/ksh93/sh/macro.c index 0d2f3407411f..4164a9f52deb 100644 --- a/src/cmd/ksh93/sh/macro.c +++ b/src/cmd/ksh93/sh/macro.c @@ -60,6 +60,7 @@ typedef struct _mac_ char *ifsp; /* pointer to IFS value */ int fields; /* number of fields */ short quoted; /* set when word has quotes */ + short atspecial; /* quoted count at which special-casing for "$@" with no PPs has been applied */ unsigned char ifs; /* first byte of IFS */ char atmode; /* when processing $@ */ char quote; /* set within double quoted contexts */ @@ -1882,9 +1883,9 @@ static int varsub(Mac_t *mp) if(v || c=='/' && offset>=0) stkseek(stkp,offset); } - /* check for quoted @ */ - if(mode=='@' && mp->quote && !v && c!='-') - mp->quoted-=2; + /* discount the quotes around $@ if there are zero PPs, so that empty "$@" generates zero fields */ + if(mode=='@' && mp->quote && !v && c!='-' && mp->atspecial != mp->quoted) + mp->quoted -= 2, mp->atspecial = mp->quoted; retry2: if(v && (!nulflg || *v ) && c!='+') { diff --git a/src/cmd/ksh93/tests/quoting.sh b/src/cmd/ksh93/tests/quoting.sh index 15d2daf49c50..0f89ced62746 100755 --- a/src/cmd/ksh93/tests/quoting.sh +++ b/src/cmd/ksh93/tests/quoting.sh @@ -2,7 +2,7 @@ # # # This software is part of the ast package # # Copyright (c) 1982-2011 AT&T Intellectual Property # -# Copyright (c) 2020-2022 Contributors to ksh 93u+m # +# Copyright (c) 2020-2024 Contributors to ksh 93u+m # # and is licensed under the # # Eclipse Public License, Version 2.0 # # # @@ -369,4 +369,33 @@ case x in $x) err_exit "case \$x='$x' should not match x";; esac +# ====== +# https://austingroupbugs.net/view.php?id=1852 +# https://mail.gnu.org/archive/html/bug-bash/2024-08/msg00132.html +unset e E q i +for e in 3 '"$@"' 5 '"$@$@"' 7 '"$@$@$@"' 9 '"$@$@$@$@"' 11 '"$@$@$@$@$@"' 13 '"$@$@$@$@$@$@"' \ + 5 '"$@""$@"' 7 '"$@""$@""$@"' 9 '"$@""$@""$@""$@"' 11 '"$@""$@""$@""$@""$@"' 13 '"$@""$@""$@""$@""$@""$@"' +do [[ $e == [0-9]* ]] && i=$e && continue + set -- # set zero PPs + eval "set -- $e" + (($# == 0)) || err_exit "$e does not yield zero fields for zero positional parameters (got $#)" + set -- one two three + eval "set -- $e" + (($# == i)) || err_exit "$e does not yield $i fields for 3 positional parameters (got $#)" + for q in "''" '""' + do for q in "$q" "$q$q" "$q$q$q" "$q$q$q$q" "$q$q$q$q$q" "$q$q$q$q$q$q" + do for E in "$q$e" "$e$q" "$q$e$q" + do set -- # set zero PPs + eval "set -- $E" + (($# == 1)) || err_exit "$E does not yield one field for zero positional parameters (got $#)" + set -- one two three + eval "set -- $E" + (($# == i)) || err_exit "$E does not yield $i fields for 3 positional parameters (got $#)" + done + done + done + ((i+=2)) +done + +# ====== exit $((Errors<125?Errors:125))