From 2bcaad46e11cd25dbe3b3c1bd0851862b627edad Mon Sep 17 00:00:00 2001 From: Barak Amar Date: Tue, 25 Aug 2026 11:51:42 +0300 Subject: [PATCH] Convert non-error panics to errors in protect protect() asserted that every recovered value is an error, but the VM panics with plain strings in many places (panic("unreachable"), panic("XMove not implemented yet"), the "expected opcode" invariants in the bytecode interpreter). Any of those turned into a second panic - "interface conversion: string is not error" - crashing the host and losing the original message. Pass real errors through unchanged and wrap anything else with fmt.Errorf, so a protected call always returns an error. --- stack.go | 10 ++++++++-- vm_test.go | 9 +++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/stack.go b/stack.go index 2570a54..3f45799 100644 --- a/stack.go +++ b/stack.go @@ -1,6 +1,9 @@ package lua -import "log" +import ( + "fmt" + "log" +) func (l *State) push(v value) { l.stack[l.top] = v @@ -406,7 +409,10 @@ func (l *State) protect(f func()) (err error) { nestedGoCallCount, protectFunction := l.nestedGoCallCount, l.protectFunction l.protectFunction = func() { if e := recover(); e != nil { - err = e.(error) + var ok bool + if err, ok = e.(error); !ok { + err = fmt.Errorf("%v", e) + } l.nestedGoCallCount, l.protectFunction = nestedGoCallCount, protectFunction } } diff --git a/vm_test.go b/vm_test.go index e78570c..df385ba 100644 --- a/vm_test.go +++ b/vm_test.go @@ -351,6 +351,15 @@ func TestErrorf(t *testing.T) { } } +func TestPanicWithString(t *testing.T) { + l := NewState() + OpenLibraries(l) + LoadString(l, "debug.gethook()") + if err := l.ProtectedCall(0, 0, 0); err == nil { + t.Fatal("expected an error, got none") + } +} + func TestPairsSplit(t *testing.T) { testString(t, ` local t = {}