From 566b295345c3a10fff636c071fd0c0ae7d4469a2 Mon Sep 17 00:00:00 2001 From: Eljees <3.14hell@gmail.com> Date: Sun, 9 Aug 2026 10:20:49 +0000 Subject: [PATCH] Record the command's id in CFSetExitCode for built-in handlers handleCommand receives the whole T_SimpleCommand as `cmd`, and the ordinary path registers the exit code against it through `handleOthers (getId cmd) ...`. regularExpansionWithStatus, used by the built-in table, shadowed that name with the command's first word, so printf, unset, wait, mapfile, readarray, read and the four DEFINE_* commands recorded their exit code under the id of a T_NormalWord instead. Consumers that resolve the id through idMap then get a word where they expect a command. checkOverwrittenExitCode is one of them: getCommandBasename is Nothing for a T_NormalWord, so isPrinting never matched and SC2320 stayed silent for printf while firing for echo, which is not in the table and goes through handleOthers. Drop the shadowing pattern so cmd again refers to the command. Fixes #3490 --- src/ShellCheck/Analytics.hs | 2 ++ src/ShellCheck/CFG.hs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ShellCheck/Analytics.hs b/src/ShellCheck/Analytics.hs index f6208e72b..fcc01c96d 100644 --- a/src/ShellCheck/Analytics.hs +++ b/src/ShellCheck/Analytics.hs @@ -5097,6 +5097,8 @@ prop_checkOverwrittenExitCode5 = verify checkOverwrittenExitCode "x; if [ $? -eq prop_checkOverwrittenExitCode6 = verify checkOverwrittenExitCode "x; [ $? -gt 0 ] && fail=$?" prop_checkOverwrittenExitCode7 = verifyNot checkOverwrittenExitCode "[ 1 -eq 2 ]; status=$?" prop_checkOverwrittenExitCode8 = verifyNot checkOverwrittenExitCode "[ 1 -eq 2 ]; exit $?" +prop_checkOverwrittenExitCode9 = verify checkOverwrittenExitCode "x; printf '%d' $?; [ $? -eq 0 ]" +prop_checkOverwrittenExitCode10 = verifyNot checkOverwrittenExitCode "read -r x; [ $? -eq 0 ]" checkOverwrittenExitCode params t = case t of T_DollarBraced id _ val | getLiteralString val == Just "?" -> check id diff --git a/src/ShellCheck/CFG.hs b/src/ShellCheck/CFG.hs index c235cb7d4..57905e9b3 100644 --- a/src/ShellCheck/CFG.hs +++ b/src/ShellCheck/CFG.hs @@ -1196,7 +1196,7 @@ handleCommand cmd vars args literalCmd = do linkRanges $ [args] ++ assignments ++ [exe] ++ dropAssignments - regularExpansionWithStatus vars args@(cmd NE.:| _) p = do + regularExpansionWithStatus vars args p = do initial <- regularExpansion vars (NE.toList args) p status <- newNodeRange $ CFSetExitCode (getId cmd) linkRange initial status