opencode/packages/core/test/shell-scan/oracle.test.ts

751 lines
32 KiB
TypeScript

import { afterAll, describe, expect, test } from "bun:test"
import { Effect, Exit } from "effect"
import fs from "fs"
import os from "os"
import path from "path"
import { ShellParse } from "../../src/shell/parse.js"
import { ShellScan } from "../../src/shell/scan.js"
const shellCandidates = ["/bin/bash", "/opt/homebrew/bin/bash", "/usr/local/bin/bash", "bash", "zsh", "dash"]
const shells = [
...new Set(
shellCandidates
.map((item) => (item.startsWith("/") ? (fs.existsSync(item) ? item : undefined) : Bun.which(item)))
.filter((item): item is string => Boolean(item)),
),
]
// Fixtures target specific shells and versions (bash 3.2 and 5.3, zsh, dash). Set SHELL_ORACLE_STRICT=1 on a
// machine with all of them to also verify that every fixture still executes somewhere.
const strict = process.env.SHELL_ORACLE_STRICT === "1"
const root = fs.mkdtempSync(path.join(os.tmpdir(), "opencode-shell-oracle-"))
const bin = path.join(root, "bin")
const target = path.join(root, "target")
const log = path.join(root, "log")
fs.mkdirSync(bin)
fs.mkdirSync(target)
fs.writeFileSync(
path.join(bin, "scan_probe"),
'#!/bin/sh\nprintf \'%s\\n\' "scan_probe${1:+ $*}" >> "$SCAN_PROBE_LOG"\n',
{
mode: 0o755,
},
)
// Dash runs `scan_probe[x y]=1` as the command `scan_probe[x`.
fs.copyFileSync(path.join(bin, "scan_probe"), path.join(bin, "scan_probe[x"))
// Zsh treats a trailing parenthesized group after an existing file name as glob qualifiers.
fs.writeFileSync(path.join(root, "a="), "")
afterAll(() => fs.rmSync(root, { recursive: true, force: true }))
function shellFlags(executable: string) {
if (executable.endsWith("/bash")) return ["--noprofile", "--norc"]
if (executable.endsWith("/zsh")) return ["-f"]
return []
}
// Runs source in a real shell and returns the lines it logged.
function observe(executable: string, source: string) {
fs.writeFileSync(log, "")
Bun.spawnSync([executable, ...shellFlags(executable), "-c", source], {
cwd: root,
env: { PATH: `${bin}:/usr/bin:/bin`, HOME: root, LC_ALL: "C", SCAN_PROBE_LOG: log },
timeout: 2_000,
})
return fs
.readFileSync(log, "utf8")
.split("\n")
.map((line) => line.trim())
.filter(Boolean)
}
// Each dialect must report what its shells run; posix, the default, covers every shell.
const dialects = {
bash: (executable: string) => path.basename(executable) === "bash",
zsh: (executable: string) => path.basename(executable) === "zsh",
posix: () => true,
} satisfies Record<ShellScan.Dialect, (executable: string) => boolean>
function expectProbesReported(source: string) {
const runs = shells.map((executable) => [executable, observe(executable, source)] as const)
if (strict)
expect(
runs.some(([, invocations]) => invocations.length > 0),
`Fixture never executed scan_probe in any real shell: ${source}`,
).toBe(true)
expect(ShellScan.scan(source)).toEqual(ShellScan.scan(source, "posix"))
for (const [dialect, runsIn] of Object.entries(dialects)) {
const result = ShellScan.scan(source, dialect as ShellScan.Dialect)
if (result.kind === "opaque") continue
const reported = result.commands.filter((command) => !command.declaration).map((command) => command.words.join(" "))
for (const [executable, invocations] of runs.filter(([executable]) => runsIn(executable)))
for (const invocation of invocations)
expect(reported, `${executable} executed ${invocation} with ${dialect} in: ${source}`).toContain(invocation)
}
}
// Each fixture runs scan_probe in at least one real shell, which the scanner must report or reject.
const fixtures = [
"a=(1)scan_probe",
"a=(\n1)scan_probe; ",
"a=(1)'scan_probe'",
"alias p=scan_probe\np",
"command alias p=scan_probe\np",
"x='*(e:scan_probe:)'; echo $^~x",
"x='$(scan_probe)'; echo \"${\\\n(e)x}\"",
"x='$(scan_probe)'; echo ${(j:):e)x}",
"x='*(e:scan_probe:)'; echo ${(f)~x}",
"x='*(e:scan_probe:)'; echo ${=~x}",
"x='*(e:scan_probe:)'; echo $=~x",
"x='$(scan_probe)'; echo ${(%e)x}",
"x='$(scan_probe)'; echo ${(s.:.e)x}",
"x='$(scan_probe)'; echo ${(s(:)e)x}",
"x='$(scan_probe)'; echo ${(ej: :)x}",
"x='*(e:scan_probe:)'; echo ${(s: :)~x}",
"x='*(e:scan_probe:)'; echo ${^~x}",
"printf -v x 'a[$(scan_probe)]'; echo $((x))",
"a=(1); getopts a: x -a 'a[$(scan_probe)]'; echo $((OPTARG))",
"command -- declare 'a[$(scan_probe)]=1'",
"a=(1); unset 'a[b[$\\\n(scan_probe)0]]'",
'declare \'a["\\"]"$(scan_probe)0]=1\'',
"declare -a 'a=(+ [$(scan_probe)]=1)'",
"a=(1); echo ${a[b[\\$(scan_probe)]]}",
'a["b[\\$(scan_probe)]"]=1',
'b=1; a["b[\\$(scan_probe)1]"]=1',
"a=(1); echo $[ ${x:-a[\\$(scan_probe)1]} ]",
"a=(1); s=abc; echo ${s:'a[$(scan_probe)0]'}",
"a=(1); s=abc; echo ${s:${x:-'a[$(scan_probe)1]'}}",
"read x <<< 'a[$(scan_probe f)]'; echo $((x))",
"mapfile -t a <<< 'b[$(scan_probe g)]'; echo $((a[0]))",
"x=1; echo $(( x[\\$(scan_probe i)] ))",
"a=(1); echo ${a[@]:'a[$(scan_probe k)]'}",
"builtin -- declare 'a[$(scan_probe)]=1'",
"command -p declare 'a[$(scan_probe)]=1'",
'echo "${unset:+${x[\'"\']}}"]}} \'$(scan_probe)\' " # "',
"echo \"${unset:+${x['}}\"']}}'; scan_probe # \"",
"echo \"${x:-$'\\''}\"; scan_probe # '\"",
"cat <<E\n${x:-'}'}' $(scan_probe)\nE",
'echo "${x:-a\'b}c\'d}"$(scan_probe)"\'"',
"echo \"${PATH//:/$'\\n'}\"; scan_probe ok",
"echo $(( : # ))'\n); scan_probe ) # '",
"echo $(( $(echo 1) # ))'\n); scan_probe ) # '",
"(( : # ))'\n); scan_probe ) # '",
"echo $(( 16#ff + 2#1 + $# + ${#x} )); scan_probe ok",
"(( scan_probe ))",
"(( (scan_probe) & (scan_probe) ))",
"(( (scan_probe)\n(scan_probe) ))",
"cat <<'}'; {\n:\n}\nscan_probe; cat <<'}'; }\n}",
"cat <<'x)'; case x in\nx)\nx) scan_probe; cat <<'x)'\nx)\n;; esac",
"cat <<'if'; f()\nif\nif scan_probe; cat <<'if'\nif\ntrue; then :; fi; f",
"cat <<E; ( true\nE\nscan_probe g\n)",
"cat <<E; { true\nE\nscan_probe g\n}",
"cat <<E; f() { true\nE\nscan_probe f\n}; f",
"cat <<E; if true\nE\nscan_probe i\nthen :; fi",
"cat <<E; echo $(true\nscan_probe s\n)\nE\nscan_probe after",
"cat <<E; echo `true\nscan_probe b\n`\nE\nscan_probe after",
"cat <<E; cat <(true\nscan_probe s\n)\nE\nscan_probe after",
"cat <<E; x=$(cat <<F\nF\n)\nE\nscan_probe out",
"{ cat <<E; }\nscan_probe x\nE\nscan_probe y",
"[[ a]]b# ]] && scan_probe",
'[[ "a]]"# ]] && scan_probe',
"[[ 'a]]'# ]] && scan_probe",
"[[ a]]b = a]]b ]] && scan_probe",
'{ export X="a"{}# ; scan_probe; }',
"{ export X=a}# ; scan_probe; }",
"{ case esac in (esac) scan_probe;; esac\n}",
"unset a[b\nscan_probe\necho ]+=1",
"export a[b; scan_probe; echo ]=1",
"a[b; scan_probe; echo ]=1",
"a[0 #]\n]=1; scan_probe",
"for i in 1; { scan_probe; }; while false; do export X=1; done",
"for ((i=0; i<1; i++)); { scan_probe; }; while false; do export X=1; done",
"for i in 1; scan_probe",
"((( echo '\"' ); scan_probe; ( echo '\"' )))",
"set=1; export X=${set:-${x['0\"0']}}; scan_probe; : '\"]}}' # '",
"export X=$$$$'\\'; scan_probe # '",
'export X="`export Y=\\"\'\\" ; scan_probe; export Z=\\"\'\\"`"',
'export X="`echo \\"(\\"; scan_probe; echo \\")\\"`"',
": <<-export\n\tex\\\n\tport\n$(scan_probe)\nexport",
'export X="${\\\n scan_probe; }"',
'export X="${\n scan_probe; }"',
'export X="${|\\\n REPLY=$(scan_probe); }"',
"!(scan_probe; true)",
"if !(scan_probe); then :; fi",
"f+() { scan_probe; }; f+",
': <<$"export"\n$export\nscan_probe\nexport',
': <<$"export"\nexport\nscan_probe\n$export',
"export X=$[1; scan_probe; : ]",
"X=$[1 scan_probe ]",
'export X=$["]"]; scan_probe # ]',
"export X=${#}# ; scan_probe",
"export X=$#a# ; scan_probe",
"export X=~+# ; scan_probe",
"{ export X=1; }# ; scan_probe; }",
"if true; then export X=1; fi# ; scan_probe; fi",
"for i in 1; do export X=1; done# ; scan_probe; done",
"case esac# in x) export X=1;; esac#) scan_probe;; esac",
": <<A <<B\nA\n$(scan_probe)\nB",
'export X="$(cat <<EOF\n)\nEOF\nscan_probe)"',
"cat <<EOF | scan_probe\nhello\nEOF",
"cat <<EOF && scan_probe\nhello\nEOF",
"cat <<'E'\"O\"F\n# $(scan_probe)\nEOF\nscan_probe",
"cat <<E\\\nOF\n$(scan_probe)\nEOF",
"cat <<-EOF\n\t EOF\n$(scan_probe)\n\tEOF",
"cat <<export\nex\\\nport\n# $(scan_probe)\nexport",
"cat <<< 'a'\"$(scan_probe)\"",
'export X=${x:-"}"}; scan_probe',
"export X=${x:-'}'}; scan_probe",
'export X="${x:-"}"}"; scan_probe',
'export X="${x:-"$(scan_probe)"}"',
"export X=${x//\\}/}; scan_probe; : }",
'export X="${x//\\}/}"; scan_probe; : "}"',
"export X=${x#${y}}; scan_probe",
'export X="${x#${y:-"}"}}"; scan_probe',
"export X=${!prefix*}; scan_probe",
"export X=${x@Q}; scan_probe",
"export X=\"${unset:+${x['\"']}}\"; scan_probe; : '\"}]}'",
'export X="${unset:+${x[\'}}\"; scan_probe; : \"\']}}"',
'echo $(case x in x) echo ")" ;; esac; scan_probe)',
"echo \"$(case x in (x) echo ')' ;; esac; scan_probe)\"",
'echo "`echo \\"$(scan_probe)\\"`"',
"echo `echo \\`scan_probe\\``",
"echo $( (echo a); scan_probe )",
"echo $((echo a); scan_probe)",
"if true; then scan_probe; elif true; then :; else :; fi",
"for i in in do done esac; do scan_probe; done",
"for do in 1; do scan_probe; done",
"for in in 1; do scan_probe; done",
"for x\nin 1; do scan_probe; done",
"set -- 1; for x\ndo scan_probe; done",
"set -- 1; for x do scan_probe; done",
"case in in in) scan_probe;; esac",
"case esac in a|esac) scan_probe;; esac",
"case y in x) ;; y) scan_probe;; esac",
"case x in x) scan_probe ;& y) : ;;& z) : ;; esac",
"case x in x) scan_probe ;| y) : ;; esac",
"case [ in [) scan_probe & ( scan_probe q ]) ;; esac",
"case x in (x|[) scan_probe & ( scan_probe q ]) ;; esac",
"while false; do :; done & scan_probe",
"{ scan_probe & }",
"( scan_probe & )",
"[[ b =~ b ]] && scan_probe",
"[[ ( a == a ) && ( b == b ) ]] && scan_probe",
'[[ "$(scan_probe)" == "]]" ]]',
"(( 1 + $(scan_probe) ))",
"(( a = 1 )) && scan_probe",
"(( (1) + (2) )); scan_probe",
"(( (echo a); scan_probe ))",
">/dev/null scan_probe",
"2>&1 scan_probe",
"A=1 >/dev/null B=2 scan_probe",
'export A=1 B="$(scan_probe)"',
'declare -a arr=(1 "$(scan_probe)")',
"export X=$'a'\\\n; scan_probe",
"case x in \\\nx) \\\nscan_probe;; \\\nesac",
"if true; then \\\nscan_probe; fi",
'() { :; } "$(scan_probe)"',
"function f() ( scan_probe ); f",
"{ export X=1 } && scan_probe",
"{ export X=1 } ; scan_probe ; }",
// Dialects disagree about reserved words after redirects and operators split by line continuations.
"if true; then >/dev/null fi; scan_probe; fi",
"if true; then >/dev/null fi; scan_probe",
"case x in x) >/dev/null esac; scan_probe; esac",
"true &>/dev/null scan_probe",
"true &>>/dev/null scan_probe",
"true &\\\n>/dev/null scan_probe",
"cat <<\\\n-EOF\nEOF\nscan_probe h1\n-EOF",
"cat <<\\\n-EOF\n-EOF\nscan_probe z\nEOF",
"cat <\\\n(scan_probe i)",
"[[ -n <\\\n(scan_probe c1) ]]",
"a=(<\\\n(scan_probe a1))",
"cat <<E\n$\\\n(scan_probe h)\nE",
"echo ${x:-$\\\n(scan_probe p)}",
"(( $\\\n(scan_probe a) ))",
"[[ $\\\n(scan_probe c) ]]",
"a=($\\\n(scan_probe arr))",
// Dash splits assignment subscripts at blanks.
"scan_probe[x y]=1",
"a[x '$(scan_probe)']=1",
"a[1 + $(scan_probe)]=1",
// Expansions inside parameter words, arithmetic, and subscripts.
"echo \"${x:-$'$(scan_probe q1)'}\"",
"echo ${x:-<(scan_probe p1)}",
"echo ${x:->(scan_probe g)}",
"x=${y:-<(scan_probe p4)}",
"[[ x == ${y:-<(scan_probe p5)} ]]",
"echo $(( $'$(scan_probe a)' ))",
"(( x = $'$(scan_probe b)' ))",
"echo $(( ${x:-'$(scan_probe a)'} ))",
"(( ${x:-'$(scan_probe b)'} ))",
"a[${x:-'$(scan_probe c)'}]=1",
"echo $[ ${x:-'$(scan_probe d)'} ]",
"echo $[ $'$(scan_probe h)' ]",
"echo ${a[${x:-'$(scan_probe e)'}]}",
"echo \"${a[${x:-'$(scan_probe f)'}]}\"",
"cat <<E\n${x:-'$(scan_probe h)'}\nE",
// Zsh glob qualifiers and extglob groups run code in globbed words.
"echo @(<(scan_probe e1))",
"printf '%s' @(one|$(scan_probe))",
"echo *(e:'scan_probe q1':)",
"echo *(+scan_probe)",
"a=(*(e:'scan_probe g':))",
"declare -a a=(*(e:'scan_probe g':))",
"export a=(*(e:'scan_probe h':))",
"for f in *(e:'scan_probe h':); do :; done",
"echo ${x:-target(e:'scan_probe p2':)}",
"echo ${x:-*(e:'scan_probe j':)}",
"echo a=(e:'scan_probe p3':)",
"echo >*(e:'scan_probe f':)",
"cat <*(e:'scan_probe g':)",
"echo $x*(e:'scan_probe h':)",
"echo \"\"*(e:'scan_probe i':)",
"echo {a,*(e:'scan_probe m':)}",
// Builtins and arithmetic evaluate subscripts in decoded literal text.
"declare -i x='a[$(scan_probe)]'",
"declare 'a[$(scan_probe)]=1'",
"declare -a 'a=([$(scan_probe)]=1)'",
"a=(1); unset 'a[$(scan_probe)]'",
'a=(1); unset "a[\\$(scan_probe)]"',
"[[ 'a[$(scan_probe)]' -eq 1 ]]",
"[[ -v 'a[$(scan_probe)]' ]]",
"read 'a[$(scan_probe)]' </dev/null",
"printf -v 'a[$(scan_probe)]' x",
"x='a[$(scan_probe)]'; echo $((x))",
"s=abc; x='a[$(scan_probe)0]'; printf '%s' \"${s:x}\"",
"ref='x[$(scan_probe)0]'; printf '%s' \"${!ref}\"",
"declare ${x:-'a[$(scan_probe)]=1'}",
'declare "${x:-a[\\$(scan_probe)]=1}"',
"read ${x:-'a[$(scan_probe)]'} </dev/null",
"declare \"$(echo 'a[$(scan_probe)]=1')\"",
"declare \"${ echo a; }\"'[$(scan_probe)]=1'",
"n=a; declare \"$n\"'[$(scan_probe)]=1'",
"declare \"$(echo a)\"'[$(scan_probe)]=1'",
"builtin declare 'a[$(scan_probe)]=1'",
"printf -v'a[$(scan_probe)]' x",
"set -- 'a[$(scan_probe)]'; echo $(($1))",
"for x in 'a[$(scan_probe)]'; do echo $((x)); done",
"a=(1 'a[$(scan_probe)]'); echo $((a[1]))",
"x='a[$(scan_probe)]' eval 'echo $((x))'",
"a=(1); echo $(( a[\\$(scan_probe)] ))",
"a=(1); (( a[\\$(scan_probe)] ))",
// Explicit evaluation operators.
"x='$(scan_probe)'; echo ${x@P}",
"x='$(scan_probe)'; echo \"${x@P}\"",
"x='$(scan_probe)'; echo ${(e)x}",
"x='$(scan_probe)'; echo ${(ee)x}",
"echo ${(e):-'$(scan_probe)'}",
"x='*(e:scan_probe:)'; echo ${~x}",
"x='*(e:scan_probe:)'; echo $~x",
// Precommand modifiers, bundled/ordered binding options, repeat, compgen, and setopt.
"noglob typeset 'a[$(scan_probe)]=1'",
"nocorrect typeset 'a[$(scan_probe)]=1'",
"a=(1); noglob unset 'a[$(scan_probe)]'",
"noglob read 'a[$(scan_probe)]' <<< x",
'a=(1); noglob let "a[\\$(scan_probe)]"',
"print -r -v 'a[$(scan_probe)]' x",
"print -rv 'a[$(scan_probe)]' x",
"a=(1); print -r -v x 'a[$(scan_probe)]'; echo $((x))",
"a=(1); print -rv x 'a[$(scan_probe)]'; echo $((x))",
": & wait -np 'a[$(scan_probe)]'",
": & wait -n -p'a[$(scan_probe)]'",
": & wait -fp 'a[$(scan_probe)]' $!",
"time declare 'a[$(scan_probe)]=1'",
"coproc declare 'a[$(scan_probe)]=1'",
"a=(1); time unset 'a[$(scan_probe)]'",
"a=(1); time let 'a[$(scan_probe)]'",
"a=(1); time [[ -v 'a[$(scan_probe)]' ]]",
"a=(1); time [[ 'a[$(scan_probe)]' -eq 1 ]]",
"a=(1); coproc [[ -v 'a[$(scan_probe)]' ]]",
'export X="${ time ! { :; }; scan_probe; }"',
'export X="${ time\\\n { :; }; scan_probe; }"',
'export X="${ time -- { :; }; scan_probe; }"',
"repeat 1 scan_probe",
"a=(1); repeat 'a[$(scan_probe)]' :",
"setopt globsubst; x='*(e:scan_probe:)'; echo $x",
"set -o globsubst; x='*(e:scan_probe:)'; echo $x",
"compgen -C scan_probe",
"compgen -C 'scan_probe'",
// Combined literal/output subscripts, deferred bindings, continuations, Zsh flags, and ANSI-C escapes.
"declare \"a[$(echo '$(scan_probe)')]=1\"",
"declare \"$(echo 'a[')\"'$(scan_probe)]=1'",
"a=(1); unset \"a[$(echo '$(scan_probe)')]\"",
"a=(1); echo $(( a[$(echo '$(scan_probe)')] ))",
"x='$(scan_probe)'; declare \"a[$x]=1\"",
"x='$(scan_probe)'; a=(1); unset \"a[$x]\"",
"x='$(scan_probe)'; let \"a[$x]=1\"",
"x='$(scan_probe)'; read \"a[$x]\" <<< 1",
"x='$(scan_probe)'; printf -v \"a[$x]\" 1",
"a=(1); x='$(scan_probe)'; echo $((a[$x]))",
"a=(1); x='$(scan_probe)'; [[ -v \"a[$x]\" ]]",
"a=(1); x='$(scan_probe)'; [[ \"a[$x]\" -eq 1 ]]",
"a=(1); echo ${a\\\n['$(scan_probe)']}",
"a=(1); s=abc; echo ${s\\\n:'a[$(scan_probe)0]'}",
"a=(1); echo ${a[0]\\\n:'a[$(scan_probe)0]'}",
"x='$(scan_probe)'; echo ${x@\\\nP}",
"x='$(scan_probe)'; echo \"${x@\\\nP}\"",
"x='$(scan_probe)'; echo ${x@P\\\n}",
"x='*(e:scan_probe:)'; echo $\\\n~x",
"x='*(e:scan_probe:)'; echo $^\\\n~x",
"x='*(e:scan_probe:)'; echo ${^\\\n~x}",
"x='*(e:scan_probe:)'; echo ${(f)\\\n~x}",
"a=(1); echo ${(f)a['a[$(scan_probe)]']}",
"a=(1); echo ${=a['a[$(scan_probe)]']}",
"a=(1); echo ${^a['a[$(scan_probe)]']}",
"a=(1); s=abc; echo ${(f)s:'a[$(scan_probe)0]'}",
"a=(1); s=abc; echo ${=s:'a[$(scan_probe)0]'}",
"a=(1); s=abc; echo ${^s:'a[$(scan_probe)0]'}",
"a=(1); [[ x != *(a)]] && 0 -eq 'a[$(scan_probe)]' ]]",
"a=(1); [[ x != *(a)]] && -v 'a[$(scan_probe)]' ]]",
"a=(1); [[ x != (a)]] && 0 -eq 'a[$(scan_probe)]' ]]",
"shopt -s extglob\na=(1); [[ x != *(a)]] && 0 -eq 'a[$(scan_probe)]' ]]",
"a=(1); echo $(( $'a\\x5b\\x24\\x28scan_probe\\x29\\x5d' ))",
"a=(1); (( $'a\\x5b\\x24\\x28scan_probe\\x29\\x5d' ))",
"a=(1); echo ${a[$'a\\x5b\\x24\\x28scan_probe\\x29\\x5d']}",
"a=(1); declare \"${x:-$'a\\x5b\\x24\\x28scan_probe\\x29\\x5d=1'}\"",
"a=(1); unset \"${x:-$'a\\x5b\\x24\\x28scan_probe\\x29\\x5d'}\"",
"(( $'\\' )) # ' )); scan_probe",
"declare -A a; a[$'\\']=1 # ']=1; scan_probe",
"a=(1); echo ${a[$'\\']} # ']} $(scan_probe)",
": <<$'export'\n$export\nscan_probe\nexport",
": <<$'EOF'\n$EOF\nscan_probe\nEOF",
"echo $'\\'; scan_probe; : ' # '",
"export X=$'\\'; scan_probe; : ' # '",
"export X=$'\\c\\\\'; scan_probe # '",
"export X=$'\\c'; scan_probe; : ' # '",
"$'scan_probe\\0x'",
"$'scan_probe\\x00x'",
"$'scan_probe\\u0000x'",
"$'scan_probe\\c@x'",
"$'declare\\0x' 'a[$(scan_probe)]=1'",
"a=(1); $'unset\\0x' 'a[$(scan_probe)]'",
"$'let\\0x' 'a[$(scan_probe)]'",
"x='x y'; scan_probe[$x]=1",
// Parameter default bindings, Zsh subscripts, precommands, namerefs, ANSI-C escapes, and Zsh builtins.
"a=(1); : ${x:='a[$(scan_probe)]'}; echo $((x))",
"a=(1); : ${x:='$(scan_probe)'}; echo $((a[$x]))",
"a=(1); : ${x='$(scan_probe)'}; unset \"a[$x]\"",
'a=(1); : "${x:="a[\\$(scan_probe)]"}"; echo $((x))',
"a=(); : ${a[0]:='a[$(scan_probe)]'}; echo $((a[0]))",
"declare -i a x; : ${x:='a[$(scan_probe)]'}",
"a=(1); echo ${@[1,'a[$(scan_probe)]']}",
"a=(1); echo ${*[1,'a[$(scan_probe)]']}",
"a=(1); echo ${1[1,'a[$(scan_probe)]']}",
"a=(1); echo ${a[1]['$(scan_probe)']}",
"a=(1); echo ${s[1][1]:'a[$(scan_probe)0]'}",
"a=(1); echo ${${a}['$(scan_probe)']}",
"a=(1); echo ${${s}:'a[$(scan_probe)0]'}",
"a=(1); echo $a['$(scan_probe)']",
'a=(1); echo "$a[a[\\$(scan_probe)]]"',
"a=(1); x='$(scan_probe)'; echo $a[a[$x]]",
"a=(1); x='$(scan_probe)'; echo \"$a[a[$x]]\"",
"a=(1); echo $@[1,'a[$(scan_probe)]']",
"a=(1); echo $^a['$(scan_probe)']",
"exec typeset 'a[$(scan_probe)]=1'",
"a=(1); - typeset 'a[$(scan_probe)]=1'",
"a=(1); exec unset 'a[$(scan_probe)]'",
"a=(1); - unset 'a[$(scan_probe)]'",
"a=(1); exec -a foo unset 'a[$(scan_probe)]'",
"a=(1); exec let 'a[$(scan_probe)]'",
"a=(1); - let 'a[$(scan_probe)]'",
"unset a=(e:'scan_probe':)",
"unsetenv a=(e:'scan_probe':)",
"a=(1); r='a[$(scan_probe)]'; declare -n r; r=1",
"a=(1); r='a[$(scan_probe)]'; declare -n r; echo $r",
"a=(1); r='a[$(scan_probe)]'; typeset -n r; r=1",
"a=(1); x='a[$(scan_probe)]'; (( $'\\170' ))",
"a=(1); x='a[$(scan_probe)]'; echo ${a[$'\\170']}",
"a=(1); x='a[$(scan_probe)]'; declare a[x]=1",
"a=(1); x='a[$(scan_probe)]'; declare 'a[x]=1'",
"a=(1); x='a[$(scan_probe)]'; unset 'a[x]'",
"a=(1); x='a[$(scan_probe)]'; [[ -v a[x] ]]",
"a=(1); x='a[$(scan_probe)]'; read 'a[x]' <<< 1",
"a=(1); x='a[$(scan_probe)]'; printf -v 'a[x]' 1",
"a=(1); x='a[$(scan_probe)]'; test -v 'a[x]'",
"a=(1); echo ${(s:):)a['a[$(scan_probe)]']}",
"a=(1); s=abc; echo ${(s:):)s:'a[$(scan_probe)0]'}",
"a=(1); [[ !(-v 'a[$(scan_probe)]') ]]",
"echo $'a\\'; scan_probe; ' \\'",
"echo $'\\'$(scan_probe)' \\'",
": <<$'a\\'; scan_probe; '\\'\n$a\\\n\na'; scan_probe; '",
"export X=$'\\c\\''; scan_probe # '",
"a=(1); declare $'a[\\0$(scan_probe)]=1'",
"a=(1); unset $'a[\\0$(scan_probe)]'",
"a=(1); declare $'a[\\u0000$(scan_probe)]=1'",
": <<$'\\u0041'\n\\u0041\nscan_probe\nA",
": <<$'\\U00000041'\n\\U00000041\nscan_probe\nA",
"$'s\\can_probe'",
"$'de\\clare' 'a[$(scan_probe)]=1'",
"$'\\scan_probe'",
"a=(1); typeset $'a\\[\\$\\(scan_probe\\)\\]=1'",
"$'scan_probe\\xz'",
"$'scan_probe\\uz'",
"$'scan_probe\\Uz'",
"$'scan_probe\\C-@x'",
"$'scan_probe\\C@x'",
"a=(1); set -A 'a[$(scan_probe)]' 1",
"a=(1); set +A 'a[$(scan_probe)]' 1",
"unsetopt noglobsubst; x='*(e:scan_probe:)'; echo $x",
"set +o noglobsubst; x='*(e:scan_probe:)'; echo $x",
"set -xo globsubst; x='*(e:scan_probe:)'; echo $x",
"emulate zsh -o globsubst; x='*(e:scan_probe:)'; echo $x",
"NULLCMD=scan_probe; >/dev/null",
"READNULLCMD=scan_probe; </dev/null",
"NULLCMD=scan_probe; <<< x",
"setopt promptsubst; x='$(scan_probe)'; print -P $x",
"setopt promptvars; print -P '$(scan_probe)'",
"a=(1); exit 'a[$(scan_probe)]'",
"f() { return 'a[$(scan_probe)]'; }; a=(1); f",
"a=(1); shift 'a[$(scan_probe)]'",
"a=(1); for x in 1; do break 'a[$(scan_probe)]'; done",
"a=(1); for x in 1; do continue 'a[$(scan_probe)]'; done",
"a=(1); bye 'a[$(scan_probe)]'",
"a=(1); logout 'a[$(scan_probe)]'",
"a=(1); getln 'a[$(scan_probe)]'",
"a=(1); vared 'a[$(scan_probe)]'",
"a=(1); zformat -f 'a[$(scan_probe)]' x",
"a=(1); zstyle -s : x 'a[$(scan_probe)]'",
"zmodload zsh/stat; a=(1); zstat -A 'a[$(scan_probe)]' .",
"a=(1); compgen -V x -W 'a[$(scan_probe)]'; echo $((x[0]))",
// Special/positional subscripts, escape decoders, split expansions, float/array declarations, and continuations.
"a=(1); set -- 1 2 3 4 5 6 7 8 9 abc; echo ${1\\\n0:'a[$(scan_probe)0]'}",
"a=(1); echo ${1\\\n0[1,'a[$(scan_probe)]']}",
"a=(1); echo $0[1,'a[$(scan_probe)]']",
"a=(1); echo $?[1,'a[$(scan_probe)]']",
"a=(1); echo $#[1,'a[$(scan_probe)]']",
"a=(1); echo $-[1,'a[$(scan_probe)]']",
"a=(1); echo $![1,'a[$(scan_probe)]']",
"a=(1); echo $$[1,'a[$(scan_probe)]']",
'a=(1); echo "$?[1,a[\\$(scan_probe)]]"',
'a=(1); echo "$$[1,a[\\$(scan_probe)]]"',
"a=(1); x='a[\\x24(scan_probe)]'; unset \"${x@E}\"",
"a=(1); x='a[\\x24(scan_probe)]'; declare \"${x@E}=1\"",
"a=(1); x='a[\\x24(scan_probe)]'; y=${x@E}; echo $((y))",
'a=(1); x="$\'a[\\\\x24(scan_probe)]\'"; unset "${(Q)x}"',
'a=(1); x="$\'a[\\\\x24(scan_probe)]\'"; typeset "${(Q)x}=1"',
"a=(1); x=\"$'a[\\\\x24(scan_probe)]'\"; y=${(Q)x}; echo $((y))",
"a=(1); x='a[$'; x+='(scan_probe)]'; unset \"$x\"",
'a=(1); d=\'$\'; x="a[${d}(scan_probe)]"; unset "$x"',
"a=(1); x='a[$_1(scan_probe)]'; unset \"${x/_1/}\"",
"a=(1); x='a[$_1(scan_probe)]'; echo $(( ${x/_1/} ))",
"a=(1); x='a[$'; y='(scan_probe)]'; echo $(( $x$y ))",
"a=(1); read x <<< 'a[$\\(scan_probe)]'; echo $((x))",
"a=(1); printf -v x 'a[\\x24(scan_probe)]'; echo $((x))",
"a=(1); print -v x 'a[\\x24(scan_probe)]'; echo $((x))",
"a=(1); typeset -E x; x='a[$(scan_probe)]'",
"a=(1); typeset -F x; x='a[$(scan_probe)]'",
"a=(1); y='a[$(scan_probe)]'; typeset -E x=y",
"a=(1); y='a[$(scan_probe)]'; typeset -F x=y",
"a=(); declare a='([$(scan_probe)]=1)'",
"a=(); typeset a='([$(scan_probe)]=1)'",
"a=(); declare a+='([$(scan_probe)]=1)'",
"f() { local -a a; local a='([$(scan_probe)]=1)'; }; f",
"declare -a a; x='([$(scan_probe)]=1)'; declare a=$x",
"x=${NULLCMD::=scan_probe}; >/dev/null",
"x=${READNULLCMD::=scan_probe}; </dev/null",
": a${NULLCMD::=scan_probe}; >/dev/null",
": ${^NULLCMD::=scan_probe}; >/dev/null",
": ${(f)NULLCMD::=scan_probe}; >/dev/null",
"print -vNULLCMD scan_probe; >/dev/null",
"(( ${NULLCMD::=scan_probe} )); >/dev/null",
"[[ -n ${NULLCMD::=scan_probe} ]]; >/dev/null",
"a=(${NULLCMD::=scan_probe}); >/dev/null",
": <<EOF\n${NULLCMD::=scan_probe}\nEOF\n>/dev/null",
"PS4='$(scan_probe)'; set -x; x=1",
"PS4='$(scan_probe)'; set -o xtrace; x=1",
"export PS4='$(scan_probe)'; set -x; x=1",
"setopt -m 'glob*'; x='*(e:scan_probe:)'; echo $x",
"setopt -m '*subst*'; x='*(e:scan_probe:)'; echo $x",
"emulate sh; unsetopt shglob; setopt bareglobqual; x='*(e:scan_probe:)'; echo $x",
"emulate ksh; unsetopt shglob; setopt bareglobqual; x='*(e:scan_probe:)'; echo $x",
"a=(1); zregexparse 'a[$(scan_probe)]' 'a[$(scan_probe)]' x /x/",
"a=(1); private 'a[$(scan_probe)]=1'",
"a=(1); [[ x =~ (a )(b)]] || 0 -eq 'a[$(scan_probe)]' ]]",
"a=(1); [[ x =~ (a )(b)]] || -v 'a[$(scan_probe)]' ]]",
"a=(1); [[ -t 'a[$(scan_probe)]' ]]",
"a=(1); x='a[$(scan_probe)]'; [[ -t x ]]",
"a=(1); x='a[$(scan_probe)]'; [[ -t $x ]]",
"a=(1); test -t 'a[$(scan_probe)]'",
"a=(1); [ -t 'a[$(scan_probe)]' ]",
"a=(1); x='a[$(scan_probe)]'; test -t x",
"a=(1); x='a[$(scan_probe)]'; [ -t x ]",
"echo $\\\n'a\\''; scan_probe; : \\'",
": <<$\\\n'EOF'\nEOF\nscan_probe\n$EOF",
': <<$\\\n"EOF"\nEOF\nscan_probe\n$EOF',
"$\\\n'scan_probe'",
'$\\\n"scan_probe"',
"a=(1); unset $\\\n'a[\\x24(scan_probe)]'",
"echo \"${x:-$\\\n'\\x24(scan_probe)'}\"",
"[[ -n x ]\\\n] && scan_probe && [[ -n y ]]",
"a=(1); (\\\n( 'a[$(scan_probe)]' ))",
"a=(1); (\\\n( 1 + 'a[$(scan_probe)]' ))",
"a=(1); echo $(\\\n( 'a[$(scan_probe)]' ))",
"a=(1); x='a[$(scan_probe)]'; echo $(( x )\\\n)",
"a=(1); [\\\n[ -v 'a[$(scan_probe)]' ]]",
"a=(1); [\\\n[ 0 -eq 'a[$(scan_probe)]' ]]",
"a[0]\\\n=1 scan_probe",
"a[0]+\\\n=1 scan_probe",
"a=(); a[0]\\\n='a[$(scan_probe)]'; echo $((a[0]))",
"x='x y'; scan_probe[$x]\\\n=1",
"a=(1); x='a[$_1]'; y='(scan_probe)'; unset \"${x/_1/$y}\"",
"a=(1); x='a[$_1]'; y='(scan_probe)'; echo $(( ${x/_1/$y} ))",
"a=(1); x='a[$1'; y='(scan_probe)]'; echo $(( ${x%1}$y ))",
"a=(1); x='a[$1\n(scan_probe)]'; unset \"${x/1?/}\"",
"printf -v x 'a[\\x24\\x28scan_probe\\x29]'; echo $((x))",
"printf -v x 'a[\\044\\050scan_probe\\051]'; echo $((x))",
"a=(1); printf -v x 'a[\\u0024(scan_probe)]'; echo $((x))",
"a=(1); printf -v x 'a[\\U00000024(scan_probe)]'; echo $((x))",
"typeset -a a; print -v x 'a[\\x24\\x28scan_probe\\x29]'; echo $((x))",
"typeset -a a; print -v x 'a[\\044\\050scan_probe\\051]'; echo $((x))",
"typeset -a a; print -v x 'a[\\u0024\\u0028scan_probe\\u0029]'; echo $((x))",
"typeset -a a; print -v x 'a[\\U00000024\\U00000028scan_probe\\U00000029]'; echo $((x))",
'a=(1); x="$\'a[\\\\x24\\\\x28scan_probe\\\\x29]\'"; unset "${x:Q}"',
'a=(1); x="$\'a[\\\\x24\\\\x28scan_probe\\\\x29]\'"; unset "$x:Q"',
"a=(1); x=\"$'a[\\\\x24\\\\x28scan_probe\\\\x29]'\"; y=${x:Q}; echo $((y))",
"a=(1); read x <<'EOF'\na[$(scan_probe)]\nEOF\necho $((x))",
"a=(1); read x <<EOF\na[\\$(scan_probe)]\nEOF\necho $((x))",
"mapfile -t a <<'EOF'\nb[$(scan_probe)]\nEOF\necho $((a[0]))",
"a=(); declare a=\"$(echo '([$(scan_probe)]=1)')\"",
"a=(); declare a=\"`echo '([$(scan_probe)]=1)'`\"",
"a=(); set -- '([$(scan_probe)]=1)'; declare a=$1",
"a=(); set -- '([$(scan_probe)]=1)'; declare a=$@",
"a=(); set -- '([$(scan_probe)]=1)'; declare a=$*",
"a=(); set -- '([$(scan_probe)]=1)'; declare a=\"$1\"",
"x==(scan_probe)",
"x+==(scan_probe)",
"a=(1); a[1]==(scan_probe)",
"a=(1); a[1]+==(scan_probe)",
"export x==(scan_probe)",
"typeset x==(scan_probe)",
"f() { local x==(scan_probe); }; f",
"x==\\\n(scan_probe)",
"a=(1); ref='a[$(scan_probe)]'; echo ${\\\n!ref}",
"a=(1); ref='a[$(scan_probe)]'; echo \"${\\\n!ref}\"",
"a=(1); set -k; f() { echo $((x)); }; f x='a[$(scan_probe)]'",
"a=(1); set -o keyword; f() { echo $((x)); }; f x='a[$(scan_probe)]'",
"a=(1); set -k; test -v 'a[x]' x='a[$(scan_probe)]'",
"a=(1); set -o posix -k; : x='a[$(scan_probe)]'; echo $((x))",
"set -oglobsubst; x='*(e:scan_probe:)'; echo $x",
"set +onoglobsubst; x='*(e:scan_probe:)'; echo $x",
"setopt -oglobsubst; x='*(e:scan_probe:)'; echo $x",
"setopt +onoglobsubst; x='*(e:scan_probe:)'; echo $x",
"printf -vPS4 '\\x24(scan_probe)'; set -x; x=1",
"printf -vPS4 '\\044(scan_probe)'; set -x; x=1",
"functions[zshexit]=scan_probe",
"functions[TRAPEXIT]=scan_probe",
"functions[TRAPDEBUG]=scan_probe; x=1",
"functions=(zshexit scan_probe)",
": ${functions[zshexit]::=scan_probe}",
"functions[echo]=scan_probe; echo hi",
"options[globsubst]=on; x='*(e:scan_probe:)'; echo $x",
"options=(globsubst on); x='*(e:scan_probe:)'; echo $x",
": ${options[globsubst]::=on}; x='*(e:scan_probe:)'; echo $x",
"aliases[echo]=scan_probe; options[aliases]=on; : `echo`",
"galiases[echo]=scan_probe; options[aliases]=on; : `echo`",
"saliases[x]=scan_probe; options[aliases]=on; : `a.x`",
"commands[ls]=$(command -v scan_probe); ls",
"a=(1); [[ x == \"\" || ( 0 -eq 'a[$(scan_probe)]' ) ]]",
"a=(1); [[ x != '' && ( 0 -eq 'a[$(scan_probe)]' ) ]]",
"a=(1); [[ x != '' && ( -v 'a[$(scan_probe)]' ) ]]",
"a=(1); [[ x != '' && ( -t 'a[$(scan_probe)]' ) ]]",
"a=(1); [[ == && ( 0 -eq 'a[$(scan_probe)]' ) ]]",
"a=(1); [[ -n == && ( 0 -eq 'a[$(scan_probe)]' ) ]]",
"a=(1); [[ x == != || ( -t 'a[$(scan_probe)]' ) ]]",
"a=(1); [[ x == == || ( 0 -eq 'a[$(scan_probe)]' ) ]]",
"a=(1); [[ ( -n == ) && ( 0 -eq 'a[$(scan_probe)]' ) ]]",
"[[ -n $$'\\'' ]]; scan_probe # ' ]]",
"a=($$'\\''); scan_probe # ')",
"echo ${x:-$$'\\''}; scan_probe # '}",
"export X=$$\\\n'\\''; scan_probe # '",
"export X=$'\\'; scan_probe; : '; cat <<E\n'\nE",
"export X=$\\\n$'\\'; scan_probe; : ' # '",
'a=(1); echo "$a[(r)\\],a[\\$(scan_probe)]]"',
'a=(1); echo "$?[1\\],a[\\$(scan_probe)]]"',
'export X="${unset:+${a[0}}"; scan_probe; : "]}}"',
"export X=${unset:+${a[0}}; scan_probe; : ]}}",
'export X=${unset:+${a["0}"}}; scan_probe; : ]}}',
'cat <<E; export X="${unset:+$(( 0 + "0 ))}" && scan_probe\n))}"\nE',
'false && export X="${unset:+$(( 0 }"; scan_probe; : "))}"',
'false && export X="${unset:+$(echo a}"; scan_probe; : ")}"',
'false && export X="${unset:+$(echo {a}"; scan_probe; : ")}"',
'false && export X="${unset:+$(echo a})}"; scan_probe; : "}"',
"export X=$$'\\' # '; scan_probe",
"export X=$$'\\' #\nexport Y='; scan_probe # '",
"export X=$^$'\\' # '; scan_probe",
"export X=$#$'\\' # '; scan_probe",
"export X=$'\\'\nscan_probe\n'",
] as const
// These also run inside every wrapper below.
const nestedFixtures = [
"[[ a]]# ]] && scan_probe",
"{ export X={}# ; scan_probe; }",
"unset a[b; scan_probe; echo ]=1",
"case esac in (esac) scan_probe;; esac",
"case esac in (a|esac) scan_probe;; esac",
"for i in 1; { if true; then scan_probe; fi }; while false; do export X=1; done",
"(( echo '\"' ); scan_probe; ( echo '\"' ))",
"export X=${unset:+${x['0\"0']}}; scan_probe; : '\"]}}' # '",
"export X=$$'\\'; scan_probe # '",
": <<-export\n\tex\\\n\tport\n# $(scan_probe)\nexport",
"!(scan_probe)",
"f+() case x in x) scan_probe;; esac; f+",
"f@g() case x in x) scan_probe;; esac; f@g",
"f@g() for i in 1; do scan_probe; done; f@g",
] as const
const wrappers: Array<[name: string, wrap: (inner: string) => string]> = [
["$(...)", (inner) => `export OUTER=$( ${inner}\n)`],
['"$(...)"', (inner) => `export OUTER="$( ${inner}\n)"`],
["backticks", (inner) => `export OUTER=\` ${inner}\n\``],
["heredoc", (inner) => `: <<EOF\n$( ${inner}\n)\nEOF`],
["case arm", (inner) => `case x in x) ${inner}\n;; esac`],
["for loop", (inner) => `for k in 1; do ${inner}\ndone`],
["function", (inner) => `wrap_fn() {\n${inner}\n}; wrap_fn`],
["brace group", (inner) => `{ ${inner}\n}`],
]
describe.skipIf(process.platform === "win32")("real-shell soundness oracle", () => {
test("discovers at least bash on PATH", () => {
expect(shells.some((item) => item.endsWith("/bash"))).toBe(true)
})
test.each([...fixtures, ...nestedFixtures])("reports or rejects real-shell execution: %j", (source) => {
expectProbesReported(source)
})
test.each(wrappers.flatMap(([name, wrap]) => nestedFixtures.map((source) => [name, source, wrap(source)])))(
"reports or rejects in %s: %j",
(_, __, source) => {
expectProbesReported(source)
},
)
})
describe.skipIf(process.platform === "win32")("real-shell directory oracle", () => {
test.each(["cd >/dev/null TARGET", "cd 2>/dev/null TARGET", "cd $'TARGET'", 'cd "TARGET"'])(
"reports the directory a real shell changes to: %s",
async (template) => {
const source = template.replace("TARGET", target)
const targets = [target, fs.realpathSync(target)]
const changed = shells.filter((executable) =>
targets.includes(observe(executable, `${source}\npwd >> "$SCAN_PROBE_LOG"`).at(-1) ?? ""),
)
expect(changed.length, `Fixture never changed directory in any real shell: ${source}`).toBeGreaterThan(0)
for (const executable of changed) {
const parsed = await Effect.runPromiseExit(ShellParse.scanPortable(source, executable, root))
if (Exit.isSuccess(parsed)) expect(parsed.value.directories, `${executable} in: ${source}`).toContain(target)
}
},
)
})