Skip to content

expand: don't panic on a missing parameter name in zsh - #1363

Closed
benjaminabbitt wants to merge 1 commit into
mvdan:masterfrom
benjaminabbitt:expand-zsh-missing-param-name
Closed

benjaminabbitt wants to merge 1 commit into
mvdan:masterfrom
benjaminabbitt:expand-zsh-missing-param-name

Conversation

@benjaminabbitt

Copy link
Copy Markdown

Under LangZsh the parser accepts an omitted parameter name (${}, ${:-foo}) and leaves ParamExp.Param nil, intended since 87df0f9 ("syntax: support missing parameter expansion names in zsh"). The expand package still read pe.Param.Value at several sites, so expanding such a word panics:

f, _ := syntax.NewParser(syntax.Variant(syntax.LangZsh)).Parse(strings.NewReader("echo x${}y"), "")
args := f.Stmts[0].Cmd.(*syntax.CallExpr).Args[1:]
expand.Fields(&expand.Config{Env: expand.ListEnviron()}, args...)
// panic: nil pointer dereference at expand.go:793 (listElems)

Real zsh expands a missing name to the empty string (echo x${}y prints xy), so I read the name through a small paramName helper that returns "" for a nil Param. This matches the nil guards already in the printer and simplifier, added when the same nil crashed --simplify in #1301. TestZshMissingParamName covers the plain and double-quoted forms through expand.Fields; both panic before the change and expand to xy after, and go test ./... passes.

Found by fuzzing a pre-tool hook that parses agent-issued commands in the user's $SHELL dialect and calls expand.Fields on them; on a zsh host any command containing ${} crashed it.

@mvdan mvdan left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! The fix looks correct, although the godoc on paramName should be way shorter. We also use more generic table-driven tests in this module, although many like in the expand or interp packages don't currently test various lang variants. I would look at expanding an existing table-driven test rather than adding a new and specific one

The zsh parser accepts an omitted parameter name, as in ${} or ${:-foo}, and
leaves ParamExp.Param nil for these (see "syntax: support missing parameter
expansion names in zsh"). The expander read pe.Param.Value at several sites, so
expanding such a word panicked with a nil pointer dereference; e.g.
expand.Fields on `echo x${}y` crashed in listElems.

Real zsh expands a missing name to the empty string (`echo x${}y` prints "xy"),
so read the name through a paramName helper that returns "" for a nil Param,
matching the nil guards already in the printer and simplifier.
@benjaminabbitt
benjaminabbitt force-pushed the expand-zsh-missing-param-name branch from c2ab623 to aceab37 Compare July 25, 2026 14:27
@benjaminabbitt

benjaminabbitt commented Jul 25, 2026 •

Copy link
Copy Markdown
Author

Thanks for taking a look! Shortened the paramName godoc, then just deleted it — the test ended up saying it better than a comment would have.

On the table-driven suggestion: I did go hunting for an existing one to hang this off of, but nothing quite fit. TestConfigNils is really about a nil/zero *Config, whereas the nil here is ParamExp.Param in the parsed AST, so it'd read a bit sideways. And the parsing side of this already lives over in syntax/filetests_test.go.

The big runTests table in interp was the other option — I could've taught it about variants and folded this in there. But it's singularly bash today (and confirms against real bash, where ${} is just a bad substitution), so reworking the whole thing to be tabular over languages for one zsh-only case felt like going a bit far.

Closest existing fit was TestFieldsIdempotency, but that one's specifically about running twice and getting the same result, so a missing-name case would've felt out of place under that name.

So I left it as its own test but tried to keep it in the house style — table-driven, parameterized over the variants, qt assertions like the rest. It asserts the zsh expansions plus the invalid parameter name rejection for the other shells.

If you'd still rather have it merged into one of the existing tables, happy to do that — just let me know.

@mvdan mvdan closed this in 1630417 Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

⌂ 换网址