Skip to content

Instantly share code, notes, and snippets.

@FrankReh
Created March 29, 2021 11:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save FrankReh/1d6d6473a315ef4c995807c42c3aeeef to your computer and use it in GitHub Desktop.
Save FrankReh/1d6d6473a315ef4c995807c42c3aeeef to your computer and use it in GitHub Desktop.
// Test case that perhaps shows a miscompile.
//
// % tinygo test -target=wasm
// panic: runtime error: nil pointer dereference
//
// To get a working compile:
//
// % tinygo test -opt=1 -target=wasm
//
// There are at least three trivial things one can do to this code to also get
// a good compile: labeled f1, f2 and f3 below.
//
// To help demonstrate the difference between a good run and a broken run,
// I've left some println s in the code. They can be removed without changing
// the working or broken nature of the build.
/*
% go version
go version go1.16.2 darwin/amd64
% tinygo version
tinygo version 0.17.0 darwin/amd64 (using go version go1.16.2 and LLVM version 11.0.0)
% tinygo test -opt=1 -target=wasm
times A 0 3 f == nil false
fn 1
fn 2
fn 3
times B 0 3
times A 1 3 f == nil false
fn 1
fn 2
fn 3
times B 1 3
times A 2 3 f == nil false
fn 1
fn 2
fn 3
times B 2 3
finished
% tinygo test -target=wasm
times A 0 3 f == nil false
fn 1
fn 2
fn 3
times B 0 3
times A 1 3 f == nil false
fn 1
panic: runtime error: nil pointer dereference
wasm://wasm/00052c52:1
RuntimeError: unreachable
at runtime.runtimePanic (<anonymous>:wasm-function[19]:0x93a)
at runtime.nilPanic (<anonymous>:wasm-function[25]:0xdc1)
at main.awaitOnPromise3Times$1 (<anonymous>:wasm-function[101]:0x648d)
at main.times.resume (<anonymous>:wasm-function[105]:0x65ed)
at runtime.scheduler (<anonymous>:wasm-function[47]:0x1c42)
at resume (<anonymous>:wasm-function[64]:0x2866)
at global.Go._resume (/Users/frank/x/targets/wasm_exec.js:492:23)
at /Users/frank/x/targets/wasm_exec.js:503:8
*/
package main
import (
"errors"
"syscall/js"
)
func main() {
awaitOnPromise3Times()
println("finished")
}
func awaitOnPromise3Times() {
s1 := "some string"
s2 := "some string"
_, _ = s1, s2
fn := func() {
println("fn 1")
_ = s1 // f1: comment out this line (this appears to be the line causing the panic the second time through the times loop)
println("fn 2")
_ = s2 // f2: or comment out this line
println("fn 3")
_, _ = await()
}
times(3, fn)
// fn() // f3: by adding this before or after the call to times, the times loop then succeeds.
}
// Call function f n times.
func times(n int, f func()) {
// These printlns aren't necessary to reproduce the problem but they show how far the loop gets.
for i := 0; i < n; i++ {
println("times A", i, n, "f == nil", (f == nil))
f()
println("times B", i, n)
}
}
// await calls 'then' on the promise with one closure (to keep the test smaller), one for success and
// one for failure and blocks on a channel until one or the other is fired by
// the promise; then it returns a js.Value or an error, the success or failure
// of the Promise.
func await() (js.Value, error) {
type callResult struct {
val js.Value
err error
}
c := make(chan callResult, 1)
success := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
var res callResult
if len(args) != 1 {
println("bug: success: len(args) != 1", len(args))
res.err = errors.New("wrong number of args to success callback")
} else {
res.val = args[0]
}
c <- res
return nil
})
// the failure js.Func has been elided for the sake of brevity.
promise := js.Global().Get("Promise").Call("resolve", "The success case") // js: promise = Promise.resolve("The success case")
_ = promise.Call("then", success) // js: promise.then(success)
// Block until JavaScript has resolved the promise. This is were it gets interesting.
res := <-c
// Don't release the function, to keep the test case as simple as possible.
// success.Release() // Normally a js.Func is released when no longer needed.
return res.val, res.err
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment