[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[PATCH] Exit status by no-argument `return' for function calls in trap h
From: |
Koichi Murase |
Subject: |
[PATCH] Exit status by no-argument `return' for function calls in trap handlers |
Date: |
Fri, 17 Apr 2020 02:21:09 +0900 |
Configuration Information [Automatically generated, do not change]:
Machine: x86_64
OS: linux-gnu
Compiler: gcc
Compilation CFLAGS: -O2 -g -pipe -Wall -Werror=format-security
-Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -fexceptions
-fstack-protector-strong -grecord-gcc-switches
-specs=/usr/lib/rpm/redhat/redhat-hardened-cc1
-specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -mtune=generic
-fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection
-Wno-parentheses -Wno-format-security
uname output: Linux chatoyancy 5.1.20-300.fc30.x86_64 #1 SMP Fri Jul
26 15:03:11 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
Machine Type: x86_64-redhat-linux-gnu
Bash Version: 5.0
Patch Level: 11
Release Status: release
Summary:
The behavior of no-argument `return' in trap handlers has been
changed from Bash 4.4 to follow the description of POSIX. Recently
this behavior caused problems in my Bash script. I am wondering
whether this change actually matches with the behavior meant by
POSIX because the change introduces unreasonable constraints in
writing shell functions.
For the condition of this special treatment of `return', POSIX says
``When return is executed in a trap action''. Here are two possible
interpretation: (A) `return's specified in the argument string of
`trap' builtin are affected, or (B) all the `return's in entire
runtime function-call tree in trap processing are affected. I guess
that POSIX wanted to provide a way to exit functions that received
signals without changing the value of `$?'. If that is the case,
the POSIX should have meant (A). However, the current Bash
implementation behaves as if it follows the interpretation (B).
I would like to hear what do you think of this.
Description:
In the latest release of Bash 5.0.16 and in the current devel
branch, when function calls in trap handlers are terminated by
no-argument `return' builtin, the exit status is always the value of
$? at the moment that the trap handler started. This behavior was
introduced in Bash 4.4.
I noticed this behavior in debugging an infinite loop caused by
SIGWINCH reported at https://github.com/akinomyoga/ble.sh/issues/48.
The structure of the code related to the infinite loop can be
summarized in the following small script.
----------------------------------------
#!/bin/bash
function check_loop_condition {
if ((index++%10==0)); then
echo index=$index
((index<100))
return # *** return exit status of the previous command ***
fi
: do something
return 0
}
function update {
local index=0
while check_loop_condition; do :; done
}
trap 'update' USR1
kill -USR1 $$
----------------------------------------
If the function `update' is called normally (outside of trap
handlers), it will print the numbers {1..101..10} and terminate
soon. However, when it is called in trap handlers, it falls into an
infinite loop in Bash 4.4+. This is because the no-argument
`return' in the function `check_loop_condition' always returns `$?'
before the trap started regardless of the exit status of
`((index<100))'.
If all the `return's in the entire function-call tree are affected
in trap processing as in the interpretation (B), one cannot reliably
use no-argument `return' to return the last-command exit status. To
avoid the problem, one has to always write the exit status
explicitly as `return $?', and there is no use case for no-argument
`return' at all. I don't think this is meant by POSIX which defines
the behavior of no-argument `return' explicitly. Or, maybe one
cannot use shell functions in trap handlers. Note that in my
script, I need to re-render the terminal contents on SIGWINCH so
that I need to run complicated shell programs implemented as shell
functions. POSIX does not prohibit the use of shell functions in
trap handlers.
Here I checked how the behavior was changed in Bash 4.4. The
related commits are 939d190e0 (commit bash-20140314 snapshot) and
e2f12fdf5 (commit bash-20140321 snapshot). The relevant ChangeLog
is quoted as follows:
> 3/11
> ----
>
> builtins/common.c
> - get_exitstat: when running `return' in a trap action, and it is not
> supplied an argument, use the saved exit status in
> trap_saved_exit_value. Fixes Posix problem reported by
> Eduardo A. Bustamante L坦pez <dualbus@gmail.com>
>
>
> 3/18
> ----
>
> builtins/common.c
> - get_exitstat: update fix of 3/11 to allow the DEBUG trap to use the
> current value of $? instead of the value it had before the trap
> action was run. This is one reason the DEBUG trap exists, and
> extended debug mode uses it. Might want to do this only in Posix
> mode
This change is made after the following discussion:
https://lists.gnu.org/archive/html/bug-bash/2014-03/msg00053.html
Taking the following comment and the code example by the original
reporter Eduardo, he seems to assume the interpretation (A), but
what was actually implemented was the interpretation (B).
> So as I read it, `action' refers to the whole string.
Also, I have checked the behavior of other shells. `zsh', `ash'
family (dash/ash, busybox sh) and `posh' does not implement the
special treatment of `return' in trap handlers. `ksh' family
(ksh93, mksh) and `yash' implements the interpretation (B). There
is no existing implementation of (A). But currently I still think
the intepretation (A) is reasonable. If there is rationale for the
interpretation (B), I would like to know it.
Repeat-By:
Here I provide a few test cases for the special treatment of
no-argument `return'. The following example demonstrates the
behavior of no-argument `return' appearing directly in the trap
argument. The expected result is `exit=0' but not `exit=222' as
`return' should return the exit status before the trap handler
starts as required by POSIX. Bash 4.3 and before outputs `exit=222'
because it does not implement the special treatment. Bash 4.4 and
later ourputs `exit=0' as expected.
----------------------------------------
#!/bin/bash
setexit() { return "$1"; }
trap 'setexit 222; return' USR1
process() { kill -USR1 $$; }
process
echo exit=$?
----------------------------------------
The next example also demonstrates the behavior of no-argument
`return' directly specified in the trap argument. The expected
result is to print `exit=0'. Bash 4.3 and earlier outputs
`exit=123', and Bash 4.4 and later outputs `exit=0' as expected by
POSIX.
----------------------------------------
#!/bin/bash
function setexit { return "$1"; }
trap 'setexit 123; return' USR1
function loop { while :; do :; done; }
function get_loop_exit { loop; echo "exit=$?"; }
{ sleep 1; kill -USR1 $$; } &
get_loop_exit
----------------------------------------
The third example is the case for `return' in function calls in trap
processing. If we adopt the interpretation A (B), the expected
result is `A' (`B'). Bash 4.3 and earlier outputs `A', and Bash 4.4
and later outputs `B'.
----------------------------------------
#!/bin/bash
check() { false; return; }
handle() { check && echo B || echo A; }
trap handle USR1
kill -USR1 $$
----------------------------------------
Fix:
I attach a patch which changes the behavior to match with the more
reasonable interpretation (A). To detect the current nest level of
returns, I initially thought about using the variable
`return_catch_flags', but it turned out that the variable also
counts the levels of `evalstring' so I decided to use `funcnest +
sourcenest' instead. The patch is tested with the above four cases.
--
Koichi
0001-builtins-common-get_exitstat-check-nest-levels-of-ru.patch
Description: Binary data
- [PATCH] Exit status by no-argument `return' for function calls in trap handlers,
Koichi Murase <=