round seconds in command timing information if precision is >= 0; fix for readline word boundaries when quoted command substitutions appear; remove nameref attribute when converting nameref variable into an array; change how help strings are written to builtins.c; change read builtin to update unwind-protect if the input string is reallocated; fix crash when an expansion on the rhs of an assignment statement unsets the variable on the lhs

This commit is contained in:
Chet Ramey
2025-10-23 16:35:55 -04:00
parent 6edfd0bf64
commit 403b32faf6
12 changed files with 272 additions and 127 deletions
+71 -6
View File
@@ -13,7 +13,8 @@ Makefile.in
-----
builtins/wait.def
- wait_builtin: don't assign the variable given with -p if there are no
jobs to wait for. Report and fix from OÄuz <oguzismailuysal@gmail.com>
jobs to wait for.
Report and fix from OÄuz <oguzismailuysal@gmail.com>
arrayfunc.c
- kvpair_assignment_p: return non-zero if argument L appears to be a
@@ -28,8 +29,8 @@ arrayfunc.h
subst.c
- expand_oneword: detect whether VALUE appears to be a key-value
pair compound assignment and call the appropriate function to expand
each word in the resulting list. Fixes inconsistency reported by
oguzismailuysal@gmail.com
each word in the resulting list.
Fixes inconsistency reported by oguzismailuysal@gmail.com
12/12
-----
@@ -152,8 +153,8 @@ variables.c
array variable that was the value of a nameref (used in the original
assignment), don't call make_variable_value on the value, since that
messes up +=. Just call assign_array_element and let that take care
of calling make_variable_value appropriately. Fixes bug reported by
Oguz <oguzismailuysal@gmail.com>
of calling make_variable_value appropriately.
Fixes bug reported by Oguz <oguzismailuysal@gmail.com>
1/14
----
@@ -206,7 +207,8 @@ Makefile.in
subst.c
- param_expand: if a nameref expands to array[@] or array[*], make sure
to call chk_atstar so the right variables are set to split the
result. Report from Oguz <oguzismailuysal@gmail.com>
result.
Report from Oguz <oguzismailuysal@gmail.com>
1/22
----
@@ -12024,3 +12026,66 @@ lib/glob/sm_loop.c
oslib.c
- bcopy: update function declaration to add `const'
execute_cmd.c
- mkfmt: fix rounding of fractional part of the printed value to
increment the seconds value if necessary (e.g., 0.9848 -> 1.0 if
precision == 1)
- mkfmt: compute the seconds and seconds fraction before writing
anything into the buffer in case we increment the seconds
Report and patch from Grisha Levit <grishalevit@gmail.com>
subst.h
- SD_QUOTEDSTR: tell skip_to_delim that we're skipping over a quoted
string; *delims is the close quote
subst.c
- skip_to_delim: understand SD_QUOTEDSTR and call skip_single_quoted
or skip_double_quoted immediately as appropriate
bashline.c
- bash_forward_shellword: use SD_QUOTEDSTR in the call to skip_to_delim
Fixes bug reported by Grisha Levit <grishalevit@gmail.com>
10/20
-----
execute_cmd.c
- mkfmt: round when the precision is 0 like with other precision values
Report and patch from Grisha Levit <grishalevit@gmail.com>
variables.c
- make_local_array_variable,make_local_assoc_variable: if we're
converting an unset nameref into an array, warn about it and turn
off the nameref attribute
Report from Grisha Levit <grishalevit@gmail.com>
builtins/mkbuiltins.c
- write_documentation: change to use modern C-style strings, where
adjacent strings are effectively concatenated
- write_documentation: don't add indent to otherwise blank lines
Original patch from Martin D Kealey <martin@kurahaupo.gen.nz>
10/21
-----
builtins/read.def
- read_builtin: since referring to a pointer after realloc may have
freed it is, in theory, undefined behavior, redo the unwind-protect
every time we increase the size of input_string. This updates the
CHERI-only change of 10/6
From https://savannah.gnu.org/bugs/?67586
10/22
-----
arrayfunc.c
- make_array_variable_value,assign_array_element_internal: set
att_assigning while evaluating the value to be assigned so the rest
of the shell knows not to unset the variable
variables.c
- bind_variable_internal,bind_variable_value: set att_assigning
around calls to make_variable_value to avoid unsetting the variable
- makunbound: if we are assigning to the variable being unset, just
empty it out and set it to invisible, assuming the variable will be
marked as visible after assignment
Report from Oguz <oguzismailuysal@gmail.com> back on 8/8/2025
+13 -3
View File
@@ -203,7 +203,13 @@ make_array_variable_value (SHELL_VAR *entry, arrayind_t ind, const char *key, co
dispose_variable (dentry);
}
else
newval = make_variable_value (entry, value, flags);
{
if (entry)
VSETATTR (entry, att_assigning);
newval = make_variable_value (entry, value, flags);
if (entry)
VUNSETATTR (entry, att_assigning);
}
return newval;
}
@@ -227,7 +233,7 @@ bind_assoc_var_internal (SHELL_VAR *entry, HASH_TABLE *hash, char *key, const ch
(*entry->assign_func) (entry, newval, 0, key);
FREE (key);
}
else
else if (assoc_p (entry))
assoc_insert (hash, key, newval);
FREE (newval);
@@ -251,7 +257,7 @@ bind_array_var_internal (SHELL_VAR *entry, arrayind_t ind, char *key, const char
(*entry->assign_func) (entry, newval, ind, key);
else if (assoc_p (entry))
assoc_insert (assoc_cell (entry), key, newval);
else
else if (array_p (entry))
array_insert (array_cell (entry), ind, newval);
FREE (newval);
@@ -432,7 +438,11 @@ assign_array_element_internal (SHELL_VAR *entry, const char *name, char *vname,
int avflags;
avflags = convert_assign_flags_to_arrayval_flags (flags);
if (entry)
VSETATTR (entry, att_assigning);
ind = array_expand_index (entry, sub, sublen, avflags);
if (entry)
VUNSETATTR (entry, att_assigning);
/* negative subscripts to indexed arrays count back from end */
if (entry && ind < 0)
ind = (array_p (entry) ? array_max_index (array_cell (entry)) : 0) + 1 + ind;
+4 -4
View File
@@ -1114,10 +1114,10 @@ bash_forward_shellword (int count, int key)
ADVANCE_CHAR (rl_line_buffer, slen, p);
break;
case '\'':
p = skip_to_delim (rl_line_buffer, ++p, "'", SD_NOJMP);
p = skip_to_delim (rl_line_buffer, ++p, "'", SD_NOJMP|SD_QUOTEDSTR|SD_COMPLETE);
break;
case '"':
p = skip_to_delim (rl_line_buffer, ++p, "\"", SD_NOJMP);
p = skip_to_delim (rl_line_buffer, ++p, "\"", SD_NOJMP|SD_QUOTEDSTR|SD_COMPLETE);
break;
}
@@ -1145,10 +1145,10 @@ bash_forward_shellword (int count, int key)
ADVANCE_CHAR (rl_line_buffer, slen, p);
break;
case '\'':
p = skip_to_delim (rl_line_buffer, ++p, "'", SD_NOJMP);
p = skip_to_delim (rl_line_buffer, ++p, "'", SD_NOJMP|SD_QUOTEDSTR|SD_COMPLETE);
break;
case '"':
p = skip_to_delim (rl_line_buffer, ++p, "\"", SD_NOJMP);
p = skip_to_delim (rl_line_buffer, ++p, "\"", SD_NOJMP|SD_QUOTEDSTR|SD_COMPLETE);
break;
}
+47 -60
View File
@@ -44,6 +44,7 @@
#include "filecntl.h"
#include "../bashansi.h"
#include <stdbool.h>
#include <stdio.h>
#include <errno.h>
@@ -1391,68 +1392,53 @@ write_documentation (FILE *stream, char **documentation, int indentation, int fl
{
int i, j;
char *line;
int string_array, texinfo, base_indent, filename_p;
int string_array, texinfo, filename_p;
int full_indent, base_indent;
if (stream == 0)
return;
string_array = flags & STRING_ARRAY;
filename_p = flags & HELPFILE;
texinfo = flags & TEXINFO;
base_indent = (string_array && single_longdoc_strings && filename_p == 0) ? BASE_INDENT : 0;
full_indent = indentation + base_indent;
if (string_array)
{
fprintf (stream, " {\n#if defined (HELP_BUILTIN)\n"); /* } */
if (single_longdoc_strings)
{
if (filename_p == 0)
{
if (documentation && documentation[0] && documentation[0][0])
fprintf (stream, "N_(\"");
else
fprintf (stream, "N_(\" "); /* the empty string translates specially. */
}
else
fprintf (stream, "\"");
}
if (filename_p == 0 && single_longdoc_strings)
fprintf (stream, "N_(");
}
base_indent = (string_array && single_longdoc_strings && filename_p == 0) ? BASE_INDENT : 0;
for (i = 0, texinfo = (flags & TEXINFO); documentation && (line = documentation[i]); i++)
for (i = 0; documentation && (line = documentation[i]); i++)
{
bool first_line = i == 0;
bool last_line = documentation[i+1] == 0;
/* Allow #ifdef's to be written out verbatim, but don't put them into
separate help files. */
if (*line == '#')
{
if (string_array && filename_p == 0 && single_longdoc_strings == 0)
if (string_array)
fprintf (stream, "%s\n", line);
continue;
}
/* prefix with N_( for gettext */
if (string_array && single_longdoc_strings == 0)
{
if (filename_p == 0)
{
if (line[0])
fprintf (stream, " N_(\"");
else
fprintf (stream, " N_(\" "); /* the empty string translates specially. */
}
else
fprintf (stream, " \"");
}
if (indentation)
for (j = 0; j < indentation; j++)
fprintf (stream, " ");
/* Don't indent the first line, because of how the help builtin works. */
if (i == 0)
indentation += base_indent;
if (string_array)
{
if (filename_p == 0 && single_longdoc_strings == 0)
fprintf (stream, "N_(");
else if (first_line == 0)
fputc ('\t', stream);
fputc ('"', stream);
if (filename_p == 0 && *line == 0 && last_line && first_line && indentation == 0)
line = " "; /* the empty string translates specially. */
if (indentation && *line)
fprintf (stream, "%*.0s", indentation, "");
for (j = 0; line[j]; j++)
{
if (line[j] == '\\' || line[j] == '"')
@@ -1460,20 +1446,18 @@ write_documentation (FILE *stream, char **documentation, int indentation, int fl
fputc (line[j], stream);
}
/* closing right paren for gettext */
if (single_longdoc_strings == 0)
{
if (filename_p == 0)
fprintf (stream, "\"),\n");
else
fprintf (stream, "\",\n");
}
else if (documentation[i+1])
/* don't add extra newline after last line */
fprintf (stream, "\\n\\\n");
if (last_line == 0)
fprintf (stream, "\\n");
fputc ('"', stream);
if (filename_p == 0 && single_longdoc_strings == 0)
fprintf (stream, "),");
if (last_line == 0)
fprintf (stream, "\n");
}
else if (texinfo)
{
{
if (indentation && *line)
fprintf (stream, "%*.0s", indentation, "");
for (j = 0; line[j]; j++)
{
switch (line[j])
@@ -1489,20 +1473,23 @@ write_documentation (FILE *stream, char **documentation, int indentation, int fl
fputc ('\n', stream);
}
else
fprintf (stream, "%s\n", line);
fprintf (stream, "%*.0s%s\n", indentation, "", line);
/* Don't indent the first line, because of how the help builtin works. */
indentation = full_indent;
}
/* closing right paren for gettext */
if (string_array && single_longdoc_strings)
{
if (filename_p == 0)
fprintf (stream, "\"),\n");
else
fprintf (stream, "\",\n");
}
if (string_array)
fprintf (stream, "#endif /* HELP_BUILTIN */\n (char *)NULL\n};\n");
{
if (single_longdoc_strings)
{
if (filename_p == 0)
fputc (')', stream);
fputc (',', stream);
}
fprintf (stream, "\n#endif /* HELP_BUILTIN */\n\t(char *)NULL\n};\n");
}
}
int
+3 -2
View File
@@ -816,8 +816,9 @@ read_builtin (WORD_LIST *list)
char *x;
x = (char *)xrealloc (input_string, size += 128);
/* Only need to change unwind-protect if input_string changes */
#ifndef __CHERI_PURE_CAPABILITY__
#if 0
/* This is, in theory, undefined behavior, since input_string may
have been freed. */
if (x != input_string)
#endif
{
+33 -27
View File
@@ -1273,18 +1273,48 @@ extern int timeval_to_cpu (struct timeval *, struct timeval *, struct timeval *)
#define BASH_TIMEFORMAT "\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS"
static const int precs[] = { 0, 100000, 10000, 1000, 100, 10, 1 };
static const int maxvals[] = { 1, 10, 100, 1000, 10000, 100000, 10000000 };
static const int maxvals[] = { 1, 10, 100, 1000, 10000, 100000, 1000000 };
/* Expand one `%'-prefixed escape sequence from a time format string. */
/* SEC_FRACTION is in usecs. We normalize and round that based on the
precision. */
int
static int
mkfmt (char *buf, int prec, int lng, time_t sec, long sec_fraction)
{
time_t min;
char abuf[INT_STRLEN_BOUND(time_t) + 1];
int ind, aind;
/* We want to add a decimal point and PREC places after it if PREC is
nonzero. PREC is not greater than 6. SEC_FRACTION is between 0
and 999999 (microseconds). */
if (prec < 6)
{
/* We round here because we changed timeval_to_secs to return
microseconds and normalized clock_t_to_secs's fractional return
value to microseconds, deferring the work to be done to now.
sec_fraction is in microseconds. Take the value, cut off what we
don't want, round up if necessary, then convert back to
microseconds. */
int frac, rest, maxval;
maxval = maxvals[6 - prec];
frac = sec_fraction / maxval;
rest = sec_fraction % maxval;
if (rest >= maxval/2)
frac++;
if (frac == maxvals[prec])
{
sec++;
sec_fraction = 0;
}
else
sec_fraction = frac * (1000000 / maxvals[prec]);
}
ind = 0;
abuf[sizeof(abuf) - 1] = '\0';
@@ -1312,32 +1342,8 @@ mkfmt (char *buf, int prec, int lng, time_t sec, long sec_fraction)
while (abuf[aind])
buf[ind++] = abuf[aind++];
/* We want to add a decimal point and PREC places after it if PREC is
nonzero. PREC is not greater than 6. SEC_FRACTION is between 0
and 999999 (microseconds). */
if (prec != 0)
if (prec > 0)
{
/* We round here because we changed timeval_to_secs to return
microseconds and normalized clock_t_to_secs's fractional return
value to microseconds, deferring the work to be done to now.
sec_fraction is in microseconds. Take the value, cut off what we
don't want, round up if necessary, then convert back to
microseconds. */
if (prec != 6)
{
int frac, rest, maxval;
maxval = maxvals[6 - prec];
frac = sec_fraction / maxval;
rest = sec_fraction % maxval;
if (rest >= maxval/2)
frac++;
sec_fraction = frac * (1000000 / maxvals[prec]);
}
buf[ind++] = locale_decpoint ();
for (aind = 1; aind <= prec; aind++)
{
+12
View File
@@ -2217,6 +2217,18 @@ skip_to_delim (const char *string, int start, const char *delims, int flags)
arithexp = (flags & SD_ARITHEXP);
skipcol = 0;
/* If the caller tells us we're skipping a quoted string after the opening
quote, and the delimiter is a single- or double-quote, skip all the logic
here and just call the same functions as word expansion. */
if ((flags & SD_QUOTEDSTR) && (*delims == '\'' || *delims == '"') && delims[1] == '\0')
{
if (*delims == '\'')
i = skip_single_quoted (string, slen, start, 0);
else
i = skip_double_quoted (string, slen, start, completeflag);
CQ_RETURN (i);
}
i = start;
pass_next = backq = dquote = 0;
while (c = string[i])
+1
View File
@@ -336,6 +336,7 @@ extern char *cond_expand_word (WORD_DESC *, int);
#define SD_HISTEXP 0x200 /* skip_to_delim during history expansion */
#define SD_ARITHEXP 0x400 /* skip_to_delim during arithmetic expansion */
#define SD_NOERROR 0x800 /* don't print error messages */
#define SD_QUOTEDSTR 0x1000 /* skipping a part of a single- or double-quoted string */
extern int skip_to_delim (const char *, int, const char *, int);
+14 -2
View File
@@ -1,10 +1,22 @@
: ${BUILD_DIR:=/usr/local/build/chet/bash/bash-current}
: ${BUILD_DIR:=$PWD}
THIS_SH=$BUILD_DIR/bash
PATH=$PATH:$BUILD_DIR
export THIS_SH PATH
export THIS_SH PATH BUILD_DIR
: ${TMPDIR:=/tmp}
export TMPDIR
export BASH_TSTOUT=/tmp/xx
rm -f ${BASH_TSTOUT}
if [ -t 1 ]; then
# can't rely on having $'...' or printf understanding \e
# bright red background, white foreground text
export CSTART=$(printf '\033[01;101;37m') CEND=$(printf '\033[0m')
else
export CSTART= CEND=
fi
export TAB=' '
${THIS_SH} "$@"
+6 -6
View File
@@ -406,10 +406,10 @@ help: help [-dms] [pattern ...]
shift - Shift positional parameters.
shift: shift [n]
Shift positional parameters.
Rename the positional parameters $N+1,$N+2 ... to $1,$2 ... If N is
not given, it is assumed to be 1.
Exit Status:
Returns success unless N is negative or greater than $#.
builtin: builtin [shell-builtin [arg ...]]
@@ -424,9 +424,9 @@ readarray: readarray [-d delim] [-n count] [-O origin] [-s count] [-t] [-u fd] [
readonly: readonly [-aAf] [name[=value] ...] or readonly -p
:: :
Null command.
No effect; the command does nothing.
Exit Status:
Always succeeds.
NAME
@@ -437,9 +437,9 @@ SYNOPSIS
DESCRIPTION
Null command.
No effect; the command does nothing.
Exit Status:
Always succeeds.
-4
View File
@@ -1,4 +0,0 @@
for f ; do
echo $f
sed 's|warning:|${CSTART}warning${CEND}:|' < $f > zzz && mv zzz $f
done
+68 -13
View File
@@ -2824,6 +2824,13 @@ make_local_array_variable (const char *name, int flags)
if (var == 0 || array_p (var) || (assoc_ok && assoc_p (var)))
return var;
/* array variables cannot be namerefs */
if (var && nameref_p (var) && invisible_p (var))
{
internal_warning (_("%s: removing nameref attribute"), name);
VUNSETATTR (var, att_nameref);
}
/* Validate any value we inherited from a variable instance at a previous
scope and discard anything that's invalid. */
if (localvar_inherit && assoc_p (var))
@@ -2880,6 +2887,13 @@ make_local_assoc_variable (const char *name, int flags)
if (var == 0 || assoc_p (var) || (array_ok && array_p (var)))
return var;
/* assoc variables cannot be namerefs */
if (var && nameref_p (var) && invisible_p (var))
{
internal_warning (_("%s: removing nameref attribute"), name);
VUNSETATTR (var, att_nameref);
}
/* Validate any value we inherited from a variable instance at a previous
scope and discard anything that's invalid. */
if (localvar_inherit && array_p (var))
@@ -3123,13 +3137,19 @@ bind_variable_internal (const char *name, const char *value, HASH_TABLE *table,
#endif
{
entry = make_new_variable (newval, table);
var_setvalue (entry, make_variable_value (entry, value, aflags));
VSETATTR (entry, att_assigning);
newval = make_variable_value (entry, value, aflags);
VUNSETATTR (entry, att_assigning);
var_setvalue (entry, newval);
}
}
else if (entry == 0)
{
entry = make_new_variable (name, table);
var_setvalue (entry, make_variable_value (entry, value, aflags)); /* XXX */
VSETATTR (entry, att_assigning);
newval = make_variable_value (entry, value, aflags); /* XXX */
VUNSETATTR (entry, att_assigning);
var_setvalue (entry, newval);
}
else if (entry->assign_func) /* array vars have assign functions now */
{
@@ -3141,7 +3161,9 @@ bind_variable_internal (const char *name, const char *value, HASH_TABLE *table,
}
INVALIDATE_EXPORTSTR (entry);
VSETATTR (entry, att_assigning);
newval = (aflags & ASS_APPEND) ? make_variable_value (entry, value, aflags) : (char *)value;
VUNSETATTR (entry, att_assigning);
if (assoc_p (entry))
entry = (*(entry->assign_func)) (entry, newval, -1, savestring ("0"));
else if (array_p (entry))
@@ -3162,9 +3184,6 @@ assign_value:
return (entry);
}
/* Variables which are bound are visible. */
VUNSETATTR (entry, att_invisible);
/* If we can optimize the assignment, do so and return. Right now, we
optimize appends to string variables. */
if (can_optimize_assignment (entry, value, aflags))
@@ -3178,15 +3197,23 @@ assign_value:
if (exported_p (entry))
array_needs_making = 1;
/* Variables which are bound are visible. */
VUNSETATTR (entry, att_invisible);
return (entry);
}
VSETATTR (entry, att_assigning);
#if defined (ARRAY_VARS)
if (assoc_p (entry) || array_p (entry))
newval = make_array_variable_value (entry, 0, "0", value, aflags);
else
#endif
newval = make_variable_value (entry, value, aflags); /* XXX */
VUNSETATTR (entry, att_assigning);
/* Variables which are bound are visible. */
VUNSETATTR (entry, att_invisible);
/* Invalidate any cached export string */
INVALIDATE_EXPORTSTR (entry);
@@ -3326,23 +3353,24 @@ SHELL_VAR *
bind_variable_value (SHELL_VAR *var, char *value, int aflags)
{
char *t;
int invis;
invis = invisible_p (var);
VUNSETATTR (var, att_invisible);
if (var->assign_func)
{
/* If we're appending, we need the old value, so use
make_variable_value */
VSETATTR (var, att_assigning);
t = (aflags & ASS_APPEND) ? make_variable_value (var, value, aflags) : value;
VUNSETATTR (var, att_assigning);
VUNSETATTR (var, att_invisible);
(*(var->assign_func)) (var, t, -1, 0);
if (t != value && t)
free (t);
}
else
{
VSETATTR (var, att_assigning);
t = make_variable_value (var, value, aflags);
VUNSETATTR (var, att_assigning);
if ((aflags & (ASS_NAMEREF|ASS_FORCE)) == ASS_NAMEREF && check_selfref (name_cell (var), t, 0))
{
if (variable_context)
@@ -3351,20 +3379,17 @@ bind_variable_value (SHELL_VAR *var, char *value, int aflags)
{
internal_error (_("%s: nameref variable self references not allowed"), name_cell (var));
free (t);
if (invis)
VSETATTR (var, att_invisible); /* XXX */
return ((SHELL_VAR *)NULL);
}
}
if ((aflags & ASS_NAMEREF) && (valid_nameref_value (t, 0) == 0))
{
free (t);
if (invis)
VSETATTR (var, att_invisible); /* XXX */
return ((SHELL_VAR *)NULL);
}
FREE (value_cell (var));
var_setvalue (var, t);
VUNSETATTR (var, att_invisible);
}
INVALIDATE_EXPORTSTR (var);
@@ -4010,6 +4035,36 @@ makunbound (const char *name, VAR_CONTEXT *vc)
return (0);
}
/* If we are currently assigning this variable, but the evaluation of the
value causes it to be unset, we need to make sure pointers to the
variable struct remain valid, and insert a cleared-out version of the
variable back into the correct hash table. */
if (old_var && assigning_p (old_var))
{
dispose_variable_value (old_var);
var_setvalue (old_var, (char *)NULL);
old_var->attributes = 0;
VSETATTR (old_var, att_assigning);
VSETATTR (old_var, att_invisible);
INVALIDATE_EXPORTSTR (old_var);
old_var->dynamic_value = NULL;
old_var->assign_func = NULL;
/* leave the context unchanged if we're going to be assigning it */
/* old_var->context = 0; */
new_elt = hash_insert (savestring (old_var->name), v->table, 0);
new_elt->data = (PTR_T)old_var;
stupidly_hack_special_variables (old_var->name);
free (elt->key);
free (elt);
return (0);
}
/* Have to save a copy of name here, because it might refer to
old_var->name. If so, stupidly_hack_special_variables will
reference freed memory. */