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

Fix crash in xtrace while processing here-document (re: d7cada7b)

Depending on the OS, the heredoc.sh regression tests, and possibly
others, still crashed with the -x option (xtrace) on.

Analysis: The lexer crashes in lex_advance(). Something has caused
an inconsistent lexer state, and it happened earlier on, so the
backtrace is useless for figuring out where that happened.

But I think I've found it. It's the sh_mactry() call here:

src/cmd/ksh93/sh/xec.c, lines 2800 to 2807 in f7213f03
2800:   if(!(cp=nv_getval(sh_scoped(shp,PS4NOD))))
2801:           cp = "+ ";
2802:   else
2803:   {
2804:           sh_offoption(SH_XTRACE);
2805:           cp = sh_mactry(shp,cp);
2806:           sh_onoption(SH_XTRACE);
2807:   }

sh_mactry() needs to parse the contents of $PS4 to perform
expansions and command substitutions in it, which involves the
lexer. If that happens in a here-document, the lexer is in the C
function call stack, in the middle of parsing the here-document.
Result: inconsistent lexer state. Solution: save and restore lexer
state in sh_mactry().

After this commit, all regression tests should pass with the
'-x'/'--xtrace' option in use, with no errors or crashes.

Note for backporters: this fix depends both on on d7cada7b and on
the consistency fix for the Lex_t type's size applied in a7ed5d9f.

src/cmd/ksh93/include/shlex.h:
- Cosmetic fix: remove a copied & pasted backslash. (re: a7ed5d9f)

src/cmd/ksh93/sh/macro.c: sh_mactry():
- Save and restore the lexer state before letting sh_mactrim()
  indirectly parse and execute code.

src/cmd/ksh93/tests/*.sh:
- Turn off xtrace in various command substitutions that contain
  2>&1 redirections, so that the xtrace output is not caught by
  the command substitutions, causing tests to fail incorrectly.
- Turn off xtrace for a few code blocks with 2>&1 redirections,
  stopping xtrace output from being written to standard output.

Resolves: https://github.com/ksh93/ksh/issues/306 (again)
This commit is contained in:
Martijn Dekker 2021-12-27 03:39:32 +00:00
parent 91a7c2e3e9
commit e072e7c170
10 changed files with 28 additions and 16 deletions

4
NEWS
View file

@ -8,6 +8,10 @@ Any uppercase BUG_* names are modernish shell bug IDs.
- Fixed a crash or freeze that would occur on Linux when using Ctrl+C to - Fixed a crash or freeze that would occur on Linux when using Ctrl+C to
interrupt a command substitution containing a pipe in an interactive shell. interrupt a command substitution containing a pipe in an interactive shell.
- Fixed a crash that could occur while processing a here-document while
xtrace (set -x) is on and the $PS4 prompt contains parameter expansions or
command substitutions.
2021-12-26: 2021-12-26:
- Listing aliases or tracked aliases in a script no longer corrupts - Listing aliases or tracked aliases in a script no longer corrupts

View file

@ -103,7 +103,7 @@ typedef struct _shlex_
Dt_t *entity_tree; /* for entity ids */ Dt_t *entity_tree; /* for entity ids */
#endif /* SHOPT_KIA */ #endif /* SHOPT_KIA */
/* The following two struct members are considered private to lex.c */ /* The following two struct members are considered private to lex.c */
struct _shlex_pvt_lexdata_ lexd; \ struct _shlex_pvt_lexdata_ lexd;
struct _shlex_pvt_lexstate_ lex; struct _shlex_pvt_lexstate_ lex;
} Lex_t; } Lex_t;

View file

@ -124,6 +124,7 @@ void *sh_macopen(Shell_t *shp)
/* /*
* perform only parameter substitution and catch failures * perform only parameter substitution and catch failures
* (also save lexer state to allow use while in here-docs)
*/ */
char *sh_mactry(Shell_t *shp,register char *string) char *sh_mactry(Shell_t *shp,register char *string)
{ {
@ -132,11 +133,13 @@ char *sh_mactry(Shell_t *shp,register char *string)
int jmp_val; int jmp_val;
int savexit = shp->savexit; int savexit = shp->savexit;
struct checkpt buff; struct checkpt buff;
Lex_t *lexp = (Lex_t*)sh.lex_context, savelex = *lexp;
sh_pushcontext(shp,&buff,SH_JMPSUB); sh_pushcontext(shp,&buff,SH_JMPSUB);
jmp_val = sigsetjmp(buff.buff,0); jmp_val = sigsetjmp(buff.buff,0);
if(jmp_val == 0) if(jmp_val == 0)
string = sh_mactrim(shp,string,0); string = sh_mactrim(shp,string,0);
sh_popcontext(shp,&buff); sh_popcontext(shp,&buff);
*lexp = savelex;
shp->savexit = savexit; shp->savexit = savexit;
return(string); return(string);
} }

View file

@ -943,7 +943,7 @@ fi
# ====== # ======
# https://github.com/ksh93/ksh/issues/334#issuecomment-968603087 # https://github.com/ksh93/ksh/issues/334#issuecomment-968603087
exp=21 exp=21
got=$(typeset -Z x=0x15; { echo $((x)); } 2>&1) got=$(typeset -Z x=0x15; set +x; { echo $((x)); } 2>&1)
[[ $got == "$exp" ]] || err_exit "typeset -Z corrupts hexadecimal number in arithmetic context" \ [[ $got == "$exp" ]] || err_exit "typeset -Z corrupts hexadecimal number in arithmetic context" \
"(expected $(printf %q "$exp"), got $(printf %q "$got"))" "(expected $(printf %q "$exp"), got $(printf %q "$got"))"

View file

@ -150,7 +150,7 @@ a=blue; let "a*=3" 2>/dev/null && err_exit "arithmetic can assign out of range (
# Enum types should parse with 'command' prefix(es) and options and instantly # Enum types should parse with 'command' prefix(es) and options and instantly
# recognise subsequent builtins it creates, even as a oneliner, even with # recognise subsequent builtins it creates, even as a oneliner, even with
# shcomp. (This requires an ugly parser hack that this tests for.) # shcomp. (This requires an ugly parser hack that this tests for.)
got=$(eval 2>&1 'command command command enum -i -i -iii --igno -ii PARSER_t=(r g b); '\ got=$(set +x; eval 2>&1 'command command command enum -i -i -iii --igno -ii PARSER_t=(r g b); '\
'command command PARSER_t -r -rrAAA -A -rArArA -Arrrrrrr hack=([C]=G); typeset -p hack') 'command command PARSER_t -r -rrAAA -A -rArArA -Arrrrrrr hack=([C]=G); typeset -p hack')
exp='PARSER_t -r -A hack=([C]=g)' exp='PARSER_t -r -A hack=([C]=g)'
[[ $got == "$exp" ]] || err_exit "incorrect typeset output for enum with command prefix and options" \ [[ $got == "$exp" ]] || err_exit "incorrect typeset output for enum with command prefix and options" \

View file

@ -593,7 +593,7 @@ result=$("$SHELL" -c 'echo ok > >(sed s/ok/good/); wait' 2>&1)
# Process substitution in an interactive shell or profile script shouldn't # Process substitution in an interactive shell or profile script shouldn't
# print the process ID of the asynchronous process. # print the process ID of the asynchronous process.
echo 'false >(false)' > "$tmp/procsub-envtest" echo 'false >(false)' > "$tmp/procsub-envtest"
result=$(ENV=$tmp/procsub-envtest "$SHELL" -ic 'true >(true)' 2>&1) result=$(set +x; ENV=$tmp/procsub-envtest "$SHELL" -ic 'true >(true)' 2>&1)
[[ -z $result ]] || err_exit 'interactive shells and/or profile scripts print a PID during process substitution' \ [[ -z $result ]] || err_exit 'interactive shells and/or profile scripts print a PID during process substitution' \
"(expected '', got $(printf %q "$result"))" "(expected '', got $(printf %q "$result"))"

View file

@ -171,6 +171,9 @@ fi
# ====== # ======
# Tests for 'return' and 'exit' without argument: they should pass down the previous exit status # Tests for 'return' and 'exit' without argument: they should pass down the previous exit status
#
# Some of these tests use 'function foo' instead of foo() to locally turn off
# xtrace; otherwise the 2>&1 redirection would write xtrace to standard output.
foo() { return; } foo() { return; }
false false
@ -187,24 +190,26 @@ foo && err_exit "'return &&' does not preserve exit status"
foo() { false; while return; do true; done; } foo() { false; while return; do true; done; }
foo && err_exit "'while return' does not preserve exit status" foo && err_exit "'while return' does not preserve exit status"
foo() { false; while return; do true; done 2>&1; } function foo { false; while return; do true; done 2>&1; }
foo && err_exit "'while return' with redirection does not preserve exit status" foo && err_exit "'while return' with redirection does not preserve exit status"
foo() { false; until return; do true; done; } foo() { false; until return; do true; done; }
foo && err_exit "'until return' does not preserve exit status" foo && err_exit "'until return' does not preserve exit status"
foo() { false; until return; do true; done 2>&1; } function foo { false; until return; do true; done 2>&1; }
foo && err_exit "'until return' with redirection does not preserve exit status" foo && err_exit "'until return' with redirection does not preserve exit status"
foo() { false; for i in 1; do return; done; } foo() { false; for i in 1; do return; done; }
foo && err_exit "'return' within 'for' does not preserve exit status" foo && err_exit "'return' within 'for' does not preserve exit status"
foo() { false; for i in 1; do return; done 2>&1; } function foo { false; for i in 1; do return; done 2>&1; }
foo && err_exit "'return' within 'for' with redirection does not preserve exit status" foo && err_exit "'return' within 'for' with redirection does not preserve exit status"
foo() { false; { return; } 2>&1; } function foo { false; { return; } 2>&1; }
foo && err_exit "'return' within { block; } with redirection does not preserve exit status" foo && err_exit "'return' within { block; } with redirection does not preserve exit status"
# Subshell functions. These have no ksh variant, but we can just turn off xtrace directly in them (set +x).
foo() ( exit ) foo() ( exit )
false false
foo && err_exit "'exit' within function does not preserve exit status" foo && err_exit "'exit' within function does not preserve exit status"
@ -220,22 +225,22 @@ foo && err_exit "'exit &&' does not preserve exit status"
foo() ( false; while exit; do true; done ) foo() ( false; while exit; do true; done )
foo && err_exit "'while exit' does not preserve exit status" foo && err_exit "'while exit' does not preserve exit status"
foo() ( false; while exit; do true; done 2>&1 ) foo() ( set +x; false; while exit; do true; done 2>&1 )
foo && err_exit "'while exit' with redirection does not preserve exit status" foo && err_exit "'while exit' with redirection does not preserve exit status"
foo() ( false; until exit; do true; done ) foo() ( false; until exit; do true; done )
foo && err_exit "'until exit' does not preserve exit status" foo && err_exit "'until exit' does not preserve exit status"
foo() ( false; until exit; do true; done 2>&1 ) foo() ( set +x; false; until exit; do true; done 2>&1 )
foo && err_exit "'until exit' with redirection does not preserve exit status" foo && err_exit "'until exit' with redirection does not preserve exit status"
foo() ( false; for i in 1; do exit; done ) foo() ( false; for i in 1; do exit; done )
foo && err_exit "'exit' within 'for' does not preserve exit status" foo && err_exit "'exit' within 'for' does not preserve exit status"
foo() ( false; for i in 1; do exit; done 2>&1 ) foo() ( set +x; false; for i in 1; do exit; done 2>&1 )
foo && err_exit "'exit' within 'for' with redirection does not preserve exit status" foo && err_exit "'exit' within 'for' with redirection does not preserve exit status"
foo() ( false; { exit; } 2>&1 ) foo() ( set +x; false; { exit; } 2>&1 )
foo && err_exit "'exit' within { block; } with redirection does not preserve exit status" foo && err_exit "'exit' within { block; } with redirection does not preserve exit status"
foo() { false; (exit); } foo() { false; (exit); }

View file

@ -538,7 +538,7 @@ cat > exit267 <<-EOF # unquoted delimiter; expansion active
foo foo
EOF EOF
exp="OK $((signum+256))" exp="OK $((signum+256))"
got=$( { "$SHELL" exit267; } 2>&1 ) got=$( set +x; { "$SHELL" exit267; } 2>&1 )
(( (e=$?)==signum+128 )) && [[ $got == "$exp" ]] || err_exit "'return' with status > 256:" \ (( (e=$?)==signum+128 )) && [[ $got == "$exp" ]] || err_exit "'return' with status > 256:" \
"(expected status $((signum+128)) and $(printf %q "$exp"), got status $e and $(printf %q "$got"))" "(expected status $((signum+128)) and $(printf %q "$exp"), got status $e and $(printf %q "$got"))"
@ -549,7 +549,7 @@ cat > bar <<-'EOF'
echo OK echo OK
EOF EOF
exp="OK" exp="OK"
got=$( { "$SHELL" bar; } 2>&1 ) got=$( set +x; { "$SHELL" bar; } 2>&1 )
(( (e=$?)==0 )) && [[ $got == "$exp" ]] || err_exit "segfaulting child process:" \ (( (e=$?)==0 )) && [[ $got == "$exp" ]] || err_exit "segfaulting child process:" \
"(expected status 0 and $(printf %q "$exp"), got status $e and $(printf %q "$got"))" "(expected status 0 and $(printf %q "$exp"), got status $e and $(printf %q "$got"))"

View file

@ -467,7 +467,7 @@ cat > B_t <<- \EOF
EOF EOF
unset n unset n
if n=$(FPATH=$PWD PATH=$PWD:$PATH "$SHELL" -c 'A_t a; print ${a.b.n}' 2>&1) if n=$(set +x; FPATH=$PWD PATH=$PWD:$PATH "$SHELL" -c 'A_t a; print ${a.b.n}' 2>&1)
then [[ $n == '5' ]] || err_exit "dynamic loading of types gives wrong result (got $(printf %q "$n"))" then [[ $n == '5' ]] || err_exit "dynamic loading of types gives wrong result (got $(printf %q "$n"))"
else err_exit "unable to load types dynamically (got $(printf %q "$n"))" else err_exit "unable to load types dynamically (got $(printf %q "$n"))"
fi fi

View file

@ -822,7 +822,7 @@ Errors=$? # ensure error count survives subshell
( (
# $x must be an unknown locale. # $x must be an unknown locale.
for x in x x.b@d xx_XX xx_XX.b@d for x in x x.b@d xx_XX xx_XX.b@d
do errmsg=$({ LANG=$x; } 2>&1) do errmsg=$(set +x; { LANG=$x; } 2>&1)
[[ -n $errmsg ]] && break [[ -n $errmsg ]] && break
done done
if [[ -z $errmsg ]] if [[ -z $errmsg ]]