Created
March 29, 2021 11:52
-
-
Save FrankReh/1d6d6473a315ef4c995807c42c3aeeef to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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