Commit 138bfc28 authored by Richard Musiol's avatar Richard Musiol Committed by Richard Musiol

syscall/js: make zero js.Value represent "undefined"

This commit changes the encoding of js.Value so that the zero js.Value
represents the JavaScript value "undefined". This is what users
intuitively expect.

Specifically, the encodings of "undefined" and the number zero have
been swapped.

Fixes #27592.

Change-Id: Icfc832c8cdf7a8a78bd69d20e00a04dbed0ccd10
Reviewed-on: https://go-review.googlesource.com/c/143137
Run-TryBot: Richard Musiol <neelance@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarBrad Fitzpatrick <bradfitz@golang.org>
parent 8ccafb1a
...@@ -95,6 +95,9 @@ ...@@ -95,6 +95,9 @@
const loadValue = (addr) => { const loadValue = (addr) => {
const f = mem().getFloat64(addr, true); const f = mem().getFloat64(addr, true);
if (f === 0) {
return undefined;
}
if (!isNaN(f)) { if (!isNaN(f)) {
return f; return f;
} }
...@@ -112,14 +115,18 @@ ...@@ -112,14 +115,18 @@
mem().setUint32(addr, 0, true); mem().setUint32(addr, 0, true);
return; return;
} }
if (v === 0) {
mem().setUint32(addr + 4, nanHead, true);
mem().setUint32(addr, 1, true);
return;
}
mem().setFloat64(addr, v, true); mem().setFloat64(addr, v, true);
return; return;
} }
switch (v) { switch (v) {
case undefined: case undefined:
mem().setUint32(addr + 4, nanHead, true); mem().setFloat64(addr, 0, true);
mem().setUint32(addr, 1, true);
return; return;
case null: case null:
mem().setUint32(addr + 4, nanHead, true); mem().setUint32(addr + 4, nanHead, true);
...@@ -334,7 +341,7 @@ ...@@ -334,7 +341,7 @@
this._inst = instance; this._inst = instance;
this._values = [ // TODO: garbage collection this._values = [ // TODO: garbage collection
NaN, NaN,
undefined, 0,
null, null,
true, true,
false, false,
...@@ -396,14 +403,14 @@ ...@@ -396,14 +403,14 @@
} }
static _makeCallbackHelper(id, pendingCallbacks, go) { static _makeCallbackHelper(id, pendingCallbacks, go) {
return function() { return function () {
pendingCallbacks.push({ id: id, args: arguments }); pendingCallbacks.push({ id: id, args: arguments });
go._resolveCallbackPromise(); go._resolveCallbackPromise();
}; };
} }
static _makeEventCallbackHelper(preventDefault, stopPropagation, stopImmediatePropagation, fn) { static _makeEventCallbackHelper(preventDefault, stopPropagation, stopImmediatePropagation, fn) {
return function(event) { return function (event) {
if (preventDefault) { if (preventDefault) {
event.preventDefault(); event.preventDefault();
} }
......
...@@ -16,15 +16,17 @@ import ( ...@@ -16,15 +16,17 @@ import (
) )
// ref is used to identify a JavaScript value, since the value itself can not be passed to WebAssembly. // ref is used to identify a JavaScript value, since the value itself can not be passed to WebAssembly.
// A JavaScript number (64-bit float, except NaN) is represented by its IEEE 754 binary representation. //
// The JavaScript value "undefined" is represented by the value 0.
// A JavaScript number (64-bit float, except 0 and NaN) is represented by its IEEE 754 binary representation.
// All other values are represented as an IEEE 754 binary representation of NaN with bits 0-31 used as // All other values are represented as an IEEE 754 binary representation of NaN with bits 0-31 used as
// an ID and bits 32-33 used to differentiate between string, symbol, function and object. // an ID and bits 32-33 used to differentiate between string, symbol, function and object.
type ref uint64 type ref uint64
// nanHead are the upper 32 bits of a ref which are set if the value is not a JavaScript number or NaN itself. // nanHead are the upper 32 bits of a ref which are set if the value is not encoded as an IEEE 754 number (see above).
const nanHead = 0x7FF80000 const nanHead = 0x7FF80000
// Value represents a JavaScript value. // Value represents a JavaScript value. The zero value is the JavaScript value "undefined".
type Value struct { type Value struct {
ref ref ref ref
} }
...@@ -38,6 +40,9 @@ func predefValue(id uint32) Value { ...@@ -38,6 +40,9 @@ func predefValue(id uint32) Value {
} }
func floatValue(f float64) Value { func floatValue(f float64) Value {
if f == 0 {
return valueZero
}
if f != f { if f != f {
return valueNaN return valueNaN
} }
...@@ -56,8 +61,9 @@ func (e Error) Error() string { ...@@ -56,8 +61,9 @@ func (e Error) Error() string {
} }
var ( var (
valueUndefined = Value{ref: 0}
valueNaN = predefValue(0) valueNaN = predefValue(0)
valueUndefined = predefValue(1) valueZero = predefValue(1)
valueNull = predefValue(2) valueNull = predefValue(2)
valueTrue = predefValue(3) valueTrue = predefValue(3)
valueFalse = predefValue(4) valueFalse = predefValue(4)
...@@ -318,13 +324,18 @@ func (v Value) New(args ...interface{}) Value { ...@@ -318,13 +324,18 @@ func (v Value) New(args ...interface{}) Value {
func valueNew(v ref, args []ref) (ref, bool) func valueNew(v ref, args []ref) (ref, bool)
func (v Value) isNumber() bool { func (v Value) isNumber() bool {
return v.ref>>32&nanHead != nanHead || v.ref == valueNaN.ref return v.ref == valueZero.ref ||
v.ref == valueNaN.ref ||
(v.ref != valueUndefined.ref && v.ref>>32&nanHead != nanHead)
} }
func (v Value) float(method string) float64 { func (v Value) float(method string) float64 {
if !v.isNumber() { if !v.isNumber() {
panic(&ValueError{method, v.Type()}) panic(&ValueError{method, v.Type()})
} }
if v.ref == valueZero.ref {
return 0
}
return *(*float64)(unsafe.Pointer(&v.ref)) return *(*float64)(unsafe.Pointer(&v.ref))
} }
......
...@@ -22,6 +22,7 @@ var dummys = js.Global().Call("eval", `({ ...@@ -22,6 +22,7 @@ var dummys = js.Global().Call("eval", `({
add: function(a, b) { add: function(a, b) {
return a + b; return a + b;
}, },
zero: 0,
NaN: NaN, NaN: NaN,
})`) })`)
...@@ -74,6 +75,9 @@ func TestInt(t *testing.T) { ...@@ -74,6 +75,9 @@ func TestInt(t *testing.T) {
if dummys.Get("someInt") != dummys.Get("someInt") { if dummys.Get("someInt") != dummys.Get("someInt") {
t.Errorf("same value not equal") t.Errorf("same value not equal")
} }
if got := dummys.Get("zero").Int(); got != 0 {
t.Errorf("got %#v, want %#v", got, 0)
}
} }
func TestIntConversion(t *testing.T) { func TestIntConversion(t *testing.T) {
...@@ -237,6 +241,9 @@ func TestType(t *testing.T) { ...@@ -237,6 +241,9 @@ func TestType(t *testing.T) {
if got, want := js.ValueOf(true).Type(), js.TypeBoolean; got != want { if got, want := js.ValueOf(true).Type(), js.TypeBoolean; got != want {
t.Errorf("got %s, want %s", got, want) t.Errorf("got %s, want %s", got, want)
} }
if got, want := js.ValueOf(0).Type(), js.TypeNumber; got != want {
t.Errorf("got %s, want %s", got, want)
}
if got, want := js.ValueOf(42).Type(), js.TypeNumber; got != want { if got, want := js.ValueOf(42).Type(), js.TypeNumber; got != want {
t.Errorf("got %s, want %s", got, want) t.Errorf("got %s, want %s", got, want)
} }
...@@ -269,6 +276,13 @@ func TestValueOf(t *testing.T) { ...@@ -269,6 +276,13 @@ func TestValueOf(t *testing.T) {
} }
} }
func TestZeroValue(t *testing.T) {
var v js.Value
if v != js.Undefined() {
t.Error("zero js.Value is not js.Undefined()")
}
}
func TestCallback(t *testing.T) { func TestCallback(t *testing.T) {
c := make(chan struct{}) c := make(chan struct{})
cb := js.NewCallback(func(args []js.Value) { cb := js.NewCallback(func(args []js.Value) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment