Skip to content

Commit

Permalink
util (ble/builtin/trap): fix the RETURN trap
Browse files Browse the repository at this point in the history
  • Loading branch information
akinomyoga committed Aug 24, 2022
1 parent 5b351e8 commit 793dfad
Show file tree
Hide file tree
Showing 7 changed files with 446 additions and 27 deletions.
10 changes: 7 additions & 3 deletions ble.pp
Expand Up @@ -1763,9 +1763,12 @@ function ble-update {
ble/bin/.freeze-utility-path man
ble/bin/.freeze-utility-path groff nroff mandoc gzip bzcat lzcat xzcat # used by core-complete.sh

ble/function#trace trap ble/builtin/trap ble/builtin/trap/finalize
ble/function#trace ble/builtin/trap/.handler ble/builtin/trap/invoke ble/builtin/trap/invoke.sandbox
ble/builtin/trap/install-hook EXIT
ble/builtin/trap/install-hook INT
ble/builtin/trap/install-hook ERR inactive
ble/builtin/trap/install-hook RETURN inactive

#%x inc.r|@|src/decode|
#%x inc.r|@|src/color|
Expand Down Expand Up @@ -2225,10 +2228,11 @@ function ble/base/sub:lib { :; } # do nothing
ble/function#trace ble/dispatch
ble/function#trace ble/base/attach-from-PROMPT_COMMAND

# Note #D1775: 以下は ble/base/unload 時に元の trap または ble.sh 有効
# 時にユーザーが設定した trap を復元する為に用いる物。
# Note #D1775: 以下は ble/base/unload 時に元の trap または ble.sh 有効時にユー
# ザーが設定した trap を復元する為に用いる物。ble/base/unload は中で
# ble/builtin/trap/finalize を呼び出す。ble/builtin/trap/finalize は別の箇所
# で ble/function#trace されている。
ble/function#trace ble/base/unload
ble/function#trace ble/builtin/trap/finalize

ble-import -f lib/_package
if [[ $_ble_init_command ]]; then
Expand Down
1 change: 1 addition & 0 deletions docs/ChangeLog.md
Expand Up @@ -290,6 +290,7 @@
- main (`ble-update`): fix error message with system-wide installation of `ble.sh` (fixed by tars0x9752) 1d2a9c1 a450775
- main. util: fix problems of readlink etc. found by test in macOS (reported by aiotter) `#D1849` fa955c1 `#D1855` a22e145
- util (`ble/builtin/trap`): run EXIT trap in subshells `#D1862` XXXXXXX
- util (`ble/builtin/trap`): fix the RETURN trap `#D1863` XXXXXXX

## Documentation

Expand Down
4 changes: 3 additions & 1 deletion make_command.sh
Expand Up @@ -1349,12 +1349,14 @@ function sub:scan {
#sub:scan/assign
sub:scan/builtin trap |
sed -E 'h;s/'"$esc"'//g;s/^[^:]*:[0-9]+:[[:space:]]*//
\Zble/util/print "trap -- '\''\$\{h//\$q/\$Q}'\'' \$nZd
\Zble/util/print "trap -- '\''\$\{_ble_trap_handler//\$q/\$Q}'\'' \$nZd
\Zline = "bind"Zd
\Ztrap_command=["'\'']trap -- Zd
\Zlocal trap$Zd
\Z\$\{content#"trap -- '\''"\}Zd
\Z\("trap -- '\''.*"\*\)Zd
\Z\(trap \| ble/builtin/trap\) .*;;Zd
\Zble/function#trace trap Zd
\Z# EXIT trapZd
g'

Expand Down
75 changes: 75 additions & 0 deletions memo/D1863.RETURN-recursive.sh
@@ -0,0 +1,75 @@

# 通常の RETURN の使い方

f2() {
trap 'echo "RETURN/f2 @ ${BLE_TRAP_FUNCNAME:-$FUNCNAME}"; trap - RETURN' RETURN
echo f2
echo /f2
}

f1() {
trap 'echo "RETURN/f1 @ ${BLE_TRAP_FUNCNAME:-$FUNCNAME}"; trap - RETURN' RETURN
echo f1
f2
echo /f1
}

f0() {
echo f0
f1
echo /f0
}

f0
trap -p
echo

# ble.sh 内部の構造 (function trap に対して RETURN trap が発火しない様
# に install-hook した時)

ihandler=0
trap_handler() {
local offset
for ((offset=1;offset<${#FUNCNAME[@]};offset++)); do
case ${FUNCNAME[offset]} in
(ble/builtin/trap/invoke.sandbox | ble/builtin/trap/invoke | ble/builtin/trap/.handler) ;;
(trap_set | trap | ble/builtin/trap) return 0 ;;
(*) break ;;
esac
done
#echo "trap_handler(offset=$offset,${FUNCNAME[*]})" >/dev/tty
eval "${trap_handlers[$1]}"
}
declare -ft trap_handler
trap_set() {
if [[ $1 == - ]]; then
trap - RETURN
echo "[note: current RETURN trap: $(trap -p RETURN)]"
else
trap_handlers[ihandler]=$1
trap "trap_handler $((ihandler++))" RETURN
fi
}
declare -ft trap_set

f2() {
trap_set 'echo "RETURN/f2 @ ${BLE_TRAP_FUNCNAME:-${FUNCNAME[1]}}"; trap_set -'
echo f2
echo /f2
}

f1() {
trap_set 'echo "RETURN/f1 @ ${BLE_TRAP_FUNCNAME:-${FUNCNAME[1]}}"; trap_set -'
echo f1
f2
echo /f1
}

f0() {
echo f0
f1
echo /f0
}

f0
trap -p
163 changes: 163 additions & 0 deletions note.txt
Expand Up @@ -6636,6 +6636,169 @@ bash_tips

2022-08-24

* RETURN trap はちゃんと動くのだろうか [#D1863]

現在の trap の実装で RETURN はちゃんと設定できるのだろうか? → 試してみた所、
ちゃんと意図した箇所で RETURN trap が呼び出されているが、関係ない場所でも
RETURN が呼び出されてしまっている。更に、設定した関数を抜けた後でも RETURN
が呼び出されている。

本来の Bash の実装を見ると trap RETURN を設定した関数 (追記: とその呼び出し
元) のみで trap RETURN が発火する様である。但し、trap -p / trap -p RETURN
すると何故か関数が抜けた後でも関数内部で使用した RETURN trap が出力される。
然し、trap -p で出力されていてもこれは決して呼び出される事はない様だ。

bash$ function a { trap 'echo RETURN a' RETURN; a2; echo a; }; function a2 { echo a2; }; a; trap -p
a2
a
RETURN a
trap -- 'echo RETURN a' RETURN
ble.sh$ function a { trap 'echo RETURN a' RETURN; a2; echo a; }; function a2 { echo a2; }; a; trap -p
RETURN a
RETURN a
a2
a
RETURN a
trap -- 'echo RETURN a' RETURN
RETURN a

と思ったが、RETURN a が発火している関数を調べてみた
所、_ble_edit_exec_gexec__TRAPDEBUG_adjustだった。そしてこれは declare -ft
を設定している関数なのであった。

% * というか試してみたが実は RETURN は元からそんなに賢い実装ではない様だ。
%
% bash$ function a0 { echo a0; a1; }
% bash$ function a1 { trap 'echo "RETURN:a1"' RETURN; echo a1; a2; }
% bash$ function a2 { trap 'echo "RETURN:a2"' RETURN; echo a2; }
% bash$ a0
% a0
% a1
% a2
% RETURN:a2
% RETURN:a2
% RETURN:a2
%
% 先ず、内側の関数で設定された trap が外側の関数で定義された trap を上書き
% してしまって、関数を抜けた後でもそれが復元される事がない。次に、内側の関
% 数で設定された trap が外側の RETURN を設定しなかった関数でも実行される。
%
% * RETURN trap が ble/builtin/trap に対しても呼び出されてしまう問題に関して
% は、単に RETURN trap の側で上手に処理してもらうべきなのではないか。という
% のも、元から設定した関数とは別の関数で呼び出される可能性があるのだから、
% ちゃんと関数を判定してから実際の処理を行う様に実装してあるべきだからであ
% る。
%
% と思ったが本当だろうか。trap - RETURN をちゃんと実行するようにしていれば
% RETURN が復元されるのではないか。そうでなかったとしても trap - RETURN と
% だけしておけば本来ちゃんと動いたのではないか。
%
% →試してみたらそうだった。RETURN trap の中でちゃんと trap を解除している
% 限りは各関数の RETURN trap が各関数で呼び出されるという形になる様だ。
%
% bash$ function a0 { echo a0; a1; }
% bash$ function a1 { trap 'echo "RETURN:a1"; trap - RETURN' RETURN; echo a1; a2; }
% bash$ function a2 { trap 'echo "RETURN:a2"; trap - RETURN' RETURN; echo a2; }
% bash$ a0
% a0
% a1
% a2
% RETURN:a2
% RETURN:a1

RETURN trap の中でちゃんと 'trap - RETURN' で trap を解除している限りは、
RETURN trap はちゃんと設定した関数の中だけで実行する振る舞いになる。これは
明らかに ble.sh の中では壊れてしまう。

"trap - RETURN" を関数内で実行してもちゃんとその効果は外に持続するだろうか?

$ st() { trap "echo '[RETURN:$1]'" RETURN; }
$ us() { trap - RETURN; }
$ f1() { st f1; echo f1; us; }
$ f1; trap -p
[RETURN:f1]
f1
[RETURN:f1]
trap -- 'echo '\''[RETURN:f1]'\''' RETURN
$ trap - RETURN; trap -p
$ st() { trap "echo '[RETURN:$1]'" RETURN; }
$ us() { trap - RETURN; }; declare -ft us
$ f1() { st f1; echo f1; us; }
$ f1; trap -p
[RETURN:f1]
f1

うーん。駄目持続しない。逆に declare -ft を実行すると削除されるがうーん。もっ
と現実的な設定で実験しないと分からない。

* memo/D1863.RETURN-recursive.sh

意外と簡単にちゃんと動く様にできた。interactive でもスクリプトでも両方と
も期待どおりに動く事を確認した。これと同じ様に実装する事を考える。

うーん。install-hook 等の枠組みを通じて修正するのではなくて RETURN は
RETURN で特別に実装するのが良い気がする。RETURN を設置する時には既存の user
trap を気にする必要もない。単に trap, ble/builtin/trap 内部で発生する
RETURN をスキップするだけで良いのでは? と思ったがスキップする為には結局
trap/.handler の様な複雑な仕組みが必要になる。

実は単に install-hook RETURN inactive にすれば良いという可能性もある? → そ
の様にして見たら各関数ごとに記録されている trap が消滅して駄目だった。trap
を各関数呼び出し階層に対して記録する必要があるのだった。

* どの様に記録してどの様に取り出すかについて落ち着いて考える必要がある。


取り敢えず trap - RETURN されない限りは内側で設定された trap はずっと残る
という事。全ての階層について記録しておいて一番深い階層で記録した物をいつ
も取り出しておくという実装で良いのだろうか。

? しかし A->B->C1 と呼び出して C1 で設定した trap は A->B では有効である
が、A->B->C2 と更に呼び出した別の関数の中では (C2 が -t を持っていない
限り) 無効の筈である。なので B に戻った時点で C1 のレベルに設定した
handler を除去して B のレベルに再設定するべきではないだろうか。

x と思ったが B に戻った時点を特定する手段がない。結局そのように実装した
としても C2 で有効になってしまうのではないか。

o と思ったが、もし本当に継承されないのだとしたら C2 の中でそもそも
trap/.handler は呼び出されないので気にしなくても良いのではないか。も
し C2 の中で trap/.handler が呼び出されるのだとしたらそれは別の
handler が明示的に設定されたという事であり、その時点で handler が上書
きされてなくなっていると期待して良いのではないか。

? C2 が -t を持っている場合 (C2/t) や、extdebug/-T が設定されている場合
はどうだろうか。この場合には結局 C1 が設定した handler を呼び出すのが
期待される振る舞いなので handler が C1 のレベルに残留していても特に問
題はない。

? trap - RETURN した時に別の文脈で設定された handler をどう処理するべきだ
ろうか。基本的には自身のレベルよりも高いレベルの物は全て削除するという
ので良い気がする。然し、別の子関数で設定された物も一緒に削除してしまっ
ては駄目である。

例えば A->B->C1 で設定された handler を A->B->C2/t で trap - RETURN し
た時には

? というかそもそも A->B->C1 で handler を設定して、その後で A->B->C2 でも
handler を設定して最後に trap - RETURN する場合を考えたら、元々
A->B->C1 に設定した handler は A->B の階層に移動して置かないと問題にな
るのではないか。

と思ったがこれに関しては C2 で handler を新しく設定する時に C2 の階層に
ある handler を B に移動すれば良い? とも思ったがそれだと同じ C2 の呼び
出しで設定した物と区別が付かない。

* extdebug や -T についても確認しておく必要がある → 対応した。

実装した。動いている気がする。FUNCNAME 等が色々面倒な事になるので
BASH_TRAP_{FUNCNAME,SOURCE,LINENO} という変数も提供する事にした。本当はもっ
と色々なパターンでテストするべきの気がするが、

source memo/D1863.RETURN-recursive.sh

が取り敢えず plain Bash と同様に動くのでそれで良い事にする。

* trap: EXIT trap が subshell で発火しない [#D1862]

これは恐らく subshell の中で改めて trap を実行しないと有効にならないのが原
Expand Down
2 changes: 2 additions & 0 deletions src/def.sh
Expand Up @@ -19,9 +19,11 @@ function blehook/declare {
blehook/declare EXIT
blehook/declare INT
# blehook/declare ERR # inactive
# blehook/declare RETURN # inactive
blehook/declare internal_EXIT
blehook/declare internal_INT
blehook/declare internal_ERR
blehook/declare internal_RETURN
blehook/declare unload
blehook/declare ATTACH
blehook/declare DETACH
Expand Down

0 comments on commit 793dfad

Please sign in to comment.