Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

emscripten: explicitly checks if an api.Function is not nil #1623

Merged
merged 2 commits into from
Aug 9, 2023
Merged
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
28 changes: 19 additions & 9 deletions internal/emscripten/emscripten.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package emscripten
import (
"context"
"errors"
"fmt"
"strconv"

"github.com/tetratelabs/wazero/api"
Expand Down Expand Up @@ -129,10 +130,7 @@ func (v *InvokeFunc) Call(ctx context.Context, mod api.Module, stack []uint64) {
// We reuse savedStack to save allocations. We allocate with a size of 2
// here to accommodate for the input and output of setThrew.
var savedStack [2]uint64
err = mod.ExportedFunction("stackSave").CallWithStack(ctx, savedStack[:])
if err != nil {
panic(err)
}
callOrPanic(ctx, mod, "stackSave", savedStack[:])

err = f.CallWithStack(ctx, stack)
if err != nil {
Expand All @@ -143,9 +141,7 @@ func (v *InvokeFunc) Call(ctx context.Context, mod api.Module, stack []uint64) {

// This is the equivalent of "stackRestore(sp);".
// Do not overwrite err here to preserve the original error.
if err := mod.ExportedFunction("stackRestore").CallWithStack(ctx, savedStack[:]); err != nil {
panic(err)
}
callOrPanic(ctx, mod, "stackRestore", savedStack[:])

// If we encounter ThrowLongjmpError, this means that the C code did a
// longjmp, which in turn called _emscripten_throw_longjmp and that is
Expand All @@ -163,9 +159,23 @@ func (v *InvokeFunc) Call(ctx context.Context, mod api.Module, stack []uint64) {
// This is the equivalent of "_setThrew(1, 0);".
savedStack[0] = 1
savedStack[1] = 0
err = mod.ExportedFunction("setThrew").CallWithStack(ctx, savedStack[:])
callOrPanic(ctx, mod, "setThrew", savedStack[:])
}
}

// maybeCallOrPanic calls a given function if it is exported, otherwise panics.
//
// This ensures if the given name is exported before calling it. In other words, this explicitly checks if an api.Function
// returned by api.Module.ExportedFunction is not nil. This is necessary because directly calling a method which is
// potentially nil interface can be fatal on some platforms due to a bug? in Go/QEMU.
// See https://github.com/tetratelabs/wazero/issues/1621
func callOrPanic(ctx context.Context, m api.Module, name string, stack []uint64) {
if f := m.ExportedFunction(name); f != nil {
err := f.CallWithStack(ctx, stack)
if err != nil {
panic(err) // setThrew failed
panic(err)
}
} else {
panic(fmt.Sprintf("%s not exported", name))
}
}
26 changes: 26 additions & 0 deletions internal/emscripten/emscripten_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package emscripten

import (
"context"
"testing"

"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental/wazerotest"
"github.com/tetratelabs/wazero/internal/testing/require"
)

func Test_callOnPanic(t *testing.T) {
const exists = "f"
var called bool
f := wazerotest.NewFunction(func(context.Context, api.Module) { called = true })
f.ExportNames = []string{exists}
m := wazerotest.NewModule(nil, f)
t.Run("exists", func(t *testing.T) {
callOrPanic(context.Background(), m, exists, nil)
require.True(t, called)
})
t.Run("not exist", func(t *testing.T) {
err := require.CapturePanic(func() { callOrPanic(context.Background(), m, "not-exist", nil) })
require.EqualError(t, err, "not-exist not exported")
})
}