Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion lua.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,10 @@ func (l *State) ProtectedCallWithContinuation(argCount, resultCount, errorFuncti
l.checkResults(argCount, resultCount)
if errorFunction != 0 {
apiCheckStackIndex(errorFunction, l.indexToValue(errorFunction))
errorFunction = l.AbsIndex(errorFunction)
// The error handler is addressed relative to this frame, but it is
// called later from whichever frame raises the error, so remember its
// absolute stack position.
errorFunction = l.callInfo.function + l.AbsIndex(errorFunction)
}

f := l.top - (argCount + 1)
Expand Down
42 changes: 42 additions & 0 deletions xpcall_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package lua

import "testing"

// TestXPCallInvokesErrorHandler checks that xpcall calls the error handler
// passed as its second argument. The handler lives in the caller's frame, so
// the VM has to remember its absolute stack position while the protected
// function runs.
func TestXPCallInvokesErrorHandler(t *testing.T) {
l := NewState()
OpenLibraries(l)
// Put a non-callable value in the first stack slot. A stale frame-relative
// error-handler index resolves to this slot, so the bug surfaces as
// "error in error handling" instead of an endless recursion into the
// outermost chunk.
l.PushNumber(0)
err := DoString(l, `
local ok, msg = xpcall(function() error("boom") end,
function(m) return "handled: " .. m end)
assert(ok == false, "xpcall reported success for a failing function")
assert(msg:sub(1, 9) == "handled: ", "handler result: " .. tostring(msg))
assert(msg:sub(-4) == "boom", "handler result: " .. tostring(msg))
`)
if err != nil {
t.Fatal(err)
}
}

// TestXPCallReturnsResults checks that a successful xpcall still returns the
// protected function's results and true.
func TestXPCallReturnsResults(t *testing.T) {
l := NewState()
OpenLibraries(l)
err := DoString(l, `
local ok, a, b = xpcall(function() return 1, 2 end, function() return "unused" end)
assert(ok == true, "xpcall reported failure for a successful function")
assert(a == 1 and b == 2, "results: " .. tostring(a) .. ", " .. tostring(b))
`)
if err != nil {
t.Fatal(err)
}
}
Loading