Skip to content

Commit

Permalink
Panics caller on exit error
Browse files Browse the repository at this point in the history
This changes the AssemblyScript abort handler and WASI proc_exit
implementation to panic the caller which eventually invoked close.

This ensures no code executes afterwards, For example, LLVM inserts
unreachable instructions after calls to exit.

See emscripten-core/emscripten#12322
See #601

Signed-off-by: Adrian Cole <adrian@tetrate.io>
  • Loading branch information
Adrian Cole committed Jul 5, 2022
1 parent bd1f742 commit 28f8be0
Show file tree
Hide file tree
Showing 11 changed files with 50 additions and 18 deletions.
12 changes: 11 additions & 1 deletion assemblyscript/assemblyscript.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/ieee754"
"github.com/tetratelabs/wazero/internal/wasm"
"github.com/tetratelabs/wazero/sys"
)

// Instantiate instantiates the "env" module used by AssemblyScript into the runtime default namespace.
Expand Down Expand Up @@ -166,7 +167,16 @@ func (a *assemblyscript) abort(
}
_, _ = fmt.Fprintf(sysCtx.Stderr(), "%s at %s:%d:%d\n", msg, fn, lineNumber, columnNumber)
}
_ = mod.CloseWithExitCode(ctx, 255)

// AssemblyScript expects the exit code to be 255
// See https://github.com/AssemblyScript/assemblyscript/blob/v0.20.13/tests/compiler/wasi/abort.js#L14
exitCode := uint32(255)

// Ensure other callers see the exit code.
_ = mod.CloseWithExitCode(ctx, exitCode)

// Prevent any code from executing after this function.
panic(sys.NewExitError(mod.Name(), exitCode))
}

// trace implements the same named function in AssemblyScript (ex. trace('Hello World!'))
Expand Down
Binary file modified examples/allocation/tinygo/testdata/greet.wasm
Binary file not shown.
9 changes: 7 additions & 2 deletions examples/wasi/cat.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"os"

"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/sys"
"github.com/tetratelabs/wazero/wasi_snapshot_preview1"
)

Expand Down Expand Up @@ -55,8 +56,12 @@ func main() {
}

// InstantiateModule runs the "_start" function which is what TinyGo compiles "main" to.
// * Set the program name (arg[0]) to "wasi" and add args to write "/test.txt" to stdout twice.
// * Set the program name (arg[0]) to "wasi" and add args to write "/test.txt" to stdout.
if _, err = r.InstantiateModule(ctx, code, config.WithArgs("wasi", os.Args[1])); err != nil {
log.Panicln(err)
if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() == 0 {
// We expect the exit code to be zero, so panic on anything else.
} else {
log.Panicln(err)
}
}
}
3 changes: 2 additions & 1 deletion examples/wasi/cat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
//
// go run cat.go /test.txt
func Test_main(t *testing.T) {
stdout, _ := maintester.TestMain(t, main, "cat", "/test.txt")
stdout, stderr := maintester.TestMain(t, main, "cat", "/test.txt")
require.Equal(t, "", stderr)
require.Equal(t, "greet filesystem\n", stdout)
}
8 changes: 4 additions & 4 deletions examples/wasi/testdata/cat.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package main

import (
"io/ioutil"
"os"
)

// main is the same as wasi: "concatenate and print files."
// main runs cat: concatenate and print files.
//
// Note: main becomes WASI's "_start" function.
func main() {
// Start at arg[1] because args[0] is the program name.
for i := 1; i < len(os.Args); i++ {
// Intentionally use ioutil.ReadFile instead of os.ReadFile for TinyGo.
bytes, err := ioutil.ReadFile(os.Args[i])
bytes, err := os.ReadFile(os.Args[i])
if err != nil {
os.Exit(1)
}
Expand Down
Binary file modified examples/wasi/testdata/cat.wasm
Binary file not shown.
1 change: 0 additions & 1 deletion internal/engine/compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,6 @@ type compiler interface {
// compileV128Add adds instructions to perform wazeroir.OperationV128Add.
compileV128Add(o *wazeroir.OperationV128Add) error
// compileV128Sub adds instructions to perform wazeroir.OperationV128Sub.
// compileV128Sub adds instructions to perform wazeroir.OperationV128Sub.
compileV128Sub(o *wazeroir.OperationV128Sub) error
// compileV128Load adds instructions to perform wazeroir.OperationV128Load.
compileV128Load(o *wazeroir.OperationV128Load) error
Expand Down
15 changes: 10 additions & 5 deletions internal/wasm/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import (
experimentalapi "github.com/tetratelabs/wazero/experimental"
"github.com/tetratelabs/wazero/internal/ieee754"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/sys"
internalsys "github.com/tetratelabs/wazero/internal/sys"
"github.com/tetratelabs/wazero/sys"
)

type (
Expand Down Expand Up @@ -345,7 +346,7 @@ func (s *Store) Instantiate(
ns *Namespace,
module *Module,
name string,
sys *sys.Context,
sys *internalsys.Context,
functionListenerFactory experimentalapi.FunctionListenerFactory,
) (*CallContext, error) {
if ctx == nil {
Expand Down Expand Up @@ -386,7 +387,7 @@ func (s *Store) instantiate(
ns *Namespace,
module *Module,
name string,
sys *sys.Context,
sysCtx *internalsys.Context,
functionListenerFactory experimentalapi.FunctionListenerFactory,
modules map[string]*ModuleInstance,
) (*CallContext, error) {
Expand Down Expand Up @@ -451,13 +452,17 @@ func (s *Store) instantiate(
}

// Compile the default context for calls to this module.
m.CallCtx = NewCallContext(ns, m, sys)
m.CallCtx = NewCallContext(ns, m, sysCtx)

// Execute the start function.
if module.StartSection != nil {
funcIdx := *module.StartSection
f := m.Functions[funcIdx]
if _, err = f.Module.Engine.Call(ctx, m.CallCtx, f); err != nil {
_, err = f.Module.Engine.Call(ctx, m.CallCtx, f)

if exitErr, ok := err.(*sys.ExitError); ok { // Don't wrap an exit error!
return nil, exitErr
} else if err != nil {
return nil, fmt.Errorf("start %s failed: %w", module.funcDesc(funcSection, funcIdx), err)
}
}
Expand Down
5 changes: 5 additions & 0 deletions internal/wasmdebug/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/buildoptions"
"github.com/tetratelabs/wazero/internal/wasmruntime"
"github.com/tetratelabs/wazero/sys"
)

// FuncName returns the naming convention of "moduleName.funcName".
Expand Down Expand Up @@ -115,6 +116,10 @@ func (s *stackTrace) FromRecovered(recovered interface{}) error {
debug.PrintStack()
}

if exitErr, ok := recovered.(*sys.ExitError); ok { // Don't wrap an exit error!
return exitErr
}

stack := strings.Join(s.frames, "\n\t")

// If the error was internal, don't mention it was recovered.
Expand Down
2 changes: 1 addition & 1 deletion sys/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
//
// Here's an example of how to get the exit code:
// main := module.ExportedFunction("main")
// if err := main(nil); err != nil {
// if err := main(ctx); err != nil {
// if exitErr, ok := err.(*sys.ExitError); ok {
// // If your main function expects to exit, this could be ok if Code == 0
// }
Expand Down
13 changes: 10 additions & 3 deletions wasi_snapshot_preview1/wasi.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ import (

"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/sys"
internalsys "github.com/tetratelabs/wazero/internal/sys"
"github.com/tetratelabs/wazero/internal/wasm"
"github.com/tetratelabs/wazero/sys"
)

// ModuleName is the module name WASI functions are exported into.
Expand Down Expand Up @@ -892,7 +893,7 @@ func (a *wasi) FdPwrite(ctx context.Context, mod api.Module, fd, iovs, iovsCount
// See https://linux.die.net/man/3/readv
func (a *wasi) FdRead(ctx context.Context, mod api.Module, fd, iovs, iovsCount, resultSize uint32) Errno {
sysCtx := mod.(*wasm.CallContext).Sys
reader := sys.FdReader(ctx, sysCtx, fd)
reader := internalsys.FdReader(ctx, sysCtx, fd)
if reader == nil {
return ErrnoBadf
}
Expand Down Expand Up @@ -1054,7 +1055,7 @@ func (a *wasi) FdTell(ctx context.Context, mod api.Module, fd, resultOffset uint
// See https://linux.die.net/man/3/writev
func (a *wasi) FdWrite(ctx context.Context, mod api.Module, fd, iovs, iovsCount, resultSize uint32) Errno {
sysCtx := mod.(*wasm.CallContext).Sys
writer := sys.FdWriter(ctx, sysCtx, fd)
writer := internalsys.FdWriter(ctx, sysCtx, fd)
if writer == nil {
return ErrnoBadf
}
Expand Down Expand Up @@ -1215,7 +1216,13 @@ func (a *wasi) PathUnlinkFile(ctx context.Context, mod api.Module, fd, path, pat
// Note: importProcExit shows this signature in the WebAssembly 1.0 Text Format.
// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#proc_exit
func (a *wasi) ProcExit(ctx context.Context, mod api.Module, exitCode uint32) {
// Ensure other callers see the exit code.
_ = mod.CloseWithExitCode(ctx, exitCode)

// Prevent any code from executing after this function. For example, LLVM
// inserts unreachable instructions after calls to exit.
// See: https://github.com/emscripten-core/emscripten/issues/12322
panic(sys.NewExitError(mod.Name(), exitCode))
}

// ProcRaise is the WASI function named functionProcRaise
Expand Down

0 comments on commit 28f8be0

Please sign in to comment.