]> Cypherpunks.ru repositories - gostls13.git/commitdiff
syscall/js: replace TypedArrayOf with CopyBytesToGo/CopyBytesToJS
authorRichard Musiol <mail@richard-musiol.de>
Wed, 15 May 2019 23:03:10 +0000 (01:03 +0200)
committerRichard Musiol <neelance@gmail.com>
Fri, 24 May 2019 09:38:37 +0000 (09:38 +0000)
The typed arrays returned by TypedArrayOf were backed by WebAssembly
memory. They became invalid each time we grow the WebAssembly memory.
This made them very error prone and hard to use correctly.

This change removes TypedArrayOf completely and instead introduces
CopyBytesToGo and CopyBytesToJS for copying bytes between a byte
slice and an Uint8Array. This breaking change is still allowed for
the syscall/js package.

Fixes #31980.
Fixes #31812.

Change-Id: I14c76fdd60b48dd517c1593972a56d04965cb272
Reviewed-on: https://go-review.googlesource.com/c/go/+/177537
Run-TryBot: Richard Musiol <neelance@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
misc/wasm/wasm_exec.js
src/crypto/cipher/xor_test.go
src/crypto/rand/rand_js.go
src/net/http/roundtrip_js.go
src/syscall/fs_js.go
src/syscall/js/js.go
src/syscall/js/js_js.s
src/syscall/js/js_test.go
src/syscall/js/typedarray.go [deleted file]

index a1d88e6eac169240c14eb9bea734fc83c4b6b8d0..a54bb9a95d45988bd02c8d1161fd611338ffa274 100644 (file)
                                                mem().setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16));
                                        },
 
+                                       // func copyBytesToGo(dst []byte, src ref) (int, bool)
+                                       "syscall/js.copyBytesToGo": (sp) => {
+                                               const dst = loadSlice(sp + 8);
+                                               const src = loadValue(sp + 32);
+                                               if (!(src instanceof Uint8Array)) {
+                                                       mem().setUint8(sp + 48, 0);
+                                                       return;
+                                               }
+                                               const toCopy = src.subarray(0, dst.length);
+                                               dst.set(toCopy);
+                                               setInt64(sp + 40, toCopy.length);
+                                               mem().setUint8(sp + 48, 1);
+                                       },
+
+                                       // func copyBytesToJS(dst ref, src []byte) (int, bool)
+                                       "syscall/js.copyBytesToJS": (sp) => {
+                                               const dst = loadValue(sp + 8);
+                                               const src = loadSlice(sp + 16);
+                                               if (!(dst instanceof Uint8Array)) {
+                                                       mem().setUint8(sp + 48, 0);
+                                                       return;
+                                               }
+                                               const toCopy = src.subarray(0, dst.length);
+                                               dst.set(toCopy);
+                                               setInt64(sp + 40, toCopy.length);
+                                               mem().setUint8(sp + 48, 1);
+                                       },
+
                                        "debug": (value) => {
                                                console.log(value);
                                        },
                                true,
                                false,
                                global,
-                               this._inst.exports.mem,
                                this,
                        ];
                        this._refs = new Map();
index 40d4e5afa3039ed0cf64f506b2362dda7b7a9a8b..4f829e946190ffb0adb9fd35775992ea66aef85e 100644 (file)
@@ -9,16 +9,11 @@ import (
        "crypto/cipher"
        "crypto/rand"
        "fmt"
-       "internal/testenv"
        "io"
-       "runtime"
        "testing"
 )
 
 func TestXOR(t *testing.T) {
-       if runtime.GOOS == "js" {
-               testenv.SkipFlaky(t, 31812)
-       }
        for j := 1; j <= 1024; j++ {
                if testing.Short() && j > 16 {
                        break
index bb213963fd44f4727bfd681888dac726b6256d8a..7e939742ac657bbe389305a512676f39feceb243 100644 (file)
@@ -13,6 +13,7 @@ func init() {
 }
 
 var jsCrypto = js.Global().Get("crypto")
+var uint8Array = js.Global().Get("Uint8Array")
 
 // reader implements a pseudorandom generator
 // using JavaScript crypto.getRandomValues method.
@@ -20,8 +21,8 @@ var jsCrypto = js.Global().Get("crypto")
 type reader struct{}
 
 func (r *reader) Read(b []byte) (int, error) {
-       a := js.TypedArrayOf(b)
+       a := uint8Array.New(len(b))
        jsCrypto.Call("getRandomValues", a)
-       a.Release()
+       js.CopyBytesToGo(b, a)
        return len(b), nil
 }
index 7d965f844fb725eb3c0992f4c454bd94143cef26..9a4c369d6674029657851b32253727ac1a6e7772 100644 (file)
@@ -17,6 +17,8 @@ import (
        "syscall/js"
 )
 
+var uint8Array = js.Global().Get("Uint8Array")
+
 // jsFetchMode is a Request.Header map key that, if present,
 // signals that the map entry is actually an option to the Fetch API mode setting.
 // Valid values are: "cors", "no-cors", "same-origin", "navigate"
@@ -96,9 +98,9 @@ func (t *Transport) RoundTrip(req *Request) (*Response, error) {
                        return nil, err
                }
                req.Body.Close()
-               a := js.TypedArrayOf(body)
-               defer a.Release()
-               opt.Set("body", a)
+               buf := uint8Array.New(len(body))
+               js.CopyBytesToJS(buf, body)
+               opt.Set("body", buf)
        }
        respPromise := js.Global().Call("fetch", req.URL.String(), opt)
        var (
@@ -210,9 +212,7 @@ func (r *streamReader) Read(p []byte) (n int, err error) {
                                return nil
                        }
                        value := make([]byte, result.Get("value").Get("byteLength").Int())
-                       a := js.TypedArrayOf(value)
-                       a.Call("set", result.Get("value"))
-                       a.Release()
+                       js.CopyBytesToGo(value, result.Get("value"))
                        bCh <- value
                        return nil
                })
@@ -273,11 +273,9 @@ func (r *arrayReader) Read(p []byte) (n int, err error) {
                )
                success := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
                        // Wrap the input ArrayBuffer with a Uint8Array
-                       uint8arrayWrapper := js.Global().Get("Uint8Array").New(args[0])
+                       uint8arrayWrapper := uint8Array.New(args[0])
                        value := make([]byte, uint8arrayWrapper.Get("byteLength").Int())
-                       a := js.TypedArrayOf(value)
-                       a.Call("set", uint8arrayWrapper)
-                       a.Release()
+                       js.CopyBytesToGo(value, uint8arrayWrapper)
                        bCh <- value
                        return nil
                })
index 3c2dac3579a7c42958944921d5fdad6f779bb163..1b835c50484033a55f06a53cfd55905dd9797fd0 100644 (file)
@@ -19,6 +19,7 @@ func now() (sec int64, nsec int32)
 var jsProcess = js.Global().Get("process")
 var jsFS = js.Global().Get("fs")
 var constants = jsFS.Get("constants")
+
 var uint8Array = js.Global().Get("Uint8Array")
 
 var (
@@ -384,10 +385,7 @@ func Read(fd int, b []byte) (int, error) {
        if err != nil {
                return 0, err
        }
-
-       a := js.TypedArrayOf(b)
-       a.Call("set", buf)
-       a.Release()
+       js.CopyBytesToGo(b, buf)
 
        n2 := n.Int()
        f.pos += int64(n2)
@@ -406,11 +404,8 @@ func Write(fd int, b []byte) (int, error) {
                return n, err
        }
 
-       a := js.TypedArrayOf(b)
        buf := uint8Array.New(len(b))
-       buf.Call("set", a)
-       a.Release()
-
+       js.CopyBytesToJS(buf, b)
        n, err := fsCall("write", fd, buf, 0, len(b), nil)
        if err != nil {
                return 0, err
@@ -426,20 +421,13 @@ func Pread(fd int, b []byte, offset int64) (int, error) {
        if err != nil {
                return 0, err
        }
-
-       a := js.TypedArrayOf(b)
-       a.Call("set", buf)
-       a.Release()
-
+       js.CopyBytesToGo(b, buf)
        return n.Int(), nil
 }
 
 func Pwrite(fd int, b []byte, offset int64) (int, error) {
-       a := js.TypedArrayOf(b)
        buf := uint8Array.New(len(b))
-       buf.Call("set", a)
-       a.Release()
-
+       js.CopyBytesToJS(buf, b)
        n, err := fsCall("write", fd, buf, 0, len(b), offset)
        if err != nil {
                return 0, err
index 0acc7da9bfdea8045e1c90067c8cae64cd1d7965..ee7fbe1aed1c8875e1cd7d6725de8b3aef09e57f 100644 (file)
@@ -79,8 +79,7 @@ var (
        valueTrue      = predefValue(3)
        valueFalse     = predefValue(4)
        valueGlobal    = predefValue(5)
-       memory         = predefValue(6) // WebAssembly linear memory
-       jsGo           = predefValue(7) // instance of the Go class in JavaScript
+       jsGo           = predefValue(6) // instance of the Go class in JavaScript
 
        objectConstructor = valueGlobal.Get("Object")
        arrayConstructor  = valueGlobal.Get("Array")
@@ -478,3 +477,29 @@ type ValueError struct {
 func (e *ValueError) Error() string {
        return "syscall/js: call of " + e.Method + " on " + e.Type.String()
 }
+
+// CopyBytesToGo copies bytes from the Uint8Array src to dst.
+// It returns the number of bytes copied, which will be the minimum of the lengths of src and dst.
+// CopyBytesToGo panics if src is not an Uint8Array.
+func CopyBytesToGo(dst []byte, src Value) int {
+       n, ok := copyBytesToGo(dst, src.ref)
+       if !ok {
+               panic("syscall/js: CopyBytesToGo: expected src to be an Uint8Array")
+       }
+       return n
+}
+
+func copyBytesToGo(dst []byte, src ref) (int, bool)
+
+// CopyBytesToJS copies bytes from src to the Uint8Array dst.
+// It returns the number of bytes copied, which will be the minimum of the lengths of src and dst.
+// CopyBytesToJS panics if dst is not an Uint8Array.
+func CopyBytesToJS(dst Value, src []byte) int {
+       n, ok := copyBytesToJS(dst.ref, src)
+       if !ok {
+               panic("syscall/js: CopyBytesToJS: expected dst to be an Uint8Array")
+       }
+       return n
+}
+
+func copyBytesToJS(dst ref, src []byte) (int, bool)
index 0ec164d5cbc1f2590b5a03c09c0944bd8984a723..5f294682378a03e0396028de07577cac1f9ef993 100644 (file)
@@ -51,3 +51,11 @@ TEXT ·valueLoadString(SB), NOSPLIT, $0
 TEXT ·valueInstanceOf(SB), NOSPLIT, $0
   CallImport
   RET
+
+TEXT ·copyBytesToGo(SB), NOSPLIT, $0
+  CallImport
+  RET
+
+TEXT ·copyBytesToJS(SB), NOSPLIT, $0
+  CallImport
+  RET
index 20ccac77790f082d20822aa5395c02b332aaa313..7a1e346f55f6ee10d04a2aad96797874135fd837 100644 (file)
@@ -167,28 +167,6 @@ func TestFrozenObject(t *testing.T) {
        }
 }
 
-func TestTypedArrayOf(t *testing.T) {
-       testTypedArrayOf(t, "[]int8", []int8{0, -42, 0}, -42)
-       testTypedArrayOf(t, "[]int16", []int16{0, -42, 0}, -42)
-       testTypedArrayOf(t, "[]int32", []int32{0, -42, 0}, -42)
-       testTypedArrayOf(t, "[]uint8", []uint8{0, 42, 0}, 42)
-       testTypedArrayOf(t, "[]uint16", []uint16{0, 42, 0}, 42)
-       testTypedArrayOf(t, "[]uint32", []uint32{0, 42, 0}, 42)
-       testTypedArrayOf(t, "[]float32", []float32{0, -42.5, 0}, -42.5)
-       testTypedArrayOf(t, "[]float64", []float64{0, -42.5, 0}, -42.5)
-}
-
-func testTypedArrayOf(t *testing.T, name string, slice interface{}, want float64) {
-       t.Run(name, func(t *testing.T) {
-               a := js.TypedArrayOf(slice)
-               got := a.Index(1).Float()
-               a.Release()
-               if got != want {
-                       t.Errorf("got %#v, want %#v", got, want)
-               }
-       })
-}
-
 func TestNaN(t *testing.T) {
        want := js.ValueOf(math.NaN())
        got := dummys.Get("NaN")
@@ -454,3 +432,55 @@ func expectPanic(t *testing.T, fn func()) {
        }()
        fn()
 }
+
+var copyTests = []struct {
+       srcLen  int
+       dstLen  int
+       copyLen int
+}{
+       {5, 3, 3},
+       {3, 5, 3},
+       {0, 0, 0},
+}
+
+func TestCopyBytesToGo(t *testing.T) {
+       for _, tt := range copyTests {
+               t.Run(fmt.Sprintf("%d-to-%d", tt.srcLen, tt.dstLen), func(t *testing.T) {
+                       src := js.Global().Get("Uint8Array").New(tt.srcLen)
+                       if tt.srcLen >= 2 {
+                               src.SetIndex(1, 42)
+                       }
+                       dst := make([]byte, tt.dstLen)
+
+                       if got, want := js.CopyBytesToGo(dst, src), tt.copyLen; got != want {
+                               t.Errorf("copied %d, want %d", got, want)
+                       }
+                       if tt.dstLen >= 2 {
+                               if got, want := int(dst[1]), 42; got != want {
+                                       t.Errorf("got %d, want %d", got, want)
+                               }
+                       }
+               })
+       }
+}
+
+func TestCopyBytesToJS(t *testing.T) {
+       for _, tt := range copyTests {
+               t.Run(fmt.Sprintf("%d-to-%d", tt.srcLen, tt.dstLen), func(t *testing.T) {
+                       src := make([]byte, tt.srcLen)
+                       if tt.srcLen >= 2 {
+                               src[1] = 42
+                       }
+                       dst := js.Global().Get("Uint8Array").New(tt.dstLen)
+
+                       if got, want := js.CopyBytesToJS(dst, src), tt.copyLen; got != want {
+                               t.Errorf("copied %d, want %d", got, want)
+                       }
+                       if tt.dstLen >= 2 {
+                               if got, want := dst.Index(1).Int(), 42; got != want {
+                                       t.Errorf("got %d, want %d", got, want)
+                               }
+                       }
+               })
+       }
+}
diff --git a/src/syscall/js/typedarray.go b/src/syscall/js/typedarray.go
deleted file mode 100644 (file)
index 04c0057..0000000
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright 2018 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build js,wasm
-
-package js
-
-import (
-       "sync"
-       "unsafe"
-)
-
-var (
-       int8Array    = Global().Get("Int8Array")
-       int16Array   = Global().Get("Int16Array")
-       int32Array   = Global().Get("Int32Array")
-       uint8Array   = Global().Get("Uint8Array")
-       uint16Array  = Global().Get("Uint16Array")
-       uint32Array  = Global().Get("Uint32Array")
-       float32Array = Global().Get("Float32Array")
-       float64Array = Global().Get("Float64Array")
-)
-
-var _ Wrapper = TypedArray{} // TypedArray must implement Wrapper
-
-// TypedArray represents a JavaScript typed array.
-//
-// BUG(neelance): The typed array currently becomes inaccessible when Go requests more memory
-// from the WebAssembly host. It is recommended to only use the typed array synchronously
-// without keeping a long-lived reference. You can also check if the length property is zero
-// to detect this detached state of the typed array.
-type TypedArray struct {
-       Value
-}
-
-// Release frees up resources allocated for the typed array.
-// The typed array and its buffer must not be accessed after calling Release.
-func (a TypedArray) Release() {
-       openTypedArraysMutex.Lock()
-       delete(openTypedArrays, a)
-       openTypedArraysMutex.Unlock()
-}
-
-var (
-       openTypedArraysMutex sync.Mutex
-       openTypedArrays      = make(map[TypedArray]interface{})
-)
-
-// TypedArrayOf returns a JavaScript typed array backed by the slice's underlying array.
-//
-// The supported types are []int8, []int16, []int32, []uint8, []uint16, []uint32, []float32 and []float64.
-// Passing an unsupported value causes a panic.
-//
-// TypedArray.Release must be called to free up resources when the typed array will not be used any more.
-func TypedArrayOf(slice interface{}) TypedArray {
-       a := TypedArray{typedArrayOf(slice)}
-       openTypedArraysMutex.Lock()
-       openTypedArrays[a] = slice
-       openTypedArraysMutex.Unlock()
-       return a
-}
-
-func typedArrayOf(slice interface{}) Value {
-       switch slice := slice.(type) {
-       case []int8:
-               if len(slice) == 0 {
-                       return int8Array.New(memory.Get("buffer"), 0, 0)
-               }
-               return int8Array.New(memory.Get("buffer"), unsafe.Pointer(&slice[0]), len(slice))
-       case []int16:
-               if len(slice) == 0 {
-                       return int16Array.New(memory.Get("buffer"), 0, 0)
-               }
-               return int16Array.New(memory.Get("buffer"), unsafe.Pointer(&slice[0]), len(slice))
-       case []int32:
-               if len(slice) == 0 {
-                       return int32Array.New(memory.Get("buffer"), 0, 0)
-               }
-               return int32Array.New(memory.Get("buffer"), unsafe.Pointer(&slice[0]), len(slice))
-       case []uint8:
-               if len(slice) == 0 {
-                       return uint8Array.New(memory.Get("buffer"), 0, 0)
-               }
-               return uint8Array.New(memory.Get("buffer"), unsafe.Pointer(&slice[0]), len(slice))
-       case []uint16:
-               if len(slice) == 0 {
-                       return uint16Array.New(memory.Get("buffer"), 0, 0)
-               }
-               return uint16Array.New(memory.Get("buffer"), unsafe.Pointer(&slice[0]), len(slice))
-       case []uint32:
-               if len(slice) == 0 {
-                       return uint32Array.New(memory.Get("buffer"), 0, 0)
-               }
-               return uint32Array.New(memory.Get("buffer"), unsafe.Pointer(&slice[0]), len(slice))
-       case []float32:
-               if len(slice) == 0 {
-                       return float32Array.New(memory.Get("buffer"), 0, 0)
-               }
-               return float32Array.New(memory.Get("buffer"), unsafe.Pointer(&slice[0]), len(slice))
-       case []float64:
-               if len(slice) == 0 {
-                       return float64Array.New(memory.Get("buffer"), 0, 0)
-               }
-               return float64Array.New(memory.Get("buffer"), unsafe.Pointer(&slice[0]), len(slice))
-       default:
-               panic("TypedArrayOf: not a supported slice")
-       }
-}