executor: a backtick anywhere in a word is a substitution (#740) - #745
Merged
Conversation
A `...` substitution that was not the FIRST thing in a word never reached a
substitution handler, so it was emitted as its own source text:
echo x`printf 4`y
before: x`printf 4`y bash, zsh, dash: x4y
The `$` forms above already handle this: they route a mid-word construct to
the general expander on `first_dollar != text`. The backtick branch tested
only `text[0]`, so a word with a LEADING literal fell through to the
plain-text path and had its backslashes removed and nothing else. A TRAILING
literal worked, because the backtick was still first -- which is the whole
shape of the bug, and why `PREFIX=`date`` shipped its own source while
``date`-suffix` did not.
The scan looks for an UNESCAPED backtick, since `x\`y` is a literal and must
not become a substitution. Finding one is not on its own enough to treat the
word as a bare substitution: cmdsub_spans_whole_word assumes the construct
starts at text[0], so the position test has to gate that call as well. Without
it the whole word went to expand_command_substitution and was executed
verbatim, reporting `x: command not found`.
All four modes were identical here and every one of them disagreed with its
own reference, so this is a defect rather than curation, and the fix is
mode-invariant.
Three adversarial results were checked and are NOT this bug:
- `IFS=,; v=`printf 'a,b'`; echo x${v}y` differs from bash because lush mode
has word splitting off by design; `mode bash` and `mode posix` both
reproduce bash exactly.
- an escaped `\"` inside a backtick inside double quotes truncates the word
(#743, filed).
- a backtick in a `case` pattern is a parse error (#744, filed) -- the
backtick sibling of #494.
Both filed defects are byte-identical against a build of the parent commit.
tests/integration/test_nested_backticks.c gains 13 checks: a leading literal
with and without a trailing one, an assignment, two substitutions in one word,
the combination with a nested substitution, empty and no-output bodies,
redirection and for-list positions, and three that pin an escaped or
single-quoted backtick as a literal. 10 of the file's 29 checks fail against
the parent build.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or 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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #740.
The defect
A
`...`substitution that was not the first thing in a word never reached a substitution handler, so it was emitted as its own source text:The
$forms above already handle this — they route a mid-word construct to the general expander onfirst_dollar != text. The backtick branch tested onlytext[0], so a word with a leading literal fell through to the plain-text path, which removes backslashes and nothing else.A trailing literal always worked, because the backtick was still first. That asymmetry is the whole shape of the bug:
PREFIX=`date`shipped its own source text while`date`-suffixdid not.Two details the fix has to get right
x\`yis a literal and must not become a substitution.cmdsub_spans_whole_wordassumes the construct starts attext[0], so the position test gates that call too. Without it the whole word went toexpand_command_substitutionand was executed verbatim, reportingx: command not found— which is exactly what my first cut did.All four modes were identical here and every one disagreed with its own reference, so this is a defect rather than curation; the fix is mode-invariant.
Three adversarial results that are NOT this bug
IFS=,; v=printf 'a,b'; echo x${v}ydiffers from bash because lush mode has word splitting off by design;mode bashandmode posixboth reproduce bash exactly. Curation, verified per-mode.\"inside a backtick inside double quotes truncates the word.casepattern is a parse error; the backtick sibling of Command substitution mis-terminates at a case-pattern ) #494, which fixed the same shape for$( ).Both filed defects are byte-identical against a build of the parent commit.
Verification
tests/integration/test_nested_backticks.c: leading literal with and without a trailing one, assignment, two substitutions in one word, combination with a nested substitution, empty and no-output bodies, redirection and for-list positions, and three pinning an escaped or single-quoted backtick as a literal.