Last active
March 30, 2021 13:39
-
-
Save ormaaj/5682807 to your computer and use it in GitHub Desktop.
Broken namerefs.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# Only ksh93 has "real" C++-like references (called namerefs). They can be used | |
# as reference parameters for passing data structures like arrays in and out of | |
# functions. Bash 4.3+ and mksh also have a nameref feature, but unlike ksh93, | |
# which has three different kinds of nameref, bash supports just two of these | |
# and mksh only one (the for-loop type won't be illustrated here). It is the | |
# "dynamic" kind of nameref that is supported by all three, which is mostly | |
# just sugar for Bash's old "${!var}" behavior (ksh93 also uses this kind when | |
# a nameref doesn't refer to a positional parameter). | |
# When attempting to pass data between functions, dynamic namerefs are | |
# technically not much better than eval-based solutions because they don't | |
# guarantee that a reference parameter actually points to the same object | |
# instance when dereferenced as it did when defined. Reference parameters in | |
# Bash and mksh dynamically point to whatever variable of a particular name is | |
# visible from the current context at the time of dereference. | |
# Keep this in mind when using shell libraries. This type of nameref is not | |
# robust. It's up to the caller of a function that uses indirection to ensure | |
# there are no local name collisions all the way up the call chain. There is no | |
# proper "information hiding" or "encapsulation" in these langauges. | |
# This testcase demonstrates the problem by attempting to pass two different | |
# instances of array variables named `x' to a function at the same time, while | |
# also recycling the names `x' and `y' as the names of local reference | |
# variables. | |
${BASH_VERSION+shopt -s extglob lastpipe} | |
function main { | |
typeset sh | |
for sh; do | |
printf '%s: %s\n' "$sh" "$("$sh" -s </dev/fd/3 2>&1)" | |
done | |
} 4<&0 <<\EOF 3<&0 <&4- | |
function f { | |
case $1 in | |
0) | |
typeset -a x | |
x=(I\'m an x) | |
f 1 x | |
;; | |
1) | |
typeset -n y=$2 | |
typeset -a x | |
x=(I\'m a different x) | |
f 2 x y # The big problem occurs here! | |
;; | |
2) | |
typeset -n x=$2 y=$3 | |
printf '%s=(%s) ' "${!x}" "${x[*]}" "${!y}" "${y[*]}" | |
echo | |
esac | |
} | |
f 0 | |
EOF | |
main {{,m}k,ba}sh | |
# main {,m}ksh /home/ormaaj/doc/programs/bash+ | |
# Output: | |
# ksh: x=(I'm a different x) x=(I'm an x) | |
# mksh: x=(I'm a different x) x=(I'm a different x) | |
# bash: line 15: typeset: x: nameref variable self references not allowed | |
# bash: line 15: typeset: y: nameref variable self references not allowed | |
# =(I'm a different x) x=(I'm a different x) | |
# Ksh93 handles all these collisions gracefully. Bash and mksh do not. Note you | |
# should never have this problem within a single function like above. The | |
# problem primarily applies to consumers of library functions and to large | |
# programs with many functions. | |
# vim: set fenc=utf-8 ff=unix ft=sh : |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment