Skip to content

Commit c468ad0

Browse files
neelanceRichard Musiol
authored andcommitted
syscall/js: replace TypedArrayOf with CopyBytesToGo/CopyBytesToJS
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 <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Cherry Zhang <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent 7e5bc47 commit c468ad0

File tree

9 files changed

+131
-168
lines changed

9 files changed

+131
-168
lines changed

misc/wasm/wasm_exec.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,34 @@
387387
mem().setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16));
388388
},
389389

390+
// func copyBytesToGo(dst []byte, src ref) (int, bool)
391+
"syscall/js.copyBytesToGo": (sp) => {
392+
const dst = loadSlice(sp + 8);
393+
const src = loadValue(sp + 32);
394+
if (!(src instanceof Uint8Array)) {
395+
mem().setUint8(sp + 48, 0);
396+
return;
397+
}
398+
const toCopy = src.subarray(0, dst.length);
399+
dst.set(toCopy);
400+
setInt64(sp + 40, toCopy.length);
401+
mem().setUint8(sp + 48, 1);
402+
},
403+
404+
// func copyBytesToJS(dst ref, src []byte) (int, bool)
405+
"syscall/js.copyBytesToJS": (sp) => {
406+
const dst = loadValue(sp + 8);
407+
const src = loadSlice(sp + 16);
408+
if (!(dst instanceof Uint8Array)) {
409+
mem().setUint8(sp + 48, 0);
410+
return;
411+
}
412+
const toCopy = src.subarray(0, dst.length);
413+
dst.set(toCopy);
414+
setInt64(sp + 40, toCopy.length);
415+
mem().setUint8(sp + 48, 1);
416+
},
417+
390418
"debug": (value) => {
391419
console.log(value);
392420
},
@@ -403,7 +431,6 @@
403431
true,
404432
false,
405433
global,
406-
this._inst.exports.mem,
407434
this,
408435
];
409436
this._refs = new Map();

src/crypto/cipher/xor_test.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,11 @@ import (
99
"crypto/cipher"
1010
"crypto/rand"
1111
"fmt"
12-
"internal/testenv"
1312
"io"
14-
"runtime"
1513
"testing"
1614
)
1715

1816
func TestXOR(t *testing.T) {
19-
if runtime.GOOS == "js" {
20-
testenv.SkipFlaky(t, 31812)
21-
}
2217
for j := 1; j <= 1024; j++ {
2318
if testing.Short() && j > 16 {
2419
break

src/crypto/rand/rand_js.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,16 @@ func init() {
1313
}
1414

1515
var jsCrypto = js.Global().Get("crypto")
16+
var uint8Array = js.Global().Get("Uint8Array")
1617

1718
// reader implements a pseudorandom generator
1819
// using JavaScript crypto.getRandomValues method.
1920
// See https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues.
2021
type reader struct{}
2122

2223
func (r *reader) Read(b []byte) (int, error) {
23-
a := js.TypedArrayOf(b)
24+
a := uint8Array.New(len(b))
2425
jsCrypto.Call("getRandomValues", a)
25-
a.Release()
26+
js.CopyBytesToGo(b, a)
2627
return len(b), nil
2728
}

src/net/http/roundtrip_js.go

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import (
1717
"syscall/js"
1818
)
1919

20+
var uint8Array = js.Global().Get("Uint8Array")
21+
2022
// jsFetchMode is a Request.Header map key that, if present,
2123
// signals that the map entry is actually an option to the Fetch API mode setting.
2224
// Valid values are: "cors", "no-cors", "same-origin", "navigate"
@@ -96,9 +98,9 @@ func (t *Transport) RoundTrip(req *Request) (*Response, error) {
9698
return nil, err
9799
}
98100
req.Body.Close()
99-
a := js.TypedArrayOf(body)
100-
defer a.Release()
101-
opt.Set("body", a)
101+
buf := uint8Array.New(len(body))
102+
js.CopyBytesToJS(buf, body)
103+
opt.Set("body", buf)
102104
}
103105
respPromise := js.Global().Call("fetch", req.URL.String(), opt)
104106
var (
@@ -210,9 +212,7 @@ func (r *streamReader) Read(p []byte) (n int, err error) {
210212
return nil
211213
}
212214
value := make([]byte, result.Get("value").Get("byteLength").Int())
213-
a := js.TypedArrayOf(value)
214-
a.Call("set", result.Get("value"))
215-
a.Release()
215+
js.CopyBytesToGo(value, result.Get("value"))
216216
bCh <- value
217217
return nil
218218
})
@@ -273,11 +273,9 @@ func (r *arrayReader) Read(p []byte) (n int, err error) {
273273
)
274274
success := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
275275
// Wrap the input ArrayBuffer with a Uint8Array
276-
uint8arrayWrapper := js.Global().Get("Uint8Array").New(args[0])
276+
uint8arrayWrapper := uint8Array.New(args[0])
277277
value := make([]byte, uint8arrayWrapper.Get("byteLength").Int())
278-
a := js.TypedArrayOf(value)
279-
a.Call("set", uint8arrayWrapper)
280-
a.Release()
278+
js.CopyBytesToGo(value, uint8arrayWrapper)
281279
bCh <- value
282280
return nil
283281
})

src/syscall/fs_js.go

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ func now() (sec int64, nsec int32)
1919
var jsProcess = js.Global().Get("process")
2020
var jsFS = js.Global().Get("fs")
2121
var constants = jsFS.Get("constants")
22+
2223
var uint8Array = js.Global().Get("Uint8Array")
2324

2425
var (
@@ -384,10 +385,7 @@ func Read(fd int, b []byte) (int, error) {
384385
if err != nil {
385386
return 0, err
386387
}
387-
388-
a := js.TypedArrayOf(b)
389-
a.Call("set", buf)
390-
a.Release()
388+
js.CopyBytesToGo(b, buf)
391389

392390
n2 := n.Int()
393391
f.pos += int64(n2)
@@ -406,11 +404,8 @@ func Write(fd int, b []byte) (int, error) {
406404
return n, err
407405
}
408406

409-
a := js.TypedArrayOf(b)
410407
buf := uint8Array.New(len(b))
411-
buf.Call("set", a)
412-
a.Release()
413-
408+
js.CopyBytesToJS(buf, b)
414409
n, err := fsCall("write", fd, buf, 0, len(b), nil)
415410
if err != nil {
416411
return 0, err
@@ -426,20 +421,13 @@ func Pread(fd int, b []byte, offset int64) (int, error) {
426421
if err != nil {
427422
return 0, err
428423
}
429-
430-
a := js.TypedArrayOf(b)
431-
a.Call("set", buf)
432-
a.Release()
433-
424+
js.CopyBytesToGo(b, buf)
434425
return n.Int(), nil
435426
}
436427

437428
func Pwrite(fd int, b []byte, offset int64) (int, error) {
438-
a := js.TypedArrayOf(b)
439429
buf := uint8Array.New(len(b))
440-
buf.Call("set", a)
441-
a.Release()
442-
430+
js.CopyBytesToJS(buf, b)
443431
n, err := fsCall("write", fd, buf, 0, len(b), offset)
444432
if err != nil {
445433
return 0, err

src/syscall/js/js.go

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,7 @@ var (
7979
valueTrue = predefValue(3)
8080
valueFalse = predefValue(4)
8181
valueGlobal = predefValue(5)
82-
memory = predefValue(6) // WebAssembly linear memory
83-
jsGo = predefValue(7) // instance of the Go class in JavaScript
82+
jsGo = predefValue(6) // instance of the Go class in JavaScript
8483

8584
objectConstructor = valueGlobal.Get("Object")
8685
arrayConstructor = valueGlobal.Get("Array")
@@ -478,3 +477,29 @@ type ValueError struct {
478477
func (e *ValueError) Error() string {
479478
return "syscall/js: call of " + e.Method + " on " + e.Type.String()
480479
}
480+
481+
// CopyBytesToGo copies bytes from the Uint8Array src to dst.
482+
// It returns the number of bytes copied, which will be the minimum of the lengths of src and dst.
483+
// CopyBytesToGo panics if src is not an Uint8Array.
484+
func CopyBytesToGo(dst []byte, src Value) int {
485+
n, ok := copyBytesToGo(dst, src.ref)
486+
if !ok {
487+
panic("syscall/js: CopyBytesToGo: expected src to be an Uint8Array")
488+
}
489+
return n
490+
}
491+
492+
func copyBytesToGo(dst []byte, src ref) (int, bool)
493+
494+
// CopyBytesToJS copies bytes from src to the Uint8Array dst.
495+
// It returns the number of bytes copied, which will be the minimum of the lengths of src and dst.
496+
// CopyBytesToJS panics if dst is not an Uint8Array.
497+
func CopyBytesToJS(dst Value, src []byte) int {
498+
n, ok := copyBytesToJS(dst.ref, src)
499+
if !ok {
500+
panic("syscall/js: CopyBytesToJS: expected dst to be an Uint8Array")
501+
}
502+
return n
503+
}
504+
505+
func copyBytesToJS(dst ref, src []byte) (int, bool)

src/syscall/js/js_js.s

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,11 @@ TEXT ·valueLoadString(SB), NOSPLIT, $0
5151
TEXT ·valueInstanceOf(SB), NOSPLIT, $0
5252
CallImport
5353
RET
54+
55+
TEXT ·copyBytesToGo(SB), NOSPLIT, $0
56+
CallImport
57+
RET
58+
59+
TEXT ·copyBytesToJS(SB), NOSPLIT, $0
60+
CallImport
61+
RET

src/syscall/js/js_test.go

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -167,28 +167,6 @@ func TestFrozenObject(t *testing.T) {
167167
}
168168
}
169169

170-
func TestTypedArrayOf(t *testing.T) {
171-
testTypedArrayOf(t, "[]int8", []int8{0, -42, 0}, -42)
172-
testTypedArrayOf(t, "[]int16", []int16{0, -42, 0}, -42)
173-
testTypedArrayOf(t, "[]int32", []int32{0, -42, 0}, -42)
174-
testTypedArrayOf(t, "[]uint8", []uint8{0, 42, 0}, 42)
175-
testTypedArrayOf(t, "[]uint16", []uint16{0, 42, 0}, 42)
176-
testTypedArrayOf(t, "[]uint32", []uint32{0, 42, 0}, 42)
177-
testTypedArrayOf(t, "[]float32", []float32{0, -42.5, 0}, -42.5)
178-
testTypedArrayOf(t, "[]float64", []float64{0, -42.5, 0}, -42.5)
179-
}
180-
181-
func testTypedArrayOf(t *testing.T, name string, slice interface{}, want float64) {
182-
t.Run(name, func(t *testing.T) {
183-
a := js.TypedArrayOf(slice)
184-
got := a.Index(1).Float()
185-
a.Release()
186-
if got != want {
187-
t.Errorf("got %#v, want %#v", got, want)
188-
}
189-
})
190-
}
191-
192170
func TestNaN(t *testing.T) {
193171
want := js.ValueOf(math.NaN())
194172
got := dummys.Get("NaN")
@@ -454,3 +432,55 @@ func expectPanic(t *testing.T, fn func()) {
454432
}()
455433
fn()
456434
}
435+
436+
var copyTests = []struct {
437+
srcLen int
438+
dstLen int
439+
copyLen int
440+
}{
441+
{5, 3, 3},
442+
{3, 5, 3},
443+
{0, 0, 0},
444+
}
445+
446+
func TestCopyBytesToGo(t *testing.T) {
447+
for _, tt := range copyTests {
448+
t.Run(fmt.Sprintf("%d-to-%d", tt.srcLen, tt.dstLen), func(t *testing.T) {
449+
src := js.Global().Get("Uint8Array").New(tt.srcLen)
450+
if tt.srcLen >= 2 {
451+
src.SetIndex(1, 42)
452+
}
453+
dst := make([]byte, tt.dstLen)
454+
455+
if got, want := js.CopyBytesToGo(dst, src), tt.copyLen; got != want {
456+
t.Errorf("copied %d, want %d", got, want)
457+
}
458+
if tt.dstLen >= 2 {
459+
if got, want := int(dst[1]), 42; got != want {
460+
t.Errorf("got %d, want %d", got, want)
461+
}
462+
}
463+
})
464+
}
465+
}
466+
467+
func TestCopyBytesToJS(t *testing.T) {
468+
for _, tt := range copyTests {
469+
t.Run(fmt.Sprintf("%d-to-%d", tt.srcLen, tt.dstLen), func(t *testing.T) {
470+
src := make([]byte, tt.srcLen)
471+
if tt.srcLen >= 2 {
472+
src[1] = 42
473+
}
474+
dst := js.Global().Get("Uint8Array").New(tt.dstLen)
475+
476+
if got, want := js.CopyBytesToJS(dst, src), tt.copyLen; got != want {
477+
t.Errorf("copied %d, want %d", got, want)
478+
}
479+
if tt.dstLen >= 2 {
480+
if got, want := dst.Index(1).Int(), 42; got != want {
481+
t.Errorf("got %d, want %d", got, want)
482+
}
483+
}
484+
})
485+
}
486+
}

0 commit comments

Comments
 (0)