Skip to content

Fix ON TIMEOUT/ON ERROR handlers not firing from inside a function (#3)#6

Open
p0dalirius wants to merge 1 commit intomainfrom
bugfix-on-handlers-in-functions
Open

Fix ON TIMEOUT/ON ERROR handlers not firing from inside a function (#3)#6
p0dalirius wants to merge 1 commit intomainfrom
bugfix-on-handlers-in-functions

Conversation

@p0dalirius
Copy link
Copy Markdown
Contributor

Linked Issue

Closes #3

Root Cause

gotoLabel() refuses to run while m_callStack is non-empty and emits "GOTO is not allowed inside functions", then stops the script. This guard is reasonable for user-written GOTO inside a function body (where top-level label indices would be misleading), but it was also being invoked on the internal dispatch path from notifyTerminalStateChanged() (for ON ERROR) and endExpect(false) (for ON TIMEOUT) without distinguishing the two callers. When a timeout or error-locked state occurred while execution was inside a CALLed function, the registered handler was therefore never dispatched — the script terminated with a misleading message the author never triggered themselves.

Fix Description

Introduce ScriptExecutor::unwindToTopLevel() that pops any open exec frames down to each active call frame's captured execStackDepth and then calls returnFromFunction() to restore the call frame's saved parameter variables, looping until the call stack is empty. Invoke this helper at the two internal dispatch points — notifyTerminalStateChanged() (before gotoLabel(m_onErrorLabel)) and endExpect(false) (before gotoLabel(m_onTimeoutLabel)) — so the runtime is at top-level script context by the time gotoLabel() runs. gotoLabel() itself is unchanged: user-written GOTO inside a function still errors exactly as before.

An alternative considered and rejected was to relax gotoLabel() itself to unwind unconditionally. Rejected because it would silently change the meaning of user GOTO inside function bodies and mask authoring mistakes that the current diagnostic usefully surfaces.

How Verified

  • Tests: added tests/test_script_executor.cpp::onTimeoutFromInsideFunction. It installs a FakeScreen ScreenInterface stub, sets GLOBAL EXPECT_TIMEOUT 50, registers ON TIMEOUT GOTO handler, then calls a function containing EXPECT TEXT "never-present". The test spies on logMessage and executionError and asserts that LOG "caught" (at the handler) fires, LOG "should-not-run" (after the CALL) does not, and no "GOTO is not allowed inside functions" error is emitted.
  • Regression check: I temporarily stubbed out the two new unwindToTopLevel() calls and reran the test — it correctly failed at caughtLogged assertion, then passed again once restored. This confirms the test exercises the bug, not a tautology.
  • Existing tests: test_script_lexer and test_script_parser both still pass.

Test Coverage

Added: tests/test_script_executor.cpp::onTimeoutFromInsideFunction — end-to-end regression via the public ScriptExecutor API with a stub ScreenInterface. A new executor test target (test_script_executor) is added to CMakeLists.txt alongside the existing lexer and parser tests.

Scope of Change

  • Files changed: src/script_executor.cpp, include/5250script/script_executor.h, tests/test_script_executor.cpp (new), CMakeLists.txt
  • Submodule pointer updated: no
  • Behavioral changes outside the bug fix: none. gotoLabel() still rejects user-written GOTO inside a function.

Risk and Rollout

Local change confined to the internal ON-handler dispatch paths. No change to public API shape. Previously, a timeout or error-locked event inside a function always ended the script with an error; now it correctly dispatches the registered handler (or, if no handler is registered, behaves exactly as before).

Notes

Establishes an executor test harness (FakeScreen + ScriptExecutor driven via QSignalSpy) that future executor-level tests can reuse.

@p0dalirius p0dalirius self-assigned this Apr 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant