5250ng / 5250ng/5250script

ON TIMEOUT / ON ERROR handlers cannot fire when triggered from inside a function

Open
#3 0 comments 0 reactions 1 assignee View on GitHub

@p0dalirius is already working on this.

Since Apr 18, 2026.

bug
Dominant language
C++
Stars
0
Forks
1
PR merge metrics
No merged PRs in 30d

Description

Summary

When an EXPECT timeout or an error-locked keyboard state occurs while execution is inside a function body, the executor attempts to jump to the registered ON TIMEOUT / ON ERROR label via gotoLabel(). gotoLabel() refuses to run while the call stack is non-empty and aborts execution with the misleading message "GOTO is not allowed inside functions", even though the script author never wrote a GOTO — they registered an ON handler.

Location
  • File: src/script_executor.cpp
  • Lines / functions:
    • ScriptExecutor::gotoLabel() at L733–L738 (early-returns with error when !m_callStack.isEmpty())
    • ScriptExecutor::notifyTerminalStateChanged() at L120–L128 (invokes gotoLabel(m_onErrorLabel) unconditionally)
    • ScriptExecutor::endExpect() at L612–L620 (invokes gotoLabel(m_onTimeoutLabel) unconditionally)
Category

functional

Severity

high

Impact: any timeout or error that happens inside a CALLed function will terminate the script with an incorrect diagnostic instead of running the registered handler, defeating the purpose of ON TIMEOUT / ON ERROR.

Reproduction / Evidence

Verified by code analysis.

Script:

ON ERROR GOTO fail
CALL myfunc()
LABEL fail
LOG "recovered"

DEF myfunc()
    EXPECT KEYBOARD UNLOCKED
ENDDEF

Execution path when the keyboard enters the ErrorLocked state during the EXPECT inside myfunc:

  1. notifyTerminalStateChanged() observes ErrorLocked (L118).
  2. m_onErrorLabel is non-empty, so gotoLabel("fail") is called (L122).
  3. gotoLabel() sees !m_callStack.isEmpty() (the active call to myfunc) and emits executionError(0, "GOTO is not allowed inside functions"), then stop() (L734–L738).
  4. The script terminates; the fail handler never runs.

The equivalent path exists for ON TIMEOUT via endExpect(false) at L614–L616 when the EXPECT was entered from inside a function.

Expected Behavior

When ON TIMEOUT / ON ERROR fires, the runtime should unwind the call stack (and any nested block exec frames) down to the top-level script, then jump to the registered label — because LABELs can only be top-level (per PROMPT.md), the handler necessarily lives there.

Actual Behavior

gotoLabel() refuses to run while inside a function and stops the script with a misleading "GOTO is not allowed inside functions" message. The registered handler never executes.

Root Cause

gotoLabel() was written as a defense against user-written GOTO inside a function (where label indices are valid only in root->children), but it is shared by the internal ON-handler dispatch path without distinguishing the two callers. The ON-handler path should be allowed to unwind the call stack first and then jump.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.