small change to brace expansion to inhibit some error messages; changes to printf builtin for precision and field widths

This commit is contained in:
Chet Ramey
2023-10-23 09:50:02 -04:00
parent 1e1a0342a4
commit b348b871b4
19 changed files with 376 additions and 44 deletions
+46
View File
@@ -7864,3 +7864,49 @@ builtins/read.def
- read_builtin: return EX_MISCERROR (2) if there is an error trying
to assign to one of the variables. This is what the newest POSIX
draft specifies.
variables.c
- dispose_variable_value: do the right thing for att_nofree vars
- makunbound: call dispose_variable_value instead of using inline code
braces.c
- brace_gobbler: make sure to set no_longjmp_on_fatal_error around
calls to extract_dollar_brace_string
10/17
-----
braces.c
- brace_gobbler: set SX_NOLONGJMP|SX_NOERROR in the flags passed
to extract_command_subst; make sure no_longjmp_on_fatal_error
is set before that call
- brace_gobbler: revert 10/13 change to use extract_dollar_brace_string
10/20
-----
parse.y
- xparse_dolparen,parse_string_to_command: if SX_NOERROR is set in
FLAGS, add PST_NOERROR to parser_state. Not much effect yet.
- parse_matched_pair: if PST_NOERROR is set in parser_state, don't
print an error message if we hit EOF, just return an error. A start
at using PST_NOERROR to suppress error messages, not just duplicate
ones. We'll see how this goes before adding more
builtins/printf.def
- decodeprec: decode the precision into an intmax_t; clamp the return
value at INT_MAX
- printf_builtin: update to posix interp 1647 (even though it's about
fprintf(3)) and output a NUL byte if %lc is supplied a null argument.
- printf_builtin: fix case where %Q is supplied with a precision in
the format string
- printf_builtin: fix case where %Q is supplied with a precision
greater than INT_MAX
- getwidestr,getwidechar: handle case where there is no argument
supplied; return NULL or NUL
- convwidestr: allow a precedence of 0 for %ls
- getint: don't call getintmax any more, just use the same code style
inline; getintmax will consume an extra argument on an error
Report and patches from Grisha Levit <grishalevit@gmail.com>
- printf_builtin: handle field width and precision overflow from
getint() by ignoring the argument (fieldwidth = 0, precision = -1)
+2
View File
@@ -1405,6 +1405,8 @@ tests/printf2.sub f
tests/printf3.sub f
tests/printf4.sub f
tests/printf5.sub f
tests/printf6.sub f
tests/printf7.sub f
tests/procsub.tests f
tests/procsub.right f
tests/procsub1.sub f
+37 -2
View File
@@ -619,9 +619,30 @@ brace_gobbler (char *text, size_t tlen, int *indx, int satisfy)
/* If compiling for the shell, treat ${...} like \{...} */
if (c == '$' && text[i+1] == '{' && quoted != '\'') /* } */
{
#if 0
/* If we want to inhibit brace expansion during parameter expansions,
we need to skip over parameter expansions here. This is easier
than teaching brace expansion about the idiosyncracies of shell
word expansion. */
int o, f;
o = no_longjmp_on_fatal_error;
no_longjmp_on_fatal_error = 1;
f = (quoted == '"') ? Q_DOUBLE_QUOTES : 0;
si = i + 2;
t = extract_dollar_brace_string (text, &si, 0, SX_NOALLOC);
t = extract_dollar_brace_string (text, &si, f, SX_NOALLOC|SX_NOLONGJMP|SX_COMPLETE|SX_NOERROR);
i = si + 1;
no_longjmp_on_fatal_error = o;
if (i > tlen)
{
i = tlen;
break;
}
#else
pass_next = 1;
i++;
if (quoted == 0)
level++;
#endif
continue;
}
#endif
@@ -654,10 +675,24 @@ brace_gobbler (char *text, size_t tlen, int *indx, int satisfy)
/* Pass new-style command and process substitutions through unchanged. */
if ((c == '$' || c == '<' || c == '>') && text[i+1] == '(') /* ) */
{
int o;
comsub:
o = no_longjmp_on_fatal_error;
no_longjmp_on_fatal_error = 1;
si = i + 2;
t = extract_command_subst (text, &si, SX_NOALLOC);
#if 0
t = extract_command_subst (text, &si, SX_NOALLOC|SX_NOLONGJMP|SX_NOERROR|SX_COMPLETE);
#else
t = extract_command_subst (text, &si, SX_NOALLOC|SX_NOLONGJMP|SX_NOERROR);
#endif
i = si + 1;
no_longjmp_on_fatal_error = o;
if (i > tlen)
{
i = tlen;
break;
}
continue;
}
#endif
+51 -15
View File
@@ -252,12 +252,12 @@ static size_t conv_bufsize;
static inline int
decodeprec (char *ps)
{
int mpr;
intmax_t mpr;
mpr = *ps++ - '0';
while (DIGIT (*ps))
mpr = (mpr * 10) + (*ps++ - '0');
return mpr;
return (mpr < 0 || mpr > INT_MAX) ? INT_MAX : mpr;
}
int
@@ -416,6 +416,9 @@ printf_builtin (WORD_LIST *list)
fmt++;
have_fieldwidth = 1;
fieldwidth = getint ();
/* Handle field with overflow by ignoring it. */
if (fieldwidth == INT_MAX || fieldwidth == INT_MIN)
fieldwidth = 0;
}
else
while (DIGIT (*fmt))
@@ -430,6 +433,10 @@ printf_builtin (WORD_LIST *list)
fmt++;
have_precision = 1;
precision = getint ();
/* Handle precision overflow by ignoring it. "A negative
precision is treated as if it were missing." */
if (precision == INT_MAX || precision == INT_MIN)
precision = -1;
}
else
{
@@ -490,7 +497,12 @@ printf_builtin (WORD_LIST *list)
ws[0] = wc;
ws[1] = L'\0';
r = printwidestr (start, ws, 1, fieldwidth, precision);
/* If %lc is supplied a null argument, posix interp 1647
says it should produce a single null byte. */
if (wc == L'\0')
r = printstr (start, "", 1, fieldwidth, precision);
else
r = printwidestr (start, ws, 1, fieldwidth, precision);
if (r < 0)
PRETURN (EXECUTION_FAILURE);
break;
@@ -514,7 +526,7 @@ printf_builtin (WORD_LIST *list)
wp = getwidestr (&slen);
r = printwidestr (start, wp, slen, fieldwidth, precision);
free (wp);
FREE (wp);
if (r < 0)
PRETURN (EXECUTION_FAILURE);
break;
@@ -647,20 +659,19 @@ printf_builtin (WORD_LIST *list)
case 'Q':
{
char *p, *xp;
int r, mpr;
int r;
size_t slen;
r = 0;
p = getstr ();
/* Decode precision and apply it to the unquoted string. */
if (convch == 'Q' && precstart)
if (convch == 'Q' && (have_precision || precstart))
{
mpr = decodeprec (precstart);
/* Error if precision > INT_MAX here? */
precision = (mpr < 0 || mpr > INT_MAX) ? INT_MAX : mpr;
if (precstart)
precision = decodeprec (precstart);
slen = strlen (p);
/* printf precision works in bytes. */
if (precision < slen)
if (precision >= 0 && precision < slen)
p[precision] = '\0';
}
if (p && *p == 0) /* XXX - getstr never returns null */
@@ -1336,17 +1347,31 @@ getstr (void)
return ret;
}
/* Don't call getintmax here because it may consume an argument on error, and
we call this to get field width/precision arguments. */
static int
getint (void)
{
intmax_t ret;
ret = getintmax ();
char *ep;
if (garglist == 0)
return ret;
return (0);
if (ret > INT_MAX)
if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"')
return asciicode ();
errno = 0;
ret = strtoimax (garglist->word->word, &ep, 0);
if (*ep)
{
sh_invalidnum (garglist->word->word);
conversion_error = 1;
}
else if (errno == ERANGE)
printf_erange (garglist->word->word);
else if (ret > INT_MAX)
{
printf_erange (garglist->word->word);
ret = INT_MAX;
@@ -1357,6 +1382,7 @@ getint (void)
ret = INT_MIN;
}
garglist = garglist->next;
return ((int)ret);
}
@@ -1519,6 +1545,13 @@ getwidestr (size_t *lenp)
size_t slen, mblength;
DECLARE_MBSTATE;
if (garglist == 0)
{
if (lenp)
*lenp = 0;
return NULL;
}
mbs = garglist->word->word;
slen = strlen (mbs);
ws = (wchar_t *)xmalloc ((slen + 1) * sizeof (wchar_t));
@@ -1547,6 +1580,9 @@ getwidechar (void)
size_t slen, mblength;
DECLARE_MBSTATE;
if (garglist == 0)
return L'\0';
wc = 0;
mblength = mbrtowc (&wc, garglist->word->word, locale_mb_cur_max, &state);
if (MB_INVALIDCH (mblength))
@@ -1571,7 +1607,7 @@ convwidestr (wchar_t *ws, int prec)
ts = (const wchar_t *)ws;
if (prec > 0)
if (prec >= 0)
{
rsize = prec * MB_CUR_MAX;
ret = (char *)xmalloc (rsize + 1);
+4 -3
View File
@@ -3793,7 +3793,8 @@ parse_matched_pair (int qc, int open, int close, size_t *lenp, int flags)
if (ch == EOF)
{
free (ret);
parser_error (start_lineno, _("unexpected EOF while looking for matching `%c'"), close);
if ((parser_state & PST_NOERROR) == 0)
parser_error (start_lineno, _("unexpected EOF while looking for matching `%c'"), close);
EOF_Reached = 1; /* XXX */
parser_state |= PST_NOERROR; /* avoid redundant error message */
return (&matched_pair_error);
@@ -4602,7 +4603,7 @@ xparse_dolparen (const char *base, char *string, size_t *indp, int flags)
/*(*/
parser_state |= PST_CMDSUBST|PST_EOFTOKEN; /* allow instant ')' */ /*{(*/
closer = shell_eof_token = funsub ? '}' : ')';
if (flags & SX_COMPLETE)
if (flags & (SX_COMPLETE|SX_NOERROR))
parser_state |= PST_NOERROR;
if (funsub)
parser_state |= PST_FUNSUBST;
@@ -4733,7 +4734,7 @@ parse_string_to_command (char *string, int flags)
#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
pushed_string_list = (STRING_SAVER *)NULL;
#endif
if (flags & SX_COMPLETE)
if (flags & (SX_COMPLETE|SX_NOERROR))
parser_state |= PST_NOERROR;
parser_state |= PST_STRING;
+1
View File
@@ -210,6 +210,7 @@ e
b c
$0
declare -a A=([0]="X=a" [1]="b")
2 3 4 5 6
FIN1:0
FIN2:0
FIN3:0
+4
View File
@@ -407,6 +407,10 @@ Z='a b'
A=( X=$Z )
declare -p A
# combine with brace expansion
letters=( {0..9} )
echo "${letters["{2..6}"]}"
# tests for assigning to noassign array variables
BASH_ARGC=(xxx) ; echo FIN1:$?
BASH_ARGC=foio ; echo FIN2:$?
+5
View File
@@ -22,6 +22,11 @@ foobar foobaz
bazx bazy
vx vy
bazx bazy
4
4
./braces.tests: command substitution: line 59: unexpected EOF while looking for matching `)'
4
4
1 2 3 4 5 6 7 8 9 10
0..10 braces
0 1 2 3 4 5 6 7 8 9 10 braces
+11
View File
@@ -49,6 +49,17 @@ echo ${var}{x,y}
unset var varx vary
# make sure ${ is parsed as a word expansion, since it may contain other
# expansions
a=4
echo "${a#'$('\'}"
echo "${a-'$('\'}"
echo "${a+'$('\'}"
echo "${a#aaaa'$(aaaa'aaa)aaa\'}"
echo "${a#aaaa'$(aaaa)'aaaa\'}"
unset -v a
# new sequence brace operators
echo {1..10}
+14
View File
@@ -166,6 +166,20 @@ argv[2] = <^?>
argv[1] = <^A^?>
argv[1] = <^A^?^A^?>
argv[1] = <^A^A^?>
argv[1] = <\^A>
argv[1] = <^A>
argv[1] = <\^A>
argv[1] = <\^A>
argv[1] = <^A>
argv[1] = <^A>
argv[1] = <^A^A>
argv[1] = <^A^A>
argv[1] = <\^A>
argv[1] = <\^A>
argv[1] = <\^A^?>
argv[1] = <\^A^?>
argv[1] = <^\^A ^\^?>
argv[1] = <^A ^_>
0.net
0.net0
+20
View File
@@ -32,3 +32,23 @@ recho $FOO
recho ''
recho ''
recho ''
# tests of backslash-escaped CTLESC
recho "${_+\}"
recho ${_+\}
recho "${_+\\}"
recho ${_+\\}
recho "${_+}"
recho ${_+}
recho "${_+}"
recho ${_+}
recho $'\\\1'
recho "\\"
recho "\\"
recho \\
recho $'\c\\\001 \c\\\177'
recho $'\c \c'
+55 -8
View File
@@ -21,6 +21,8 @@ this\&that
echo a\\;ls
echo a\'\;ls
echo 'a'\''b'\;ls
\*
\*
1 2 3 4 5
onestring 0 0 0
onestring 0 0 0.00
@@ -38,7 +40,7 @@ A7
--\"abcd\"--
--\'abcd\'--
--a\x--
./printf.tests: line 112: printf: missing hex digit for \x
./printf.tests: line 115: printf: missing hex digit for \x
--\x--
----
----
@@ -99,12 +101,12 @@ A7
26
26
26
./printf.tests: line 236: printf: `%10': missing format character
./printf.tests: line 237: printf: `M': invalid format character
ab./printf.tests: line 240: printf: `y': invalid format character
./printf.tests: line 243: printf: GNU: invalid number
./printf.tests: line 239: printf: `%10': missing format character
./printf.tests: line 240: printf: `M': invalid format character
ab./printf.tests: line 243: printf: `y': invalid format character
./printf.tests: line 246: printf: GNU: invalid number
0
./printf.tests: line 244: printf: GNU: invalid number
./printf.tests: line 247: printf: GNU: invalid number
0
-
(foo )(bar )
@@ -157,9 +159,15 @@ b
xx
xx
< >< >
./printf.tests: line 348: printf: 9223372036854775825: Result too large
0
^@
0
0.00
''
''
./printf.tests: line 364: printf: 9223372036854775825: Result too large
9223372036854775807
./printf.tests: line 349: printf: -9223372036854775815: Result too large
./printf.tests: line 365: printf: -9223372036854775815: Result too large
-9223372036854775808
one
one\ctwo
@@ -330,6 +338,10 @@ hello --
hello --
123 --
6 --
0000000 000
0000001
0000000 000
0000001
0000000 340 262 207 340 262 263 340 262 277 340 262 225 340 263 206 340
0000010 262 227 340 262 263 340 263 201 012
0000019
@@ -355,3 +367,38 @@ hello --
0000007
0000000 340 262 207 040 040 040 055 055 055 012
000000a
[][]
./printf7.sub: line 19: printf: 21474836470: Result too large
[]
./printf7.sub: line 20: printf: 21474836470: Result too large
[X]
./printf7.sub: line 22: printf: 21474836470: Result too large
VAR=[]
./printf7.sub: line 25: printf: 21474836470: Result too large
VAR=[X]
./printf7.sub: line 31: printf: 9223372036854775825: Result too large
[ ]
./printf7.sub: line 32: printf: 9223372036854775825: Result too large
[X]
./printf7.sub: line 34: printf: 9223372036854775825: Result too large
VAR=[ ]
./printf7.sub: line 37: printf: 9223372036854775825: Result too large
VAR=[X]
./printf7.sub: line 43: printf: 21474836470: Result too large
[]
./printf7.sub: line 44: printf: 21474836470: Result too large
[X]
./printf7.sub: line 46: printf: 21474836470: Result too large
VAR=[]
./printf7.sub: line 49: printf: 21474836470: Result too large
VAR=[X]
./printf7.sub: line 55: printf: 9223372036854775825: Result too large
[]
./printf7.sub: line 56: printf: 9223372036854775825: Result too large
[X]
./printf7.sub: line 58: printf: 9223372036854775825: Result too large
VAR=[]
./printf7.sub: line 61: printf: 9223372036854775825: Result too large
VAR=[X]
XY
XY
+17 -2
View File
@@ -77,6 +77,9 @@ printf 'echo %.2Q%Q\n' "$S" "$T" # note the difference
# a different way to do it
printf 'echo %.*s%q\n' ${#S1} "$S1" "$T"
printf '%.1Q\n' '**'
printf '%.*Q\n' 1 '**'
# make sure the format string is reused to use up arguments
printf "%d " 1 2 3 4 5; printf "\n"
@@ -341,6 +344,19 @@ printf -v var "%b" @(hugo); echo "x${var}x"
# make sure that missing arguments are always handled like the empty string
printf "<%3s><%3b>\n"
# other format specifiers with missing arguments
# 0
printf '%d\n'
# null char
printf '%c\n'
printf '%x\n'
printf '%4.2f\n'
printf '%b'
printf '%q\n'
printf '%Q\n'
# let's test some out-of-range integer errors for POSIX-specified behavior
TOOBIG=9223372036854775825
TOOSMALL=-9223372036854775815
@@ -348,8 +364,6 @@ TOOSMALL=-9223372036854775815
printf '%d\n' "$TOOBIG"
printf '%d\n' "$TOOSMALL"
# invalid variable name
# tests variable assignment with -v
${THIS_SH} ./printf1.sub
${THIS_SH} ./printf2.sub
@@ -358,3 +372,4 @@ ${THIS_SH} ./printf4.sub
${THIS_SH} ./printf5.sub
# multibyte characters with %ls/%S and %lc/%C
${THIS_SH} ./printf6.sub
${THIS_SH} ./printf7.sub
+24
View File
@@ -1,3 +1,24 @@
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# this should echo nothing
printf '%ls'
# this should echo a null byte
printf '%lc' | hexdump -b
# this should echo a null byte per posix interp 1647
printf '%lc' '' | hexdump -b
# test %ls and %lc with multibyte characters
V=ಇಳಿಕೆಗಳು
@@ -19,3 +40,6 @@ printf "%C\n" "$V3" | hexdump -b
printf "%4.2lc\n" "$V3" | hexdump -b
printf "%-4.2lc---\n" "$V3" | hexdump -b
# make sure %ls handles 0 precision the same as %s
printf '[%.0s][%.0ls]\n' X X
+68
View File
@@ -0,0 +1,68 @@
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# tests of integer overflow for field width and precision arguments
INT_MAX=$(getconf INT_MAX)
TOOBIG=$(( $INT_MAX * 10 ))
printf '[%*s]\n' "${TOOBIG}"
printf '[%*s]\n' "${TOOBIG}" X
printf -v VAR '[%*s]' "${TOOBIG}"
echo VAR="$VAR"
unset -v VAR
printf -v VAR '[%*s]' "${TOOBIG}" X
echo VAR="$VAR"
unset -v VAR
TOOBIG=9223372036854775825
printf '[%*s]\n' "${TOOBIG}"
printf '[%*s]\n' "${TOOBIG}" X
printf -v VAR '[%*s]' "${TOOBIG}"
echo VAR="$VAR"
unset -v VAR
printf -v VAR '[%*s]' "${TOOBIG}" X
echo VAR="$VAR"
unset -v VAR
TOOBIG=$(( $INT_MAX * 10 ))
printf '[%.*s]\n' "${TOOBIG}"
printf '[%.*s]\n' "${TOOBIG}" X
printf -v VAR '[%.*s]' "${TOOBIG}"
echo VAR="$VAR"
unset -v VAR
printf -v VAR '[%.*s]' "${TOOBIG}" X
echo VAR="$VAR"
unset -v VAR
TOOBIG=9223372036854775825
printf '[%.*s]\n' "${TOOBIG}"
printf '[%.*s]\n' "${TOOBIG}" X
printf -v VAR '[%.*s]' "${TOOBIG}"
echo VAR="$VAR"
unset -v VAR
printf -v VAR '[%.*s]' "${TOOBIG}" X
echo VAR="$VAR"
unset -v VAR
# out of range inline precisions
printf "%.${TOOBIG}s\n" XY
printf "%.${TOOBIG}Q\n" XY
+2
View File
@@ -31,6 +31,8 @@ argv[1] = < foo>
argv[1] = <foo>
argv[1] = <foo>
argv[1] = < foo>
./read.tests: line 101: b: readonly variable
a = a b = c = stat = 2
a = abcdefg
xyz
a = xyz
+9
View File
@@ -96,6 +96,15 @@ echo " foo" | { IFS=$' \t\n' ; read line; recho "$line"; }
echo " foo" | { IFS=$':' ; read line; recho "$line"; }
# this leaves `b' readonly
readonly b
read a b c <<EOF
a b c
EOF
# the latest POSIX draft says $? should be > 1
echo a = $a b = $b c = $c stat = $?
# test read -d delim behavior
${THIS_SH} ./read1.sub
+1 -1
View File
@@ -1,2 +1,2 @@
${THIS_SH} ./braces.tests > ${BASH_TSTOUT}
${THIS_SH} ./braces.tests > ${BASH_TSTOUT} 2>&1
diff ${BASH_TSTOUT} braces.right && rm -f ${BASH_TSTOUT}
+5 -13
View File
@@ -3695,7 +3695,9 @@ copy_variable (SHELL_VAR *var)
static void
dispose_variable_value (SHELL_VAR *var)
{
if (function_p (var))
if (nofree_p (var))
var_setvalue (var, (char *)NULL);
else if (function_p (var))
dispose_command (function_cell (var));
#if defined (ARRAY_VARS)
else if (array_p (var))
@@ -3930,18 +3932,8 @@ makunbound (const char *name, VAR_CONTEXT *vc)
if (old_var && local_p (old_var) &&
(old_var->context == variable_context || (localvar_unset && old_var->context < variable_context)))
{
if (nofree_p (old_var))
var_setvalue (old_var, (char *)NULL);
#if defined (ARRAY_VARS)
else if (array_p (old_var))
array_dispose (array_cell (old_var));
else if (assoc_p (old_var))
assoc_dispose (assoc_cell (old_var));
#endif
else if (nameref_p (old_var))
FREE (nameref_cell (old_var));
else
FREE (value_cell (old_var));
dispose_variable_value (old_var);
#if 0
/* Reset the attributes. Preserve the export attribute if the variable
came from a temporary environment. Make sure it stays local, and