From: Michal Privoznik Date: Fri, 16 Jan 2026 08:12:30 +0000 (+0100) Subject: syntax-check: Introduce sc_prohibit_local_with_subshell rule X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=28482f26305b3a15bb7d43f99e73c8bb8b91fef6;p=thirdparty%2Flibvirt.git syntax-check: Introduce sc_prohibit_local_with_subshell rule In shell, the following function doesn't echo '1' but '0': func() { local var=$(false) echo $? } This is because '$?' does not refer to 'false' but 'local'. The bash_builtins(1) manpage explains it well. And it also mentions other commands behaving the same: export, declare and readonly. Since it is really easy to miss this pattern, introduce a syntax-check rule. Mind you, the following pattern (which passes the rule) does check for the subshell exit code: func() { local var var=$(false) echo $? } Signed-off-by: Michal Privoznik Reviewed-by: Ján Tomko --- diff --git a/build-aux/syntax-check.mk b/build-aux/syntax-check.mk index f605c9b0e3..022e8e6550 100644 --- a/build-aux/syntax-check.mk +++ b/build-aux/syntax-check.mk @@ -394,6 +394,17 @@ sc_prohibit_g_autofree_const: halt='‘g_autofree’ discards ‘const’ qualifier from pointer target type' \ $(_sc_search_regexp) +sc_prohibit_local_with_subshell: + @err=0; for f in $$($(VC_LIST_EXCEPT) | $(GREP) -E '\.sh(\.in)?$$'); do \ + lines=$$( $(AWK) \ + '/^\s*(local|export|declare|readonly)\s+.*=/ { nr=NR; c=1; next } \ + /^\s*$$/ { if (c) next } \ + /\$$\?/ { if (c) { print nr; c=0; } next } \ + { c=0 }' $$f ) ; \ + for l in $$lines; do echo $$f:$$l 1>&2; err=1; done ; \ + done; \ + test $$err -eq 0 || { msg="Declare and assign separately to avoid masking return values" $(_sc_say_and_exit) } + # Many of the function names below came from this filter: # git grep -B2 '\<_('|grep -E '\.c- *[[:alpha:]_][[:alnum:]_]* ?\(.*[,;]$' \