Skip to content
1 change: 0 additions & 1 deletion build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke
overrides := make(map[string]overrideInfo)
for _, file := range overlayFiles {
augmentOverlayFile(file, overrides)
pruneImports(file)
}
delete(overrides, "init")

Expand Down
4 changes: 1 addition & 3 deletions compiler/expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression {
switch basic.Kind() {
case types.Int32, types.Int:
return fc.formatParenExpr("$imul(%e, %e)", e.X, e.Y)
case types.Uint32, types.Uintptr:
case types.Uint32, types.Uint, types.Uintptr:
return fc.formatParenExpr("$imul(%e, %e) >>> 0", e.X, e.Y)
}
return fc.fixNumber(fc.formatExpr("%e * %e", e.X, e.Y), basic)
Expand Down Expand Up @@ -1131,8 +1131,6 @@ func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type
return fc.fixNumber(fc.formatParenExpr("%1l + ((%1h >> 31) * 4294967296)", expr), t)
}
return fc.fixNumber(fc.formatExpr("%s.$low", fc.translateExpr(expr)), t)
case isFloat(basicExprType):
return fc.formatParenExpr("%e >> 0", expr)
case types.Identical(exprType, types.Typ[types.UnsafePointer]):
return fc.translateExpr(expr)
default:
Expand Down
6 changes: 6 additions & 0 deletions compiler/prelude/jsmapping.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,9 @@ var $internalize = (v, t, recv, seen, makeWrapper) => {
case $kindFloat64:
return parseFloat(v);
case $kindArray:
if (v === null || v === undefined) {
$throwRuntimeError("cannot internalize "+v+" as a "+t.string);
}
if (v.length !== t.len) {
$throwRuntimeError("got array with wrong size from JavaScript native");
}
Expand Down Expand Up @@ -331,6 +334,9 @@ var $internalize = (v, t, recv, seen, makeWrapper) => {
return $internalize(v, t.elem, makeWrapper);
}
case $kindSlice:
if (v == null) {
return t.zero();
}
return new t($mapArray(v, e => { return $internalize(e, t.elem, makeWrapper); }));
case $kindString:
v = String(v);
Expand Down
39 changes: 39 additions & 0 deletions tests/js_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,45 @@ func TestExternalize(t *testing.T) {
}
}

func TestInternalizeSlice(t *testing.T) {
tests := []struct {
name string
init []int
want string
}{
{
name: `nil slice`,
init: []int(nil),
want: `[]int(nil)`,
},
{
name: `empty slice`,
init: []int{},
want: `[]int{}`,
},
{
name: `non-empty slice`,
init: []int{42, 53, 64},
want: `[]int{42, 53, 64}`,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b := struct {
*js.Object
V []int `js:"V"` // V is externalized
}{Object: js.Global.Get("Object").New()}
b.V = tt.init

result := fmt.Sprintf(`%#v`, b.V) // internalize b.V
if result != tt.want {
t.Errorf(`Unexpected result %q != %q`, result, tt.want)
}
})
}
}

func TestInternalizeExternalizeNull(t *testing.T) {
type S struct {
*js.Object
Expand Down
108 changes: 108 additions & 0 deletions tests/numeric_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package tests

import (
"fmt"
"math/bits"
"math/rand"
"runtime"
"testing"
"testing/quick"

"github.com/gopherjs/gopherjs/js"
)

// naiveMul64 performs 64-bit multiplication without using the multiplication
Expand Down Expand Up @@ -93,3 +97,107 @@ func BenchmarkMul64(b *testing.B) {
}
})
}

func TestIssue733(t *testing.T) {
if runtime.GOOS != "js" {
t.Skip("test uses GopherJS-specific features")
}

t.Run("sign", func(t *testing.T) {
f := float64(-1)
i := uint32(f)
underlying := js.InternalObject(i).Float() // Get the raw JS number behind i.
if want := float64(4294967295); underlying != want {
t.Errorf("Got: uint32(float64(%v)) = %v. Want: %v.", f, underlying, want)
}
})
t.Run("truncation", func(t *testing.T) {
f := float64(300)
i := uint8(f)
underlying := js.InternalObject(i).Float() // Get the raw JS number behind i.
if want := float64(44); underlying != want {
t.Errorf("Got: uint32(float64(%v)) = %v. Want: %v.", f, underlying, want)
}
})
}

// Test_32BitEnvironment tests that GopherJS behaves correctly
// as a 32-bit environment for integers. To simulate a 32 bit environment
// we have to use `$imul` instead of `*` to get the correct result.
func Test_32BitEnvironment(t *testing.T) {
if bits.UintSize != 32 {
t.Skip(`test is only relevant for 32-bit environment`)
}

tests := []struct {
x, y, exp uint64
}{
{
x: 65535, // x = 2^16 - 1
y: 65535, // same as x
exp: 4294836225, // x² works since it doesn't overflow 32 bits.
},
{
x: 134217729, // x = 2^27 + 1, x < 2^32 and x > sqrt(2^53), so x² overflows 53 bits.
y: 134217729, // same as x
exp: 268435457, // x² mod 2^32 = (2^27 + 1)² mod 2^32 = (2^54 + 2^28 + 1) mod 2^32 = 2^28 + 1
// In pure JS, `x * x >>> 0`, would result in 268,435,456 because it lost the least significant bit
// prior to being truncated, where in a real 32 bit environment, it would be 268,435,457 since
// the rollover removed the most significant bit and doesn't affect the least significant bit.
},
{
x: 4294967295, // x = 2^32 - 1 another case where x² overflows 53 bits causing a loss of precision.
y: 4294967295, // same as x
exp: 1, // x² mod 2^32 = (2^32 - 1)² mod 2^32 = (2^64 - 2^33 + 1) mod 2^32 = 1
// In pure JS, `x * x >>> 0`, would result in 0 because it lost the least significant bits.
},
{
x: 4294967295, // x = 2^32 - 1
y: 3221225473, // y = 2^31 + 2^30 + 1
exp: 1073741823, // 2^32 - 1.
// In pure JS, `x * y >>> 0`, would result in 1,073,741,824.
},
{
x: 4294967295, // x = 2^32 - 1
y: 134217729, // y = 2^27 + 1
exp: 4160749567, // In pure JS, `x * y >>> 0`, would result in 4,160,749,568.
},
}

for i, test := range tests {
t.Run(fmt.Sprintf(`#%d/uint32`, i), func(t *testing.T) {
x, y, exp := uint32(test.x), uint32(test.y), uint32(test.exp)
if got := x * y; got != exp {
t.Errorf("got: %d\nwant: %d.", got, exp)
}
})

t.Run(fmt.Sprintf(`#%d/uintptr`, i), func(t *testing.T) {
x, y, exp := uintptr(test.x), uintptr(test.y), uintptr(test.exp)
if got := x * y; got != exp {
t.Errorf("got: %d\nwant: %d.", got, exp)
}
})

t.Run(fmt.Sprintf(`#%d/uint`, i), func(t *testing.T) {
x, y, exp := uint(test.x), uint(test.y), uint(test.exp)
if got := x * y; got != exp {
t.Errorf("got: %d\nwant: %d.", got, exp)
}
})

t.Run(fmt.Sprintf(`#%d/int32`, i), func(t *testing.T) {
x, y, exp := int32(test.x), int32(test.y), int32(test.exp)
if got := x * y; got != exp {
t.Errorf("got: %d\nwant: %d.", got, exp)
}
})

t.Run(fmt.Sprintf(`#%d/int`, i), func(t *testing.T) {
x, y, exp := int(test.x), int(test.y), int(test.exp)
if got := x * y; got != exp {
t.Errorf("got: %d\nwant: %d.", got, exp)
}
})
}
}