######################################################################## # # # This software is part of the ast package # # Copyright (c) 1982-2012 AT&T Intellectual Property # # and is licensed under the # # Eclipse Public License, Version 1.0 # # by AT&T Intellectual Property # # # # A copy of the License is available at # # http://www.eclipse.org/org/documents/epl-v10.html # # (with md5 checksum b35adb5213ca9657e911e9befb180842) # # # # Information and Software Systems Research # # AT&T Research # # Florham Park NJ # # # # David Korn # # # ######################################################################## function err_exit { print -u2 -n "\t" print -u2 -r ${Command}[$1]: "${@:2}" let Errors+=1 } alias err_exit='err_exit $LINENO' Command=${0##*/} integer Errors=0 [[ -d $tmp && -w $tmp && $tmp == "$PWD" ]] || { err\_exit "$LINENO" '$tmp not set; run this from shtests. Aborting.'; exit 1; } # Determine method for running tests. # The 'vmstate' builtin can be used if ksh was compiled with vmalloc. if builtin vmstate 2>/dev/null && n=$(vmstate --format='%(busy_size)u') && let "($n) == ($n) && n > 0" # non-zero number? then N=512 # number of iterations for each test unit=bytes tolerance=$((4*N)) # tolerate 4 bytes per iteration to account for vmalloc artefacts function getmem { vmstate --format='%(busy_size)u' } # Otherwise, make do with the nonstandard 'rss' (real resident size) keyword # of the 'ps' command (the standard 'vsz', virtual size, is not usable). elif n=$(ps -o rss= -p "$$" 2>/dev/null) && let "($n) == ($n) && n > 0" then N=16384 unit=KiB tolerance=$((8*N/1024)) # tolerate 8 bytes per iteration to account for malloc/ps artefacts function getmem { ps -o rss= -p "$$" } else err\_exit "$LINENO" 'WARNING: cannot find method to measure memory usage; skipping tests' exit 0 fi # test for variable reset leak # function test_reset { integer i N=$1 for ((i = 0; i < N; i++)) do u=$i done } # Initialise variables used below to avoid false leaks before=0 after=0 i=0 u=0 # Check results. # The function has 'err_exit' in the name so that shtests counts each call as at test. function err_exit_if_leak { if ((after > before + tolerance)) then err\_exit "$1" "$2 (leaked approx $((after - before)) $unit after $N iterations)" fi } alias err_exit_if_leak='err_exit_if_leak "$LINENO"' # one round to get to steady state -- sensitive to -x test_reset $N test_reset $N before=$(getmem) test_reset $N after=$(getmem) err_exit_if_leak "variable value reset memory leak" # buffer boundary tests for exp in 65535 65536 do got=$($SHELL -c 'x=$(printf "%.*c" '$exp' x); print ${#x}' 2>&1) [[ $got == $exp ]] || err_exit "large command substitution failed -- expected $exp, got $got" done data="(v=;sid=;di=;hi=;ti='1328244300';lv='o';id='172.3.161.178';var=(k='conn_num._total';u=;fr=;l='Number of Connections';n='22';t='number';))" read -C stat <<< "$data" for ((i=0; i < 8; i++)) # steady state first do print -r -- "$data" | while read -u$n -C stat; do :; done {n}<&0- done before=$(getmem) for ((i=0; i < N; i++)) do print -r -- "$data" done | while read -u$n -C stat do : done {n}<&0- after=$(getmem) err_exit_if_leak "memory leak with read -C when deleting compound variable" # extra 'read's to get to steady state for ((i=0; i < 10; i++)) do read -C stat <<< "$data" done before=$(getmem) for ((i=0; i < N; i++)) do read -C stat <<< "$data" done after=$(getmem) err_exit_if_leak "memory leak with read -C when using <<<" # ====== # Unsetting an associative array shouldn't cause a memory leak # See https://www.mail-archive.com/ast-users@lists.research.att.com/msg01016.html typeset -A stuff before=$(getmem) for (( i=0; i < N; i++ )) do unset stuff[xyz] typeset -A stuff[xyz] stuff[xyz][elem0]="data0" stuff[xyz][elem1]="data1" stuff[xyz][elem2]="data2" stuff[xyz][elem3]="data3" stuff[xyz][elem4]="data4" done unset stuff after=$(getmem) err_exit_if_leak 'unset of associative array causes memory leak' # ====== # Memory leak when resetting PATH and clearing hash table # ...steady memory state: command -v ls >/dev/null # add something to hash table PATH=/dev/null true # set/restore PATH & clear hash table # ...test for leak: before=$(getmem) for ((i=0; i < N; i++)) do PATH=/dev/null true # set/restore PATH & clear hash table command -v ls # do PATH search, add to hash table done >/dev/null after=$(getmem) err_exit_if_leak 'memory leak on PATH reset before PATH search' # ...test for another leak that only shows up when building with nmake: before=$(getmem) for ((i=0; i < N; i++)) do PATH=/dev/null true # set/restore PATH & clear hash table done >/dev/null after=$(getmem) err_exit_if_leak 'memory leak on PATH reset' # ====== # Defining a function in a virtual subshell # https://github.com/ksh93/ksh/issues/114 unset -f foo before=$(getmem) for ((i=0; i < N; i++)) do (function foo { :; }; foo) done after=$(getmem) err_exit_if_leak 'ksh function defined in virtual subshell' typeset -f foo >/dev/null && err_exit 'ksh function leaks out of subshell' unset -f foo before=$(getmem) for ((i=0; i < N; i++)) do (foo() { :; }; foo) done after=$(getmem) err_exit_if_leak 'POSIX function defined in virtual subshell' typeset -f foo >/dev/null && err_exit 'POSIX function leaks out of subshell' # ====== # Sourcing a dot script in a virtual subshell echo 'echo "$@"' > $tmp/dot.sh before=$(getmem) for ((i=0; i < N; i++)) do (. "$tmp/dot.sh" dot one two three >/dev/null) done after=$(getmem) err_exit_if_leak 'script dotted in virtual subshell' echo 'echo "$@"' > $tmp/dot.sh before=$(getmem) for ((i=0; i < N; i++)) do (source "$tmp/dot.sh" source four five six >/dev/null) done after=$(getmem) err_exit_if_leak 'script sourced in virtual subshell' # ====== # Multiple leaks when using arrays in functions (Red Hat #921455) # Fix based on: https://src.fedoraproject.org/rpms/ksh/blob/642af4d6/f/ksh-20120801-memlik.patch # TODO: both of these tests still leak (although much less after the patch) when run in a non-C locale. saveLANG=$LANG; LANG=C # comment out to test remaining leak (1/2) function _hash { typeset w=([abc]=1 [def]=31534 [xyz]=42) print -u2 $w 2>&- # accessing the var will leak } before=$(getmem) for ((i=0; i < N; i++)) do _hash done after=$(getmem) err_exit_if_leak 'associative array in function' function _array { typeset w=(1 31534 42) print -u2 $w 2>&- # unset w will prevent leak } before=$(getmem) for ((i=0; i < N; i++)) do _array done after=$(getmem) err_exit_if_leak 'indexed array in function' LANG=$saveLANG # comment out to test remaining leak (2/2) # ====== # Memory leak in typeset (Red Hat #1036470) # Fix based on: https://src.fedoraproject.org/rpms/ksh/blob/642af4d6/f/ksh-20120801-memlik3.patch # The fix was backported from ksh 93v- beta. function myFunction { typeset toPrint="something" echo "${toPrint}" } state=$(myFunction) before=$(getmem) for ((i=0; i < N; i++)) do state=$(myFunction) done after=$(getmem) err_exit_if_leak 'typeset in function called by command substitution' # ====== # Check that unsetting an alias frees both the node and its value before=$(getmem) for ((i=0; i < N; i++)) do alias "test$i=command$i" unalias "test$i" done after=$(getmem) err_exit_if_leak 'unalias' # ====== # Red Hat bug rhbz#982142: command substitution leaks # case1: Nested command substitutions # (reportedly already fixed in 93u+, but let's keep the test) before=$(getmem) for ((i=0; i < N; i++)) do a=`true 1 + \`true 1 + 1\`` # was: a=`expr 1 + \`expr 1 + 1\`` done after=$(getmem) err_exit_if_leak 'nested command substitutions' # case2: Command alias alias ls='true -ltr' # was: alias ls='ls -ltr' before=$(getmem) for ((i=0; i < N; i++)) do eval 'a=`ls`' done after=$(getmem) unalias ls err_exit_if_leak 'alias in command substitution' # case3: Function call via autoload cat >$tmp/func1 <<\EOF function func1 { echo "func1 call"; } EOF FPATH=$tmp autoload func1 a=`func1` # steady memory state before=$(getmem) for ((i=0; i < N; i++)) do a=`func1` done after=$(getmem) unset -f func1 unset -v FPATH err_exit_if_leak 'function call via autoload in command substitution' # ====== exit $((Errors<125?Errors:125))