I was looking at how to represent try-after in Core Erlang, and noticed something odd. Given
z(E, H, A, X) -> try E() catch _C:_R -> H() after A(X) end.
erlc +to_core (OTP-25.0.3) generates
'z'/4 =
%% Line 4
( fun (_0,_1,_2,_3) ->
try
try
apply _0
()
of <_4> ->
_4
catch <_7,_6,_5> ->
apply _1
()
of <_9> ->
do apply _2
(_3)
_9
catch <_12,_11,_10> -> % (1)
do apply _2
(_3)
primop 'raise'
(_10, _11) % (2)
-| [{'function',{'z',4}}] )
which looks fine (a nested try, the outer one deals with the after logic).
However, if the inner try raises an exception, we end up at (1) and bind _12, _11 and _10 to the Class, Reason and (raw) Stacktrace of the exception, perform the after A(X) call, and then re-raise the exception at (2). But at (2) we only pass the (raw) Stacktrace and Reason, not the Class. Yet when running this the correct exception class (exit, error, or throw) is propagated from A(X) to the caller of z/4.
Is there some side-channel which propagates the exception class even though the intermediate code (Core Erlang) doesn’t do so?