Skip to content

fix(xpcall): catch errors under direct PCall/DoString + inspect upvalues#32

Open
skhaz wants to merge 5 commits into
mainfrom
fix/xpcall-recover
Open

fix(xpcall): catch errors under direct PCall/DoString + inspect upvalues#32
skhaz wants to merge 5 commits into
mainfrom
fix/xpcall-recover

Conversation

@skhaz

@skhaz skhaz commented Jul 7, 2026

Copy link
Copy Markdown
Member

Two latent fixes that are prerequisites for the Wippy runtime traceback work in wippyai/runtime#448.

xpcall: catch direct-call errors without corrupting later calls

xpcall leaked its error and never invoked the message handler outside a coroutine (for example under DoString/PCall). It depended on threadRun's recovery boundary, which only handles the coroutine path.

baseXPCall now catches synchronous errors inline and invokes the message handler before unwinding, while throw-site frames remain inspectable by debug.getlocal/debug.traceback.

The protected-call unwind is also centralized: discarded frame extensions are cleared before their stack indexes can be reused. Without that cleanup, a failed direct xpcall left its CallK continuation and handler behind; a subsequent Go-backed Lua call at the same depth could execute the stale continuation instead of its own function. The same invariant now covers Lua pcall and API PCall unwinds.

Regression coverage includes:

  • direct xpcall catch, success, nesting, and coroutine behavior;
  • a normal Go-backed call immediately after a failed xpcall;
  • an error raised by the xpcall message handler;
  • nested CallK frames discarded by Lua pcall and API PCall;
  • frame-extension cleanup after recovery.

Inspect: capture upvalues

GetStackFrame never reported upvalues because it read funcTable["f"], while GetInfo's f flag returns the *LFunction directly. The returned function is now used for Go-function name resolution and the upvalue walk.

CI repair

The workflow previously failed validation before creating any jobs because runner.temp was referenced at job scope. Cache environment variables now live on execution steps, where the runner context is valid. The existing staticcheck/unused findings were also removed so the restored lint job has a clean baseline.

Verification

  • go test ./... -count=1
  • go test -race ./... -count=1
  • targeted protected-call and yield regressions under the race detector
  • golangci-lint v2.8.0: 0 issues
  • actionlint v1.7.12: clean
  • git diff --check

This PR does not tag or publish a go-lua release.

skhaz added 2 commits July 7, 2026 15:01
Why:
xpcall leaked its error and never invoked the message handler when invoked
outside a coroutine (e.g. under DoString/PCall), aborting the surrounding
chunk. basePCall was updated to wrap CallK in its own defer/recover, but
baseXPCall was left depending on threadRun's recover via handleProtectedError
— a layer that only exists inside coroutines.

What:
- Wrap baseXPCall's CallK in an inline defer/recover, mirroring basePCall.
- Invoke the error handler BEFORE resetting the stack so it can inspect the
  throw site (debug.getlocal / debug.traceback), matching standard Lua
  semantics (PCall's own errfunc path does the same).
- Add errorObjectFromRecover and invokeErrorHandler helpers.
- Add xpcall_direct_test.go covering direct-call catch, chunk preservation,
  success path, nesting under pcall, and the still-working coroutine path.
Why:
GetStackFrame never reported upvalues: it read funcTable["f"] for the frame
function, but GetInfo's 'f' flag RETURNS the *LFunction (it does not store it
under any table key). The funcTable was always empty, so the upvalue loop was
skipped and debug.getupvalue kept disagreeing with inspect.

What:
- Capture the *LFunction from GetInfo's return value and use it for both the
  Go-function name resolution and the upvalue walk.
- Drop the unused funcTable allocation.
- Add TestGetStackFrameCapturesUpvalues guarding against regression.
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.

2 participants