From 403b32faf6a5b31ed58a42d1ff696fb8d3d7d5b9 Mon Sep 17 00:00:00 2001 From: Chet Ramey Date: Thu, 23 Oct 2025 16:35:55 -0400 Subject: [PATCH] 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 --- CWRU/CWRU.chlog | 77 ++++++++++++++++++++++++++++--- arrayfunc.c | 16 +++++-- bashline.c | 8 ++-- builtins/mkbuiltins.c | 105 ++++++++++++++++++------------------------ builtins/read.def | 5 +- execute_cmd.c | 60 +++++++++++++----------- subst.c | 12 +++++ subst.h | 1 + tests/RUN-TEST-SCRIPT | 16 ++++++- tests/builtins.right | 12 ++--- tests/conv-warnings | 4 -- variables.c | 81 ++++++++++++++++++++++++++------ 12 files changed, 271 insertions(+), 126 deletions(-) delete mode 100644 tests/conv-warnings diff --git a/CWRU/CWRU.chlog b/CWRU/CWRU.chlog index 34efc0df..17786954 100644 --- a/CWRU/CWRU.chlog +++ b/CWRU/CWRU.chlog @@ -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 + jobs to wait for. + Report and fix from OÄuz 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 + of calling make_variable_value appropriately. + Fixes bug reported by Oguz 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 + result. + Report from Oguz 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 + +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 + + 10/20 + ----- +execute_cmd.c + - mkfmt: round when the precision is 0 like with other precision values + Report and patch from Grisha Levit + +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 + +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 + + 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 back on 8/8/2025 + diff --git a/arrayfunc.c b/arrayfunc.c index 72ec83b3..fd3ebb1a 100644 --- a/arrayfunc.c +++ b/arrayfunc.c @@ -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; diff --git a/bashline.c b/bashline.c index e4f9975b..bd5a2542 100644 --- a/bashline.c +++ b/bashline.c @@ -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; } diff --git a/builtins/mkbuiltins.c b/builtins/mkbuiltins.c index 2a27826f..04e1bf0c 100644 --- a/builtins/mkbuiltins.c +++ b/builtins/mkbuiltins.c @@ -44,6 +44,7 @@ #include "filecntl.h" #include "../bashansi.h" +#include #include #include @@ -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 (string_array) { - if (filename_p == 0) - fprintf (stream, "\"),\n"); - else - fprintf (stream, "\",\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"); } - - if (string_array) - fprintf (stream, "#endif /* HELP_BUILTIN */\n (char *)NULL\n};\n"); } int diff --git a/builtins/read.def b/builtins/read.def index 6b62d0be..c6ab8b2d 100644 --- a/builtins/read.def +++ b/builtins/read.def @@ -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 { diff --git a/execute_cmd.c b/execute_cmd.c index 436f37ba..8d4e9e67 100644 --- a/execute_cmd.c +++ b/execute_cmd.c @@ -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++) { diff --git a/subst.c b/subst.c index fd7262a4..0e804b76 100644 --- a/subst.c +++ b/subst.c @@ -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]) diff --git a/subst.h b/subst.h index c9f155a8..59f09065 100644 --- a/subst.h +++ b/subst.h @@ -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); diff --git a/tests/RUN-TEST-SCRIPT b/tests/RUN-TEST-SCRIPT index 2aff0e09..5d962abd 100755 --- a/tests/RUN-TEST-SCRIPT +++ b/tests/RUN-TEST-SCRIPT @@ -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} "$@" diff --git a/tests/builtins.right b/tests/builtins.right index db0f6864..c0194b86 100644 --- a/tests/builtins.right +++ b/tests/builtins.right @@ -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. diff --git a/tests/conv-warnings b/tests/conv-warnings deleted file mode 100644 index 2cdd25a7..00000000 --- a/tests/conv-warnings +++ /dev/null @@ -1,4 +0,0 @@ -for f ; do - echo $f - sed 's|warning:|${CSTART}warning${CEND}:|' < $f > zzz && mv zzz $f -done diff --git a/variables.c b/variables.c index 7f1449d1..543b4bf7 100644 --- a/variables.c +++ b/variables.c @@ -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. */ -- 2.47.3