1
0
Fork 0
mirror of git://git.code.sf.net/p/cdesktopenv/code synced 2025-02-24 06:54:13 +00:00
cde/src/cmd/ksh93/tests/variables.sh
Martijn Dekker e1c41bb2de Fix subshell leak for 3 special variables (re: 417883df, bd3e2a80)
Using a process of elimination I've identified ${.sh.level}
(SH_LEVELNOD) as the cause of the crash. This node apparently
cannot be copied or moved without destabilising the shell. It
contains the current depth of function calls and it cannot be
changed by assignment, so this is not actually a problem.
Meanwhile, this commit re-fixes it for the other three.

src/cmd/ksh93/sh/subshell.c:
- Simplify sh_assignok() by removing special-casing for L_ARGNOD,
  SH_SUBSCRNOD and SH_NAMENOD. 'add' now has 3 modes (0, 1, 2).
- The test for a ${ subshare; } was actually wrong. sp->subshare is
  a saved backup value. We must test shp->subshare. (re: a9de50bf)

src/cmd/ksh93/bltins/typeset.c:
- setall(): Update the mode 3 sh_assignok() call.

src/cmd/ksh93/tests/variables.sh:
- Regress-test subshell leaks for all special variables except
  ${.sh.level}.
2020-09-05 20:47:03 +02:00

1081 lines
28 KiB
Bash
Executable file

########################################################################
# #
# 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 <dgk@research.att.com> #
# #
########################################################################
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; }
[[ ${.sh.version} == "$KSH_VERSION" ]] || err_exit '.sh.version != KSH_VERSION'
unset ss
[[ ${@ss} ]] && err_exit '${@ss} should be empty string when ss is unset'
[[ ${!ss} == ss ]] || err_exit '${!ss} should be ss when ss is unset'
[[ ${#ss} == 0 ]] || err_exit '${#ss} should be 0 when ss is unset'
# RANDOM
if (( RANDOM==RANDOM || $RANDOM==$RANDOM ))
then err_exit RANDOM variable not working
fi
# SECONDS
let SECONDS=0.0
sleep .001
if (( SECONDS < .001 ))
then err_exit "either 'sleep' or \$SECONDS not working"
fi
# _
set abc def
if [[ $_ != def ]]
then err_exit _ variable not working
fi
# ERRNO
#set abc def
#rm -f foobar#
#ERRNO=
#2> /dev/null < foobar#
#if (( ERRNO == 0 ))
#then err_exit ERRNO variable not working
#fi
# PWD
if [[ ! $PWD -ef . ]]
then err_exit PWD variable failed, not equivalent to .
fi
# PPID
exp=$$
got=${ $SHELL -c 'print $PPID'; }
if [[ ${ $SHELL -c 'print $PPID'; } != $$ ]]
then err_exit "PPID variable failed -- expected '$exp', got '$got'"
fi
# OLDPWD
old=$PWD
cd /
if [[ $OLDPWD != $old ]]
then err_exit "OLDPWD variable failed -- expected '$old', got '$OLDPWD'"
fi
cd "$old" || err_exit cd failed
# REPLY
read <<-!
foobar
!
if [[ $REPLY != foobar ]]
then err_exit REPLY variable not working
fi
integer save=$LINENO
# LINENO
LINENO=10
#
# These lines intentionally left blank
#
if (( LINENO != 13))
then err_exit LINENO variable not working
fi
LINENO=save+10
IFS=:
x=a::b::c
if [[ $x != a::b::c ]]
then err_exit "word splitting on constants"
fi
set -- $x
if [[ $# != 5 ]]
then err_exit ":: doesn't separate null arguments "
fi
set x
if x$1=0 2> /dev/null
then err_exit "x\$1=value treated as an assignment"
fi
# check for attributes across subshells
typeset -i x=3
y=1/0
if ( x=y ) 2> /dev/null
then err_exit "attributes not passed to subshells"
fi
unset x
function x.set
{
nameref foo=${.sh.name}.save
foo=${.sh.value}
.sh.value=$0
}
x=bar
if [[ $x != x.set ]]
then err_exit 'x.set does not override assignment'
fi
x.get()
{
nameref foo=${.sh.name}.save
.sh.value=$foo
}
if [[ $x != bar ]]
then err_exit 'x.get does not work correctly'
fi
typeset +n foo
unset foo
foo=bar
(
unset foo
set +u
if [[ $foo != '' ]]
then err_exit '$foo not null after unset in subsehll'
fi
)
if [[ $foo != bar ]]
then err_exit 'unset foo in subshell produces side effect '
fi
unset foo
if [[ $( { : ${foo?hi there} ; } 2>&1) != *'hi there' ]]
then err_exit '${foo?hi there} with foo unset does not print hi there on 2'
fi
x=$0
set foobar
if [[ ${@:0} != "$x foobar" ]]
then err_exit '${@:0} not expanding correctly'
fi
set --
if [[ ${*:0:1} != "$0" ]]
then err_exit '${@:0} not expanding correctly'
fi
ACCESS=0
function COUNT.set
{
(( ACCESS++ ))
}
COUNT=0
(( COUNT++ ))
if (( COUNT != 1 || ACCESS!=2 ))
then err_exit " set discipline failure COUNT=$COUNT ACCESS=$ACCESS"
fi
save_LANG=$LANG
LANG=C > /dev/null 2>&1
if [[ $LANG != C ]]
then err_exit "C locale not working"
fi
LANG=$save_LANG
if [[ $LANG != "$save_LANG" ]]
then err_exit "$save_LANG locale not working"
fi
unset RANDOM
unset -n foo
foo=junk
function foo.get
{
.sh.value=stuff
unset -f foo.get
}
if [[ $foo != stuff ]]
then err_exit "foo.get discipline not working"
fi
if [[ $foo != junk ]]
then err_exit "foo.get discipline not working after unset"
fi
# special variables
set -- 1 2 3 4 5 6 7 8 9 10
sleep 1000 &
if [[ $(print -r -- ${#10}) != 2 ]]
then err_exit '${#10}, where ${10}=10 not working'
fi
for i in @ '*' ! '#' - '?' '$'
do false
eval foo='$'$i bar='$'{$i}
if [[ ${foo} != "${bar}" ]]
then err_exit "\$$i not equal to \${$i}"
fi
command eval bar='$'{$i%?} 2> /dev/null || err_exit "\${$i%?} gives syntax error"
if [[ $i != [@*] && ${foo%?} != "$bar" ]]
then err_exit "\${$i%?} not correct"
fi
command eval bar='$'{$i#?} 2> /dev/null || err_exit "\${$i#?} gives syntax error"
if [[ $i != [@*] && ${foo#?} != "$bar" ]]
then err_exit "\${$i#?} not correct"
fi
command eval foo='$'{$i} bar='$'{#$i} || err_exit "\${#$i} gives syntax error"
if [[ $i != @([@*]) && ${#foo} != "$bar" ]]
then err_exit "\${#$i} not correct"
fi
done
kill -s 0 $! || err_exit '$! does not point to latest asynchronous process'
kill $!
unset x
cd /tmp || exit
CDPATH=/
x=$(cd ${tmp#/})
if [[ $x != $tmp ]]
then err_exit 'CDPATH does not display new directory'
fi
CDPATH=/:
x=$(cd ${tmp%/*}; cd ${tmp##*/})
if [[ $x ]]
then err_exit 'CDPATH displays new directory when not used'
fi
x=$(cd ${tmp#/})
if [[ $x != $tmp ]]
then err_exit "CDPATH ${tmp#/} does not display new directory"
fi
cd "$tmp" || exit
TMOUT=100
(TMOUT=20)
if (( TMOUT !=100 ))
then err_exit 'setting TMOUT in subshell affects parent'
fi
unset y
function setdisc # var
{
eval function $1.get'
{
.sh.value=good
}
'
}
y=bad
setdisc y
if [[ $y != good ]]
then err_exit 'setdisc function not working'
fi
integer x=$LINENO
: $'\
'
if (( LINENO != x+3 ))
then err_exit '\<newline> gets linenumber count wrong'
fi
set --
set -- "${@-}"
if (( $# !=1 ))
then err_exit '"${@-}" not expanding to null string'
fi
for i in : % + / 3b '**' '***' '@@' '{' '[' '}' !! '*a' '$foo'
do (eval : \${"$i"} 2> /dev/null) && err_exit "\${$i} not an syntax error"
done
# ___ begin: IFS tests ___
unset IFS
( IFS=' ' ; read -r a b c <<-!
x y z
!
if [[ $b ]]
then err_exit 'IFS=" " not causing adjacent space to be null string'
fi
)
read -r a b c <<-!
x y z
!
if [[ $b != y ]]
then err_exit 'IFS not restored after subshell'
fi
# The next part generates 3428 IFS set/read tests.
unset IFS x
function split
{
i=$1 s=$2 r=$3
IFS=': '
set -- $i
IFS=' '
g="[$#]"
while :
do case $# in
0) break ;;
esac
g="$g($1)"
shift
done
case "$g" in
"$s") ;;
*) err_exit "IFS=': '; set -- '$i'; expected '$s' got '$g'" ;;
esac
print "$i" | IFS=": " read arg rem; g="($arg)($rem)"
case "$g" in
"$r") ;;
*) err_exit "IFS=': '; read '$i'; expected '$r' got '$g'" ;;
esac
}
for str in \
'-' \
'a' \
'- -' \
'- a' \
'a -' \
'a b' \
'- - -' \
'- - a' \
'- a -' \
'- a b' \
'a - -' \
'a - b' \
'a b -' \
'a b c'
do
IFS=' '
set x $str
shift
case $# in
0) continue ;;
esac
f1=$1
case $f1 in
'-') f1='' ;;
esac
shift
case $# in
0) for d0 in '' ' '
do
for d1 in '' ' ' ':' ' :' ': ' ' : '
do
case $f1$d1 in
'') split "$d0$f1$d1" "[0]" "()()" ;;
' ') ;;
*) split "$d0$f1$d1" "[1]($f1)" "($f1)()" ;;
esac
done
done
continue
;;
esac
f2=$1
case $f2 in
'-') f2='' ;;
esac
shift
case $# in
0) for d0 in '' ' '
do
for d1 in ' ' ':' ' :' ': ' ' : '
do
case ' ' in
$f1$d1|$d1$f2) continue ;;
esac
for d2 in '' ' ' ':' ' :' ': ' ' : '
do
case $f2$d2 in
'') split "$d0$f1$d1$f2$d2" "[1]($f1)" "($f1)()" ;;
' ') ;;
*) split "$d0$f1$d1$f2$d2" "[2]($f1)($f2)" "($f1)($f2)" ;;
esac
done
done
done
continue
;;
esac
f3=$1
case $f3 in
'-') f3='' ;;
esac
shift
case $# in
0) for d0 in '' ' '
do
for d1 in ':' ' :' ': ' ' : '
do
case ' ' in
$f1$d1|$d1$f2) continue ;;
esac
for d2 in ' ' ':' ' :' ': ' ' : '
do
case $f2$d2 in
' ') continue ;;
esac
case ' ' in
$f2$d2|$d2$f3) continue ;;
esac
for d3 in '' ' ' ':' ' :' ': ' ' : '
do
case $f3$d3 in
'') split "$d0$f1$d1$f2$d2$f3$d3" "[2]($f1)($f2)" "($f1)($f2)" ;;
' ') ;;
*) x=$f2$d2$f3$d3
x=${x#' '}
x=${x%' '}
split "$d0$f1$d1$f2$d2$f3$d3" "[3]($f1)($f2)($f3)" "($f1)($x)"
;;
esac
done
done
done
done
continue
;;
esac
done
# BUG_KUNSETIFS: Unsetting IFS fails to activate default default field splitting if two conditions are met:
IFS='' # condition 1: no split in main shell
: ${foo-} # at least one expansion is also needed to trigger this
( # condition 2: subshell (non-forked)
unset IFS
v="one two three"
set -- $v
let "$# == 3" # without bug, should be 3
) || err_exit 'IFS fails to be unset in subshell (BUG_KUNSETIFS)'
# Test known BUG_KUNSETIFS workaround (assign to IFS before unset)
IFS= v=
: ${v:=a$'\n'bc$'\t'def\ gh}
case $(unset IFS; set -- $v; print $#) in
4 | 1) # test if the workaround works whether we've got the bug or not
v=$(IFS=foobar; unset IFS; set -- $v; print $#)
[[ $v == 4 ]] || err_exit "BUG_KUNSETIFS workaround fails (expected 4, got $v)" ;;
*) err_exit 'BUG_KUNSETIFS detection failed'
esac
# Multi-byte characters should work with $IFS
if [[ ${LC_ALL:-${LC_CTYPE:-${LANG:-}}} =~ [Uu][Tt][Ff]-?8 ]] # The multi-byte tests are pointless without UTF-8
then
# Test the following characters:
# Lowercase accented e (two bytes)
# Roman sestertius sign (four bytes)
for delim in é 𐆘; do
IFS=$delim
set : :
expect=:$delim:
actual=$*
[[ $actual == "$expect" ]] || err_exit "IFS failed with multi-byte character $delim" \
"(expected $(printf %q "$expect"), got $(printf %q "$actual"))"
read -r first second third <<< "one${delim}two${delim}three"
[[ $first == one ]] || err_exit "IFS failed with multi-byte character $delim (expected one, got $first)"
[[ $second == two ]] || err_exit "IFS failed with multi-byte character $delim (expected two, got $second)"
[[ $third == three ]] || err_exit "IFS failed with multi-byte character $delim (expected three, got $three)"
# Ensure subshells don't get corrupted when IFS becomes a multi-byte character
IFS=$' \t\n'
expect=$(printf ":$delim:\\ntrap -- 'echo end' EXIT\\nend")
actual=$(set : :; IFS=$delim; echo "$*"; trap "echo end" EXIT; trap)
[[ $actual == "$expect" ]] || err_exit "IFS in subshell failed with multi-byte character $delim" \
"(expected $(printf %q "$expect"), got $(printf %q "$actual"))"
done
# Multibyte characters with the same initial byte shouldn't be parsed as the same
# character if they are different. The regression test below tests two characters
# with the same initial byte (0xC2).
IFS='£' # £ = C2 A3
v='abc§def ghi§jkl' # § = C2 A7 (same initial byte)
set -- $v
v="${#},${1-},${2-},${3-}"
[[ $v == '1,abc§def ghi§jkl,,' ]] || err_exit "IFS treats £ (C2 A3) and § (C2 A7) as the same character"
fi
# Ensure fallback to first byte if IFS doesn't start with a valid multibyte character
# (however, this test should pass regardless of the locale)
IFS=$'\x[A0]a'
set : :
expect=$':\x[A0]:'
actual=$*
[[ $actual == "$expect" ]] || err_exit "IFS failed with invalid multi-byte character" \
"(expected $(printf %q "$expect"), got $(printf %q "$actual"))"
# ^^^ end: IFS tests ^^^
# restore default split:
unset IFS
if [[ $( (print ${12345:?}) 2>&1) != *12345* ]]
then err_exit 'incorrect error message with ${12345?}'
fi
unset foobar
if [[ $( (print ${foobar:?}) 2>&1) != *foobar* ]]
then err_exit 'incorrect error message with ${foobar?}'
fi
unset bar
if [[ $( (print ${bar:?bam}) 2>&1) != *bar*bam* ]]
then err_exit 'incorrect error message with ${foobar?}'
fi
{ $SHELL -c '
function foo
{
typeset SECONDS=0
sleep .002
print $SECONDS
}
x=$(foo)
(( x >.001 && x < 1 ))
'
} 2> /dev/null || err_exit 'SECONDS not working in function'
cat > $tmp/script <<-\!
posixfun()
{
unset x
nameref x=$1
print -r -- "$x"
}
function fun
{
nameref x=$1
print -r -- "$x"
}
if [[ $1 ]]
then file=${.sh.file}
else print -r -- "${.sh.file}"
fi
!
chmod +x $tmp/script
. $tmp/script 1
[[ $file == $tmp/script ]] || err_exit ".sh.file not working for dot scripts"
[[ $($SHELL $tmp/script) == $tmp/script ]] || err_exit ".sh.file not working for scripts"
[[ $(posixfun .sh.file) == $tmp/script ]] || err_exit ".sh.file not working for posix functions"
[[ $(fun .sh.file) == $tmp/script ]] || err_exit ".sh.file not working for functions"
[[ $(posixfun .sh.fun) == posixfun ]] || err_exit ".sh.fun not working for posix functions"
[[ $(fun .sh.fun) == fun ]] || err_exit ".sh.fun not working for functions"
[[ $(posixfun .sh.subshell) == 1 ]] || err_exit ".sh.subshell not working for posix functions"
[[ $(fun .sh.subshell) == 1 ]] || err_exit ".sh.subshell not working for functions"
(
[[ $(posixfun .sh.subshell) == 2 ]] || err_exit ".sh.subshell not working for posix functions in subshells"
[[ $(fun .sh.subshell) == 2 ]] || err_exit ".sh.subshell not working for functions in subshells"
(( .sh.subshell == 1 )) || err_exit ".sh.subshell not working in a subshell"
)
TIMEFORMAT='this is a test'
[[ $(set +x; { { time :;} 2>&1;}) == "$TIMEFORMAT" ]] || err_exit 'TIMEFORMAT not working'
alias _test_alias=true
: ${.sh.version}
[[ $(alias _test_alias) == *.sh.* ]] && err_exit '.sh. prefixed to alias name'
: ${.sh.version}
[[ $(whence rm) == *.sh.* ]] && err_exit '.sh. prefixed to tracked alias name'
: ${.sh.version}
[[ $(cd /bin;env | grep PWD=) == *.sh.* ]] && err_exit '.sh. prefixed to PWD'
# unset discipline bug fix
dave=dave
function dave.unset
{
unset dave
}
unset dave
[[ $(typeset +f) == *dave.* ]] && err_exit 'unset discipline not removed'
x=$(
dave=dave
function dave.unset
{
print dave.unset
}
)
[[ $x == dave.unset ]] || err_exit 'unset discipline not called with subset completion'
print 'print ${VAR}' > $tmp/script
unset VAR
VAR=new $tmp/script > $tmp/out
got=$(<$tmp/out)
[[ $got == new ]] || err_exit "previously unset environment variable not passed to script, expected 'new', got '$got'"
[[ ! $VAR ]] || err_exit "previously unset environment variable set after script, expected '', got '$VAR'"
unset VAR
VAR=old
VAR=new $tmp/script > $tmp/out
got=$(<$tmp/out)
[[ $got == new ]] || err_exit "environment variable covering local variable not passed to script, expected 'new', got '$got'"
[[ $VAR == old ]] || err_exit "previously set local variable changed after script, expected 'old', got '$VAR'"
unset VAR
export VAR=old
VAR=new $tmp/script > $tmp/out
got=$(<$tmp/out)
[[ $got == new ]] || err_exit "environment variable covering environment variable not passed to script, expected 'new', got '$got'"
[[ $VAR == old ]] || err_exit "previously set environment variable changed after script, expected 'old', got '$VAR'"
(
unset dave
function dave.append
{
.sh.value+=$dave
dave=
}
dave=foo; dave+=bar
[[ $dave == barfoo ]] || exit 2
) 2> /dev/null
case $? in
0) ;;
1) err_exit 'append discipline not implemented';;
*) err_exit 'append discipline not working';;
esac
.sh.foobar=hello
{
function .sh.foobar.get
{
.sh.value=world
}
} 2> /dev/null || err_exit "cannot add get discipline to .sh.foobar"
[[ ${.sh.foobar} == world ]] || err_exit 'get discipline for .sh.foobar not working'
[[ -o xtrace ]] && opt_x=-x || opt_x=+x
x='a|b'
IFS='|'
set -- $x
[[ $2 == b ]] || err_exit '$2 should be b after set'
exec 3>&2 2> /dev/null
set -x
( IFS= ) 2> /dev/null
set "$opt_x"
exec 2>&3-
set -- $x
[[ $2 == b ]] || err_exit '$2 should be b after subshell'
: & pid=$!
( : & )
[[ $pid == $! ]] || err_exit '$! value not preserved across subshells'
pid=$!
{ : & } >&2
[[ $pid == $! ]] && err_exit '$! value not updated after bg job in braces+redir'
pid=$!
{ : |& } >&2
[[ $pid == $! ]] && err_exit '$! value not updated after co-process in braces+redir'
unset foo
typeset -A foo
function foo.set
{
case ${.sh.subscript} in
bar) if ((.sh.value > 1 ))
then .sh.value=5
foo[barrier_hit]=yes
fi
;;
barrier_hit)
if [[ ${.sh.value} == yes ]]
then foo[barrier_not_hit]=no
else foo[barrier_not_hit]=yes
fi
;;
esac
}
foo[barrier_hit]=no
foo[bar]=1
(( foo[bar] == 1 )) || err_exit 'foo[bar] should be 1'
[[ ${foo[barrier_hit]} == no ]] || err_exit 'foo[barrier_hit] should be no'
[[ ${foo[barrier_not_hit]} == yes ]] || err_exit 'foo[barrier_not_hit] should be yes'
foo[barrier_hit]=no
foo[bar]=2
(( foo[bar] == 5 )) || err_exit 'foo[bar] should be 5'
[[ ${foo[barrier_hit]} == yes ]] || err_exit 'foo[barrier_hit] should be yes'
[[ ${foo[barrier_not_hit]} == no ]] || err_exit 'foo[barrier_not_hit] should be no'
unset x
typeset -i x
function x.set
{
typeset sub=${.sh.subscript}
(( sub > 0 )) && (( x[sub-1]= x[sub-1] + .sh.value ))
}
x[0]=0 x[1]=1 x[2]=2 x[3]=3
[[ ${x[@]} == '12 8 5 3' ]] || err_exit 'set discipline for indexed array not working correctly'
float seconds
((SECONDS=3*4))
seconds=SECONDS
(( seconds < 12 || seconds > 12.1 )) && err_exit "SECONDS is $seconds and should be close to 12"
unset a
function a.set
{
print -r -- "${.sh.name}=${.sh.value}"
}
[[ $(a=1) == a=1 ]] || err_exit 'set discipline not working in subshell assignment'
[[ $(a=1 :) == a=1 ]] || err_exit 'set discipline not working in subshell command'
[[ ${.sh.subshell} == 0 ]] || err_exit '${.sh.subshell} should be 0'
(
[[ ${.sh.subshell} == 1 ]] || err_exit '${.sh.subshell} should be 1'
(
[[ ${.sh.subshell} == 2 ]] || err_exit '${.sh.subshell} should be 2'
exit $Errors
)
)
Errors=$? # ensure error count survives subshell
actual=$(
{
(
echo ${.sh.subshell} | cat # left element of pipe should increase ${.sh.subshell}
echo ${.sh.subshell}
ulimit -t unlimited # fork
echo ${.sh.subshell} # should be same after forking existing virtual subshell
)
echo ${.sh.subshell} # a background job should also increase ${.sh.subshell}
} & wait "$!"
echo ${.sh.subshell}
)
expect=$'4\n3\n3\n2\n1'
[[ $actual == "$expect" ]] || err_exit "\${.sh.subshell} failure (expected $(printf %q "$expect"), got $(printf %q "$actual"))"
set -- {1..32768}
(( $# == 32768 )) || err_exit "\$# failed -- expected 32768, got $#"
set --
(
unset r v x
x=foo
for v in EDITOR VISUAL OPTIND CDPATH FPATH PATH ENV LINENO RANDOM SECONDS _
do nameref r=$v
unset $v
if ( $SHELL -c "unset $v; : \$$v" ) 2>/dev/null
then [[ $r ]] && err_exit "unset $v failed -- expected '', got '$r'"
r=$x
[[ $r == $x ]] || err_exit "$v=$x failed -- expected '$x', got '$r'"
else err_exit "unset $v; : \$$v failed"
fi
done
x=x
for v in LC_ALL LC_CTYPE LC_MESSAGES LC_COLLATE LC_NUMERIC
do nameref r=$v
unset $v
[[ $r ]] && err_exit "unset $v failed -- expected '', got '$r'"
d=$($SHELL -c "$v=$x" 2>&1)
[[ $d ]] || err_exit "$v=$x failed -- expected locale diagnostic"
{ g=$( r=$x; print -- $r ); } 2>/dev/null
[[ $g == '' ]] || err_exit "$v=$x failed -- expected '', got '$g'"
{ g=$( r=C; r=$x; print -- $r ); } 2>/dev/null
[[ $g == 'C' ]] || err_exit "$v=C; $v=$x failed -- expected 'C', got '$g'"
done
exit $Errors
)
Errors=$? # ensure error count survives subshell
print print -n zzz > zzz
chmod +x zzz
exp='aaazzz'
got=$($SHELL -c 'unset SHLVL; print -n aaa; ./zzz' 2>&1) >/dev/null 2>&1
[[ $got == "$exp" ]] || err_exit "unset SHLVL causes script failure -- expected '$exp', got '$got'"
mkdir glean
for cmd in date ok
do exp="$cmd ok"
rm -f $cmd
print print $exp > glean/$cmd
chmod +x glean/$cmd
got=$(set +x; CDPATH=:.. $SHELL -c "command -p date > /dev/null; cd glean && ./$cmd" 2>&1)
[[ $got == "$exp" ]] || err_exit "cd with CDPATH after PATH change failed -- expected '$exp', got '$got'"
done
v=LC_CTYPE
unset $v
[[ -v $v ]] && err_exit "unset $v; [[ -v $v ]] failed"
eval $v=C
[[ -v $v ]] || err_exit "$v=C; [[ -v $v ]] failed"
cmd='set --nounset; unset foo; : ${!foo*}'
$SHELL -c "$cmd" 2>/dev/null || err_exit "'$cmd' exit status $?, expected 0"
SHLVL=1
level=$($SHELL -c $'$SHELL -c \'print -r "$SHLVL"\'')
[[ $level == 3 ]] || err_exit "SHLVL should be 3 not $level"
[[ $($SHELL -c '{ x=1; : ${x.};print ok;}' 2> /dev/null) == ok ]] || err_exit '${x.} where x is a simple variable causes shell to abort'
$SHELL -c 'unset .sh' 2> /dev/null
[[ $? == 1 ]] || err_exit 'unset .sh should return 1'
#'
# ======
# ${var+set} within a loop.
_test_isset() { eval "
$1=initial_value
function _$1_test {
typeset $1 # make local and initially unset
for i in 1 2 3 4 5; do
case \${$1+s} in
( s ) print -n 'S'; unset -v $1 ;;
( '' ) print -n 'U'; $1='' ;;
esac
done
}
_$1_test
[[ -n \${$1+s} && \${$1} == initial_value ]] || exit
for i in 1 2 3 4 5; do
case \${$1+s} in
( s ) print -n 's'; unset -v $1 ;;
( '' ) print -n 'u'; $1='' ;;
esac
done
"; }
expect='USUSUsusus'
actual=$(_test_isset var)
[[ "$actual" = "$expect" ]] || err_exit "\${var+s} expansion fails in loops (expected '$expect', got '$actual')"
actual=$(_test_isset IFS)
[[ "$actual" = "$expect" ]] || err_exit "\${IFS+s} expansion fails in loops (expected '$expect', got '$actual')"
# [[ -v var ]] within a loop.
_test_v() { eval "
$1=initial_value
function _$1_test {
typeset $1 # make local and initially unset
for i in 1 2 3 4 5; do
if [[ -v $1 ]]
then print -n 'S'; unset -v $1
else print -n 'U'; $1=''
fi
done
}
_$1_test
[[ -v $1 && \${$1} == initial_value ]] || exit
for i in 1 2 3 4 5; do
if [[ -v $1 ]]
then print -n 's'; unset -v $1
else print -n 'u'; $1=''
fi
done
"; }
expect='USUSUsusus'
actual=$(_test_v var)
[[ "$actual" = "$expect" ]] || err_exit "[[ -v var ]] command fails in loops (expected '$expect', got '$actual')"
actual=$(_test_v IFS)
[[ "$actual" = "$expect" ]] || err_exit "[[ -v IFS ]] command fails in loops (expected '$expect', got '$actual')"
# ======
# Verify that importing untrusted environment variables does not allow evaluating
# arbitrary expressions, but does recognize all integer literals recognized by ksh.
expect=8
actual=$(env SHLVL='7' "$SHELL" -c 'echo $SHLVL')
[[ $actual == $expect ]] || err_exit "decimal int literal not recognized (expected '$expect', got '$actual')"
expect=14
actual=$(env SHLVL='013' "$SHELL" -c 'echo $SHLVL')
[[ $actual == $expect ]] || err_exit "leading zeros int literal not recognized (expected '$expect', got '$actual')"
expect=4
actual=$(env SHLVL='2#11' "$SHELL" -c 'echo $SHLVL')
[[ $actual == $expect ]] || err_exit "base#value int literal not recognized (expected '$expect', got '$actual')"
expect=12
actual=$(env SHLVL='16#B' "$SHELL" -c 'echo $SHLVL')
[[ $actual == $expect ]] || err_exit "base#value int literal not recognized (expected '$expect', got '$actual')"
expect=1
actual=$(env SHLVL="2#11+x[\$(env echo Exploited vuln CVE-2019-14868 >&2)0]" "$SHELL" -c 'echo $SHLVL' 2>&1)
[[ $actual == $expect ]] || err_exit "expression allowed on env var import (expected '$expect', got '$actual')"
# ======
# Check unset and cleanup/restore behavior of special variables.
# Keep the list in sync (minus ".sh") with shtab_variables[] in src/cmd/ksh93/data/variables.c
# Note: as long as changing $PATH forks a virtual subshell, "PATH" should also be excluded below.
set -- \
"PS1" \
"PS2" \
"IFS" \
"PWD" \
"HOME" \
"MAIL" \
"REPLY" \
"SHELL" \
"EDITOR" \
"MAILCHECK" \
"RANDOM" \
"ENV" \
"HISTFILE" \
"HISTSIZE" \
"HISTEDIT" \
"HISTCMD" \
"FCEDIT" \
"CDPATH" \
"MAILPATH" \
"PS3" \
"OLDPWD" \
"VISUAL" \
"COLUMNS" \
"LINES" \
"PPID" \
"_" \
"TMOUT" \
"SECONDS" \
"LINENO" \
"OPTARG" \
"OPTIND" \
"PS4" \
"FPATH" \
"LANG" \
"LC_ALL" \
"LC_COLLATE" \
"LC_CTYPE" \
"LC_MESSAGES" \
"LC_NUMERIC" \
"FIGNORE" \
"KSH_VERSION" \
"JOBMAX" \
".sh.edchar" \
".sh.edcol" \
".sh.edtext" \
".sh.edmode" \
".sh.name" \
".sh.subscript" \
".sh.value" \
".sh.version" \
".sh.dollar" \
".sh.match" \
".sh.command" \
".sh.file" \
".sh.fun" \
".sh.lineno" \
".sh.subshell" \
".sh.level" \
".sh.stats" \
".sh.math" \
".sh.pool" \
".sh.pid" \
"SHLVL" \
"CSWIDTH"
# ... unset
$SHELL -c '
errors=0
unset -v "$@" || let errors++
for var
do if [[ $var != "_" ]] && # only makes sense that $_ is immediately set again
{ [[ -v $var ]] || eval "[[ -n \${$var+s} ]]"; }
then echo " $0: special variable $var still set" >&2
let errors++
elif eval "[[ -n \${$var} ]]"
then echo " $0: special variable $var has value, though unset" >&2
let errors++
fi
done
exit $((errors + 1)) # a possible erroneous asynchronous fork would cause exit status 0
' unset_test "$@"
e=$?
((e == 1)) || err_exit "Failure in unsetting one or more special variables (exit status $e)"
# ... unset in virtual subshell inside of nested function
$SHELL -c '
errors=0
fun1()
{
fun2()
{
(
unset -v "$@" || let errors++
for var
do if [[ $var != "_" ]] && # only makes sense that $_ is immediately set again
{ [[ -v $var ]] || eval "[[ -n \${$var+s} ]]"; }
then echo " $0: special variable $var still set" >&2
let errors++
elif eval "[[ -n \${$var} ]]"
then echo " $0: special variable $var has value, though unset" >&2
let errors++
fi
done
exit $errors
) || errors=$?
}
fun2 "$@"
}
fun1 "$@"
exit $((errors + 1)) # a possible erroneous asynchronous fork would cause exit status 0
' unset_subsh_fun_test "$@"
e=$?
((e == 1)) || err_exit "Unset of special variable(s) in a virtual subshell within a nested function fails (exit status $e)"
# ... readonly in subshell
$SHELL -c '
errors=0
(
readonly "$@"
for var
do if (eval "$var=") 2>/dev/null
then echo " $0: special variable $var not made readonly in subshell" >&2
let errors++
fi
done
exit $errors
) || errors=$?
for var
do if ! (eval "$var=")
then echo " $0: special variable $var still readonly outside subshell" >&2
let errors++
fi
done
exit $((errors + 1)) # a possible erroneous asynchronous fork would cause exit status 0
' readonly_test "$@"
e=$?
((e == 1)) || err_exit "Failure in making one or more special variables readonly in a subshell (exit status $e)"
# ... subshell leak test
$SHELL -c '
errors=0
for var
do if [[ $var == .sh.level ]]
then continue # known to fail
fi
if eval "($var=bug); [[ \${$var} == bug ]]" 2>/dev/null
then echo " $0: special variable $var leaks out of subshell" >&2
let errors++
fi
done
exit $((errors + 1))
' subshell_leak_test "$@"
e=$?
((e == 1)) || err_exit "One or more special variables leak out of a subshell (exit status $e)"
# ======
# ${.sh.pid} should be the forked subshell's PID
(
ulimit -t unlimited
[[ ${.sh.pid} == $$ ]]
) && err_exit "\${.sh.pid} is the same as \$$ (both are $$)"
# ${.sh.pid} should be the PID of the running job
echo ${.sh.pid} > "$tmp/jobpid" &
wait
[[ $(cat "$tmp/jobpid") == ${.sh.pid} ]] && err_exit "\${.sh.pid} is not set to a job's PID (expected $!, got $(cat "$tmp/jobpid"))"
# ${.sh.pid} should be the same as $$ in the parent shell
[[ $$ == ${.sh.pid} ]] || err_exit "\${.sh.pid} and \$$ differ in the parent shell (expected $$, got ${.sh.pid})"
# ======
# Parentheses after the '-', '+', and '=' expansion operators were causing syntax errors.
# Check both the unset variable case and the set variable case for each set of symbols.
unset -v foo
for op in - :- = :=
do for word in '(word)' 'w(or)d' '(wor)d' 'w(ord)' 'w(ord' 'wor)d'
do exp=$(set +x; eval "echo \${foo${op}${word}}" 2>&1)
if [[ $exp != "$word" ]]
then err_exit "\${foo${op}${word}} when foo is not set: expected \"$word\", got \"$exp\""
fi
done
done
foo=some_value
for op in - :- = :=
do for word in '(word)' 'w(or)d' '(wor)d' 'w(ord)' 'w(ord' 'wor)d'
do exp=$(set +x; eval "echo \${foo${op}${word}}" 2>&1)
if [[ $exp != "$foo" ]]
then err_exit "\${foo${op}${word}} when foo is set: expected \"$foo\", got \"$exp\""
fi
done
done
unset -v foo
for op in + :+
do for word in '(word)' 'w(or)d' '(wor)d' 'w(ord)' 'w(ord' 'wor)d'
do exp=$(set +x; eval "echo \${foo${op}${word}}" 2>&1)
if [[ $exp != "" ]]
then err_exit "\${foo${op}${word}} when foo is not set: expected null, got \"$exp\""
fi
done
done
# ======
exit $((Errors<125?Errors:125))