Reproducer
declare sub dowork()
dim as integer marker = 4711
dim as integer counter = 13
on error goto handler
dowork()
print "no error"
end 1
handler:
print "marker="; marker; " counter="; counter
end 0
sub dowork()
dim as integer f = freefile
open "/nonexistent/x" for input as #f
close #f
end sub
Build and run with fbc -e locals.bas && ./locals.
Expected behavior
The handler prints marker= 4711 counter= 13.
Actual behavior
The handler runs with the failing procedure's stack and frame pointer instead of the handler's. On current master (5714d10) this reproduces with the C and GAS64 backends:
| backend |
optimization |
result |
| gcc (C backend) |
-O 0 |
garbage module-local values |
| gcc (C backend) |
-O 2 |
SIGSEGV |
| clang (C backend) |
-O 0 |
garbage module-local values |
| gas64 |
-O 0 and -O 2 |
garbage module-local values |
ON LOCAL ERROR GOTO is unaffected because its handler remains in the same procedure.
Cause
fb_ErrorThrow*() returns ctx->handler, then generated code jumps to that address. If the error was raised in a procedure and the handler is at module level, the transfer does not restore the handler frame. The module-level handler reads locals through the wrong frame pointer; with optimized C output the stack can also violate the platform ABI and crash in the next runtime call.
Required direction
A correct nonlocal handler transfer needs to capture the handler context where ON ERROR GOTO installs it and restore it before entering the handler, similar to setjmp/longjmp. That has a RESUME consequence: a transfer that unwinds the failing procedure cannot later resume inside it. A same-procedure handler can retain ordinary RESUME behavior.
Reproducer
Build and run with
fbc -e locals.bas && ./locals.Expected behavior
The handler prints
marker= 4711 counter= 13.Actual behavior
The handler runs with the failing procedure's stack and frame pointer instead of the handler's. On current
master(5714d10) this reproduces with the C and GAS64 backends:-O 0-O 2-O 0-O 0and-O 2ON LOCAL ERROR GOTOis unaffected because its handler remains in the same procedure.Cause
fb_ErrorThrow*()returnsctx->handler, then generated code jumps to that address. If the error was raised in a procedure and the handler is at module level, the transfer does not restore the handler frame. The module-level handler reads locals through the wrong frame pointer; with optimized C output the stack can also violate the platform ABI and crash in the next runtime call.Required direction
A correct nonlocal handler transfer needs to capture the handler context where
ON ERROR GOTOinstalls it and restore it before entering the handler, similar to setjmp/longjmp. That has a RESUME consequence: a transfer that unwinds the failing procedure cannot later resume inside it. A same-procedure handler can retain ordinary RESUME behavior.