ast
ast copied to clipboard
ksh dumps function body to stdout and freezes when eval'ing a function definition
Description of problem: Under certain conditions, when eval'ing a function definition, ksh (both beta and current development) erroneously dumps the function body to standard output and becomes prone to freezing. These conditions include file descriptor 3 (and no other) being exec'ed, and lots of function definitions having been previously eval'ed.
Ksh version: Version ABIJM 93v- 2014-12-24 Version A 2017.0.0-devel-2343-g09033f5
Steps to reproduce: Run the crazy test script below, and/or:
-
git clone -b 0.14 https://github.com/modernish/modernish
-
cd modernish
-
/path/to/ksh bin/modernish --test
Running the modernish regression test suite gives you lots of dumped function definitions to standard output and then a freeze.
Test script output (see crazy test script below)
$ /path/to/ksh test.sh
{ : This function body is dumped to standard output; }$
Expected output
$ /path/to/ksh test.sh
$
Additional info:
The test script below is crazy, and I apologise for that, but after about a year of intermittently trying to track down a specific trigger, it's the best I've been able to come up with. I came up with this by systematically deleting everything from modernish until it stopped triggering the bug. The test script below is at the point where if you remove even one line, or even change indentation, the bug is no longer triggered. Obviously all the code before # ------- MAIN -------
now does not make sense, as it was stripped down to the minimum needed to trigger the bug, so don't bother trying to understand it (look at the modernish code repo instead if you're curious).
#! /bin/ksh
eval '
function _Msh_testFn {
_Msh_test2=${_Msh_test}
}
function _Msh_testFn2 {
typeset _Msh_test=local || return
_Msh_testFn
}'
_Msh_tSH_testBI='case $CCn${_Msh_biCache}$CCn in
( *"$CCn${1#--bi=}$CCn"* ) ;;
( *"/${1#--bi=}$CCn"* )
# Found builtin with path name. Isolate the "directory" and check $PATH.
_Msh_tSH_D=${_Msh_biCache}${CCn}
_Msh_tSH_D=${_Msh_tSH_D%%/${1#--bi=}${CCn}*}
_Msh_tSH_D=${_Msh_tSH_D##*${CCn}}
case :$PATH: in
( *":${_Msh_tSH_D}:"* | *":${_Msh_tSH_D}/:"* )
unset -v _Msh_tSH_D ;;
( * ) unset -v _Msh_tSH_D; return 1 ;;
esac ;;
( * ) return 1 ;;
esac'
_Msh_tSH_testKW='case "${_Msh_kwCache} " in
( *" ${1#--[rk]w=} "* ) ;;
( *" !${1#--[rk]w=} "* ) return 1 ;;
( * ) case $(command -V "${1#--[rk]w=}" 2>/dev/null) in
( *"${_Msh_kwOutput}" )
_Msh_kwCache="${_Msh_kwCache} ${1#--[rk]w=}" ;;
( * ) _Msh_kwCache="${_Msh_kwCache} !${1#--[rk]w=}"
return 1 ;;
esac
esac'
eval 'thisshellhas() {
case ${#},${-} in
( 0,* ) _Msh_dieArgs thisshellhas "$#" "at least 1" || return ;;
( *a* ) set +a; thisshellhas "$@"; eval "set -a; return $?" ;;
esac
while :; do
case $1 in
( --cache )
_Msh_cacheCap
;;
( --show )
_Msh_cacheCap --show
;;
( "" | --bi= | --[rk]w= | --bi=*/* | --[rk]w=*/* \
| --bi=*[!\[\]\!{}"$SHELLSAFECHARS"]* \
| --[rk]w=*[!\[\]\!{}"$SHELLSAFECHARS"]* \
| --sig=*[!"$SHELLSAFECHARS"]* )
return 2 # invalid identifier
;;
( --bi=* )
'"${_Msh_tSH_testBI}"'
;;
( --[rk]w=* )
'"${_Msh_tSH_testKW}"'
;;
( --sig=* )
use -q var/stack/trap || die "thisshellhas: --sig: requires var/stack/trap" || return
if _Msh_arg2sig "${1#--sig=}" -nv; then
REPLY=${_Msh_sig}
unset -v _Msh_sig
else
unset -v _Msh_sig REPLY
return 1
fi ;;
( --* ) die "thisshellhas: invalid option: ${1%%=*}" || return
;;
( -o ) case ${2-} in
( allexport | errexit | ignoreeof | noclobber \
| noglob | noexec | nounset | verbose | xtrace )
;;
( "" | *[!"$ASCIIALNUM"_-]* | -* )
return 2 ;;
( * ) case " ${_Msh_optCache} " in
( *" $2 "* ) ;;
( *" !$2 "* ) return 1 ;;
( * ) if (set +o "$2") 2>/dev/null; then
_Msh_optCache=${_Msh_optCache:+${_Msh_optCache} }$2
else
_Msh_optCache=${_Msh_optCache:+${_Msh_optCache} }!$2
return 1
fi ;;
esac ;;
esac
shift ;;
( -[aCefmnuvx] )
;;
( -["$ASCIIALNUM"] )
case " ${_Msh_optCache} " in
( *" $1 "* ) ;;
( *" !$1 "* ) return 1 ;;
( * ) if isset "$1" || (set "+${1#-}") 2>/dev/null; then
_Msh_optCache=${_Msh_optCache:+${_Msh_optCache} }$1
else
_Msh_optCache=${_Msh_optCache:+${_Msh_optCache} }!$1
return 1
fi ;;
esac ;;
( -* ) return 2 ;;
( *[!ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_]* )
thisshellhas "--bi=$1" || thisshellhas "--rw=$1" || return
;;
( * ) '"${_Msh_tSH_bashLEPIPEMAIN}"'
case " ${_Msh_cap} " in
( *" $1 "* ) ;;
( *" !$1 "* | *" #ALLCACHED "* ) return 1 ;;
( * ) _Msh_doCapTest "$1"
case $? in
( 0 ) _Msh_cap=${_Msh_cap:+${_Msh_cap} }$1
unset -v _Msh_test ;;
( 1 | 127 ) # 127 = *.t file not found
_Msh_cap=${_Msh_cap:+${_Msh_cap} }!$1
unset -v _Msh_test
return 1 ;;
( * ) die "thisshellhas(): failure while testing for $1" || return ;;
esac ;;
esac
;;
esac
shift
case $# in
( 0 ) break ;;
esac
done
}'
_Msh_inSbSh_generic_part1='_Msh_inSbSh_P=$(exec "$MSH_SHELL" -c "set -o nounset && echo \$PPID" 2>/dev/null) \
&& case ${_Msh_inSbSh_P} in ("" | *[!0123456789]*) ! : ;; esac \
|| _Msh_inSbSh_P=$(PATH=$DEFPATH exec /bin/sh -c '\''ppid=`ps -o ppid= -p $$` && echo $ppid'\'')
case ${1-},${_Msh_inSbSh_P} in
( *, | *,*[!0123456789]* )
putln "${ME##*/}: insubshell: internal error: cannot determine parent PID" 1>&2
isset _Msh_die_isrunning && return 0 || die ;;'
_Msh_inSbSh_generic_part2='
(,$$) unset -v _Msh_inSbSh_P; return 1 ;;
(*,$$) REPLY=$$; unset -v _Msh_inSbSh_P; return 1 ;;
(,*) unset -v _Msh_inSbSh_P; return 0 ;;
(*,*) REPLY=${_Msh_inSbSh_P}; unset -v _Msh_inSbSh_P; return 0 ;;
esac'
_Msh_inSbSh_method='
if ((.sh.subshell > 0)); then
command ulimit -t unlimited 2>/dev/null
(($# == 0)) && return 0
fi
'"${_Msh_inSbSh_generic_part1}${_Msh_inSbSh_generic_part2}"
eval 'insubshell() {
case ${#},${1-} in
( 1,-p | 1,-u ) ;;
( [!0]* ) die "insubshell: invalid arguments: $@" || return ;;
esac
'"${_Msh_inSbSh_method}"'
}'
_Msh_isset='[[ -v $1 ]]'
_Msh_isset_v='[[ -v $2 ]]'
_Msh_isset_x='unset -v _Msh_issetEx_WasRun _Msh_issetEx_FoundIt
export "_Msh_issetExV=$2" # guarantee one exported variable to check if this method works
alias export=_Msh_issetExHandleExport
eval "$(command export -p)"
unalias export
unset -v _Msh_issetExV
isset _Msh_issetEx_WasRun && unset -v _Msh_issetEx_WasRun ||
die "isset -x: internal error: '\''export -p'\'' not parseable or '\''export'\'' not aliasable" || return
isset _Msh_issetEx_FoundIt && unset -v _Msh_issetEx_FoundIt'
_Msh_isset_r='! ( command unset -v "$2" || \exit 1 ) 2>/dev/null'
_Msh_isset_f='command typeset -f "$2" >/dev/null 2>&1'
eval 'isset() {
case ${#},${1-},${2-} in
( 1,-o, ) die "isset -o: long-form option name expected" ;;
( 1,-["$ASCIIALNUM"], )
case $- in ( *"${1#-}"* ) ;; ( * ) return 1 ;; esac ;;
( 1,, | 1,[0123456789]* | 1,*[!"$ASCIIALNUM"_]*, \
| 2,-[vxrf], | 2,-[vxrf],[0123456789]* | 2,-[vxrf],*[!"$ASCIIALNUM"_]* \
| 2,-o, | 2,-o,*[!"$ASCIIALNUM"_-]* )
return 2 ;; # invalid identifier
( 1,* ) '"${_Msh_isset}"' ;;
( 2,-v,* ) '"${_Msh_isset_v}"' ;;
( 2,-x,* ) '"${_Msh_isset_x}"' ;;
( 2,-r,* ) '"${_Msh_isset_r}"' ;;
( 2,-f,* ) '"${_Msh_isset_f}"' ;;
( 2,-* ) die "isset: invalid option: $2" ;;
( * ) die "isset: invalid arguments" ;;
esac
}'
eval '_Msh_qV_dblQuote() {
_Msh_qV=${_Msh_qV_VAL//\\/\\\\}
_Msh_qV=${_Msh_qV//\$/\\\$}
_Msh_qV=${_Msh_qV//\`/\\\`}
case ${_Msh_qV_VAL} in
( *[$CONTROLCHARS]* )
_Msh_qV=${_Msh_qV//$CC01/'\''${CC01}'\''}
_Msh_qV=${_Msh_qV//$CC02/'\''${CC02}'\''}
_Msh_qV=${_Msh_qV//$CC03/'\''${CC03}'\''}
_Msh_qV=${_Msh_qV//$CC04/'\''${CC04}'\''}
_Msh_qV=${_Msh_qV//$CC05/'\''${CC05}'\''}
_Msh_qV=${_Msh_qV//$CC06/'\''${CC06}'\''}
_Msh_qV=${_Msh_qV//$CC07/'\''${CCa}'\''}
_Msh_qV=${_Msh_qV//$CC08/'\''${CCb}'\''}
_Msh_qV=${_Msh_qV//$CC09/'\''${CCt}'\''}
_Msh_qV=${_Msh_qV//$CC0A/'\''${CCn}'\''}
_Msh_qV=${_Msh_qV//$CC0B/'\''${CCv}'\''}
_Msh_qV=${_Msh_qV//$CC0C/'\''${CCf}'\''}
_Msh_qV=${_Msh_qV//$CC0D/'\''${CCr}'\''}
_Msh_qV=${_Msh_qV//$CC0E/'\''${CC0E}'\''}
_Msh_qV=${_Msh_qV//$CC0F/'\''${CC0F}'\''}
_Msh_qV=${_Msh_qV//$CC10/'\''${CC10}'\''}
_Msh_qV=${_Msh_qV//$CC11/'\''${CC11}'\''}
_Msh_qV=${_Msh_qV//$CC12/'\''${CC12}'\''}
_Msh_qV=${_Msh_qV//$CC13/'\''${CC13}'\''}
_Msh_qV=${_Msh_qV//$CC14/'\''${CC14}'\''}
_Msh_qV=${_Msh_qV//$CC15/'\''${CC15}'\''}
_Msh_qV=${_Msh_qV//$CC16/'\''${CC16}'\''}
_Msh_qV=${_Msh_qV//$CC17/'\''${CC17}'\''}
_Msh_qV=${_Msh_qV//$CC18/'\''${CC18}'\''}
_Msh_qV=${_Msh_qV//$CC19/'\''${CC19}'\''}
_Msh_qV=${_Msh_qV//$CC1A/'\''${CC1A}'\''}
_Msh_qV=${_Msh_qV//$CC1B/'\''${CCe}'\''}
_Msh_qV=${_Msh_qV//$CC1C/'\''${CC1C}'\''}
_Msh_qV=${_Msh_qV//$CC1D/'\''${CC1D}'\''}
_Msh_qV=${_Msh_qV//$CC1E/'\''${CC1E}'\''}
_Msh_qV=${_Msh_qV//$CC1F/'\''${CC1F}'\''}
_Msh_qV=${_Msh_qV//$CC7F/'\''${CC7F}'\''} ;;
esac
_Msh_qV_VAL=\"${_Msh_qV//\"/\\\"}\"
}'
eval 'str() {
case ${#},${1-} in
( 1,empty | 1,eq )
;;
( 1,isint | 1,isvarname | 1,ne )
return 1 ;;
( 2,eq | 2,in | 2,begin | 2,end | 2,match | 2,ematch )
return 1 ;;
( 2,ne ) ;;
( 2,empty ) case ${2:+n} in ( n ) return 1 ;; esac ;;
( 2,isint ) case ${2#"${2%%[!" $CCt$CCn"]*}"} in
( 0[xX]*[!0123456789abcdefABCDEF]* | [+-]0[xX]*[!0123456789abcdefABCDEF]* )
return 1 ;;
( 0[xX]?* | [+-]0[xX]?* )
;;
( "" | [+-] | ?*[+-]* | *[!0123456789+-]* | 0*[!01234567]* | [+-]0*[!01234567]* )
return 1 ;;
esac ;;
( 2,isvarname ) case $2 in
( [0123456789]* | *[!"$ASCIIALNUM"_]* )
return 1 ;;
esac ;;
( 3,eq ) case $2 in ( "$3" ) ;; ( * ) return 1 ;; esac ;;
( 3,ne ) case $2 in ( "$3" ) return 1 ;; ( * ) ;; esac ;;
( 3,in ) case $2 in ( *"$3"* ) ;; ( * ) return 1 ;; esac ;;
( 3,begin ) case $2 in ( "$3"* ) ;; ( * ) return 1 ;; esac ;;
( 3,end ) case $2 in ( *"$3" ) ;; ( * ) return 1 ;; esac ;;
( 3,match ) '"${_Msh_doMatch}"' ;;
( 3,ematch ) '"${_Msh_doEMatch}"' ;;
( 3,lt ) '"${_Msh_doSortsBefore}"' ;;
( 3,gt ) '"${_Msh_doSortsAfter}"' ;;
( 3,le ) str lt "$2" "$3" || str eq "$2" "$3" ;;
( 3,ge ) str gt "$2" "$3" || str eq "$2" "$3" ;;
( *,empty | *,isint | *,isvarname )
_Msh_dieArgs "str $1" "$((${#}-1))" "max. 1" ;;
( *,eq | *,ne ) _Msh_dieArgs "str $1" "$((${#}-1))" "max. 2" ;;
( *,in | *,begin | *,end | *,match | *,ematch )
_Msh_dieArgs "str $1" "$((${#}-1))" "1 or 2" ;;
( *,lt | *,gt | *,le | *,ne )
_Msh_dieArgs "str $1" "$((${#}-1))" "2" ;;
( 0, ) die "str: operator expected" ;;
( * ) die "str: invalid operator: $1" ;;
esac
}'
# --------------------
# ------- MAIN -------
# --------------------
exec 3>&1
eval "fn() { : This function body is dumped to standard output; }"
There are intermittent test failures that are similar to the description above. It is likely to be yet another instance of using heap memory after it has been freed or something similar.
@McDutchie I take it you can't reproduce this problem with ksh93u+?
Op 10-02-19 om 02:24 schreef Kurtis Rader:
@McDutchie I take it you can't reproduce this problem with ksh93u+? Correct, it's only with the beta and the development code.
Perhaps this is the code to reproduce this Issue. The problem occurs when the total size of the function defined by eval
exceeds 8KB. This is reproduced even when two 4KB functions are defined. Confirmed with ksh 2020 on Ubuntu 20.04.
#!/bin/sh
str=''
for i in $(seq 1024); do
str="${str}12345678"
done
echo "${#str}"
eval "foo() { $str; }"
ls -al /proc/$(exec sh -c 'echo "$PPID"')/fd/ > /dev/tty
baz() { eval "bar() { This string will be output to STDOUT; }"; }
( baz >&3 ) 3>&1
output
8192
total 0
dr-x------ 2 user user 0 Jan 17 10:00 .
dr-xr-xr-x 9 user user 0 Jan 17 10:00 ..
lrwx------ 1 user user 64 Jan 17 10:00 0 -> /dev/pts/0
lrwx------ 1 user user 64 Jan 17 10:00 1 -> /dev/pts/0
lr-x------ 1 user user 64 Jan 17 10:00 10 -> /tmp
lr-x------ 1 user user 64 Jan 17 10:00 11 -> /tmp/script.sh
lrwx------ 1 user user 64 Jan 17 10:00 2 -> /dev/pts/0
lrwx------ 1 user user 64 Jan 17 10:00 3 -> '/tmp/sf.XXa7Xyug (deleted)' <=== Something's wrong.
{ This string will be output to STDOUT; }
Workaround
#!/bin/sh
if [ "${KSH_VERSION:-}" ]; then
( exec 3>/dev/null 4>&3 5>&3 6>&3 7>&3 8>&3 9>&3
eval "dummy() { $(printf '%8192s' ':'); }"
) 3>&- 4>&- 5>&- 6>&- 7>&- 8>&- 9>&-
fi
str=''
for i in $(seq 1024); do
str="${str}12345678"
done
echo "${#str}"
eval "foo() { $str; }"
ls -al /proc/$(exec sh -c 'echo "$PPID"')/fd/ > /dev/tty
baz() { eval "bar() { This string will be output to STDOUT; }"; }
( baz >&3 ) 3>&1
output
8192
total 0
dr-x------ 2 user user 0 Jan 17 14:18 .
dr-xr-xr-x 9 user user 0 Jan 17 14:18 ..
lrwx------ 1 user user 64 Jan 17 14:18 0 -> /dev/pts/0
lrwx------ 1 user user 64 Jan 17 14:18 1 -> /dev/pts/0
lr-x------ 1 user user 64 Jan 17 14:18 10 -> /tmp
lr-x------ 1 user user 64 Jan 17 14:18 11 -> /tmp/script.sh
lrwx------ 1 user user 64 Jan 17 14:18 2 -> /dev/pts/0
lrwx------ 1 user user 64 Jan 17 14:18 20 -> '/tmp/sf.XXaLaLlg (deleted)'