From dfa651bd261c57b7082718fe3b7861e388fa5380 Mon Sep 17 00:00:00 2001 From: shiroyk Date: Sun, 11 Aug 2024 15:01:40 +0800 Subject: [PATCH 01/16] Implemented BigInt --- builtin_bigint.go | 359 +++++++++++++++++++++++++++++++++ builtin_bigint_test.go | 117 +++++++++++ builtin_global.go | 4 +- builtin_json.go | 11 +- builtin_object.go | 3 + builtin_string.go | 5 +- builtin_typedarrays.go | 83 ++++++++ compiler_expr.go | 7 + compiler_test.go | 11 +- object.go | 1 + parser/lexer.go | 24 ++- parser/lexer_test.go | 17 ++ parser/parser_test.go | 13 ++ runtime.go | 35 +++- string.go | 1 + string_ascii.go | 8 + tc39_test.go | 9 - typedarrays.go | 198 ++++++++++++++++++- typedarrays_test.go | 29 +++ value.go | 15 +- vm.go | 438 ++++++++++++++++++++++++++++++++++------- 21 files changed, 1291 insertions(+), 97 deletions(-) create mode 100644 builtin_bigint.go create mode 100644 builtin_bigint_test.go diff --git a/builtin_bigint.go b/builtin_bigint.go new file mode 100644 index 00000000..c41c6980 --- /dev/null +++ b/builtin_bigint.go @@ -0,0 +1,359 @@ +package goja + +import ( + "fmt" + "hash/maphash" + "math" + "math/big" + "reflect" + "strconv" + "sync" + + "github.com/dop251/goja/unistring" +) + +type valueBigInt struct { + i *big.Int +} + +func (v valueBigInt) ToInteger() int64 { + return v.i.Int64() +} + +func (v valueBigInt) toString() String { + return asciiString(v.i.String()) +} + +func (v valueBigInt) string() unistring.String { + return unistring.String(v.String()) +} + +func (v valueBigInt) ToString() Value { + return v +} + +func (v valueBigInt) String() string { + return v.i.String() +} + +func (v valueBigInt) ToFloat() float64 { + f, _ := v.i.Float64() + return f +} + +func (v valueBigInt) ToNumber() Value { + panic(typeError("Cannot convert a BigInt value to a number")) +} + +func (v valueBigInt) ToBoolean() bool { + return v.i.Sign() != 0 +} + +func (v valueBigInt) ToObject(r *Runtime) *Object { + return r.newPrimitiveObject(v, r.getBigIntPrototype(), classBigInt) +} + +func (v valueBigInt) SameAs(other Value) bool { + if o, ok := other.(valueBigInt); ok { + return v.i.Cmp(o.i) == 0 + } + return false +} + +func (v valueBigInt) Equals(other Value) bool { + switch o := other.(type) { + case valueBigInt: + return v.i.Cmp(o.i) == 0 + case valueInt: + return v.i.Cmp(big.NewInt(int64(o))) == 0 + case valueFloat: + if IsInfinity(o) || math.IsNaN(float64(o)) { + return false + } + if f := big.NewFloat(float64(o)); f.IsInt() { + i, _ := f.Int(nil) + return v.i.Cmp(i) == 0 + } + return false + case String: + bigInt, err := stringToBigInt(o.toTrimmedUTF8()) + if err != nil { + return false + } + return bigInt.Cmp(v.i) == 0 + case valueBool: + return v.i.Int64() == o.ToInteger() + case *Object: + return v.Equals(o.toPrimitiveNumber()) + } + return false +} + +func (v valueBigInt) StrictEquals(other Value) bool { + o, ok := other.(valueBigInt) + if ok { + return v.i.Cmp(o.i) == 0 + } + return false +} + +func (v valueBigInt) Export() interface{} { + return v.i +} + +func (v valueBigInt) ExportType() reflect.Type { + return typeBigInt +} + +func (v valueBigInt) baseObject(rt *Runtime) *Object { + return rt.getBigIntPrototype() +} + +func (v valueBigInt) hash(*maphash.Hash) uint64 { + return v.i.Uint64() +} + +func toBigInt(value Value) valueBigInt { + // Undefined Throw a TypeError exception. + // Null Throw a TypeError exception. + // Boolean Return 1n if prim is true and 0n if prim is false. + // BigInt Return prim. + // Number Throw a TypeError exception. + // String 1. Let n be StringToBigInt(prim). + // 2. If n is undefined, throw a SyntaxError exception. + // 3. Return n. + // Symbol Throw a TypeError exception. + switch prim := value.(type) { + case valueBigInt: + return prim + case String: + bigInt, err := stringToBigInt(prim.toTrimmedUTF8()) + if err != nil { + panic(syntaxError(fmt.Sprintf("Cannot convert %s to a BigInt", prim))) + } + return valueBigInt{bigInt} + case valueBool: + return valueBigInt{big.NewInt(prim.ToInteger())} + case *Symbol: + panic(typeError("Cannot convert Symbol to a BigInt")) + case *Object: + return toBigInt(prim.toPrimitiveNumber()) + default: + panic(typeError(fmt.Sprintf("Cannot convert %s to a BigInt", prim))) + } +} + +func numberToBigInt(v Value) valueBigInt { + switch v := toNumeric(v).(type) { + case valueBigInt: + return v + case valueInt: + return valueBigInt{big.NewInt(v.ToInteger())} + case valueFloat: + if IsInfinity(v) || math.IsNaN(float64(v)) { + panic(rangeError(fmt.Sprintf("Cannot convert %s to a BigInt", v))) + } + if f := big.NewFloat(float64(v)); f.IsInt() { + n, _ := f.Int(nil) + return valueBigInt{n} + } + panic(rangeError(fmt.Sprintf("Cannot convert %s to a BigInt", v))) + case *Object: + prim := v.toPrimitiveNumber() + switch prim.(type) { + case valueInt, valueFloat: + return numberToBigInt(prim) + default: + return toBigInt(prim) + } + default: + panic(newTypeError("Cannot convert %s to a BigInt", v)) + } +} + +func stringToBigInt(str string) (*big.Int, error) { + var bigint big.Int + n, err := stringToInt(str) + if err != nil { + switch { + case isRangeErr(err): + bigint.SetString(str, 0) + case err == strconv.ErrSyntax: + default: + return nil, strconv.ErrSyntax + } + } else { + bigint.SetInt64(n) + } + return &bigint, nil +} + +func (r *Runtime) thisBigIntValue(value Value) Value { + switch t := value.(type) { + case valueBigInt: + return t + case *Object: + switch t := t.self.(type) { + case *primitiveValueObject: + return r.thisBigIntValue(t.pValue) + case *objectGoReflect: + if t.class == classBigInt && t.valueOf != nil { + return t.valueOf() + } + } + } + panic(r.NewTypeError("requires that 'this' be a BigInt")) +} + +func (r *Runtime) bigintproto_valueOf(call FunctionCall) Value { + return r.thisBigIntValue(call.This) +} + +func (r *Runtime) bigintproto_toString(call FunctionCall) Value { + x := r.thisBigIntValue(call.This).(valueBigInt) + radix := call.Argument(0) + var radixMV int + + if radix == _undefined { + radixMV = 10 + } else { + radixMV = int(radix.ToInteger()) + if radixMV < 2 || radixMV > 36 { + panic(r.newError(r.getRangeError(), "radix must be an integer between 2 and 36")) + } + } + + return asciiString(x.i.Text(radixMV)) +} + +func (r *Runtime) bigint_asIntN(call FunctionCall) Value { + if len(call.Arguments) < 2 { + panic(r.NewTypeError("Cannot convert undefined to a BigInt")) + } + bits := r.toIndex(call.Argument(0).ToNumber()) + if bits < 0 { + panic(r.NewTypeError("Invalid value: not (convertible to) a safe integer")) + } + bigint := toBigInt(call.Argument(1)) + + lsh := big.NewInt(2).Lsh(big.NewInt(1), uint(bits)) + mod := new(big.Int).Mod(bigint.i, lsh) + if bits > 0 && mod.Cmp(big.NewInt(2).Lsh(big.NewInt(1), uint(bits-1))) >= 0 { + return valueBigInt{mod.Sub(mod, lsh)} + } else { + return valueBigInt{mod} + } +} + +func (r *Runtime) bigint_asUintN(call FunctionCall) Value { + if len(call.Arguments) < 2 { + panic(r.NewTypeError("Cannot convert undefined to a BigInt")) + } + bits := r.toIndex(call.Argument(0).ToNumber()) + if bits < 0 { + panic(r.NewTypeError("Invalid value: not (convertible to) a safe integer")) + } + bigint := toBigInt(call.Argument(1)) + return valueBigInt{bigint.i.Mod(bigint.i, big.NewInt(2).Lsh(big.NewInt(1), uint(bits)))} +} + +var bigintTemplate *objectTemplate +var bigintTemplateOnce sync.Once + +func getBigIntTemplate() *objectTemplate { + bigintTemplateOnce.Do(func() { + bigintTemplate = createBigIntTemplate() + }) + return bigintTemplate +} + +func createBigIntTemplate() *objectTemplate { + t := newObjectTemplate() + t.protoFactory = func(r *Runtime) *Object { + return r.getFunctionPrototype() + } + + t.putStr("name", func(r *Runtime) Value { return valueProp(asciiString("BigInt"), false, false, true) }) + t.putStr("length", func(r *Runtime) Value { return valueProp(intToValue(1), false, false, true) }) + t.putStr("prototype", func(r *Runtime) Value { return valueProp(r.getBigIntPrototype(), false, false, false) }) + + t.putStr("asIntN", func(r *Runtime) Value { return r.methodProp(r.bigint_asIntN, "asIntN", 2) }) + t.putStr("asUintN", func(r *Runtime) Value { return r.methodProp(r.bigint_asUintN, "asUintN", 2) }) + + return t +} + +func (r *Runtime) builtin_BigInt(call FunctionCall) Value { + if len(call.Arguments) > 0 { + switch v := call.Argument(0).(type) { + case valueBigInt, valueInt, valueFloat, *Object: + return numberToBigInt(v) + default: + return toBigInt(v) + } + } + return valueBigInt{big.NewInt(0)} +} + +func (r *Runtime) builtin_newBigInt(args []Value, newTarget *Object) *Object { + if newTarget != nil { + panic(r.NewTypeError("BigInt is not a constructor")) + } + var v Value + if len(args) > 0 { + v = numberToBigInt(args[0]) + } else { + v = valueBigInt{big.NewInt(0)} + } + return r.newPrimitiveObject(v, newTarget, classBigInt) +} + +func (r *Runtime) getBigInt() *Object { + ret := r.global.BigInt + if ret == nil { + ret = &Object{runtime: r} + r.global.BigInt = ret + r.newTemplatedFuncObject(getBigIntTemplate(), ret, r.builtin_BigInt, + r.wrapNativeConstruct(r.builtin_newBigInt, ret, r.getBigIntPrototype())) + } + return ret +} + +func createBigIntProtoTemplate() *objectTemplate { + t := newObjectTemplate() + t.protoFactory = func(r *Runtime) *Object { + return r.global.ObjectPrototype + } + + t.putStr("length", func(r *Runtime) Value { return valueProp(intToValue(0), false, false, true) }) + t.putStr("name", func(r *Runtime) Value { return valueProp(asciiString(classBigInt), false, false, true) }) + t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getBigInt(), true, false, true) }) + + t.putStr("toLocaleString", func(r *Runtime) Value { return r.methodProp(r.bigintproto_toString, "toLocaleString", 0) }) + t.putStr("toString", func(r *Runtime) Value { return r.methodProp(r.bigintproto_toString, "toString", 0) }) + t.putStr("valueOf", func(r *Runtime) Value { return r.methodProp(r.bigintproto_valueOf, "valueOf", 0) }) + t.putSym(SymToStringTag, func(r *Runtime) Value { return valueProp(asciiString(classBigInt), false, false, true) }) + + return t +} + +var bigintProtoTemplate *objectTemplate +var bigintProtoTemplateOnce sync.Once + +func getBigIntProtoTemplate() *objectTemplate { + bigintProtoTemplateOnce.Do(func() { + bigintProtoTemplate = createBigIntProtoTemplate() + }) + return bigintProtoTemplate +} + +func (r *Runtime) getBigIntPrototype() *Object { + ret := r.global.BigIntPrototype + if ret == nil { + ret = &Object{runtime: r} + r.global.BigIntPrototype = ret + o := r.newTemplatedObject(getBigIntProtoTemplate(), ret) + o.class = classBigInt + } + return ret +} diff --git a/builtin_bigint_test.go b/builtin_bigint_test.go new file mode 100644 index 00000000..1de35cb6 --- /dev/null +++ b/builtin_bigint_test.go @@ -0,0 +1,117 @@ +package goja + +import ( + "math/big" + "testing" +) + +func TestBigInt(t *testing.T) { + const SCRIPT = `0xabcdef0123456789abcdef0123n` + b := new(big.Int) + b.SetString("0xabcdef0123456789abcdef0123", 0) + testScript(SCRIPT, valueBigInt{b}, t) +} + +func TestBigIntExportTo(t *testing.T) { + vm := New() + + t.Run("bigint exportType", func(t *testing.T) { + v, err := vm.RunString(`BigInt(Number.MAX_SAFE_INTEGER + 10);`) + if err != nil { + t.Fatal(err) + } + if typ := v.ExportType(); typ != typeBigInt { + t.Fatal(typ) + } + }) + + t.Run("bigint", func(t *testing.T) { + var b big.Int + err := vm.ExportTo(vm.ToValue(big.NewInt(10)), &b) + if err != nil { + t.Fatal(err) + } + if b.Cmp(big.NewInt(10)) != 0 { + t.Fatalf("bigint: %s", b.String()) + } + }) +} + +func TestBigIntFormat(t *testing.T) { + const SCRIPT = ` +assert.sameValue((1n).toString(undefined), "1", "radius undefined"); +assert.throws(RangeError, () => { (1n).toString(-1); }, "radius -1"); +assert.throws(RangeError, () => { (1n).toString(37); }, "radius 37"); +assert.sameValue((1n).toString(2), "1", "radius 2"); +assert.sameValue((10n).toString(3), "101", "radius 3"); +` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestBigIntOperator(t *testing.T) { + const SCRIPT = ` +assert.throws(TypeError, () => { 1 - 1n; }, "mix type add"); +assert.throws(TypeError, () => { 1n - 1; }, "mix type add"); +assert.throws(TypeError, () => { 1n + 1; }, "mix type sub"); +assert.throws(TypeError, () => { 1 + 1n; }, "mix type sub"); +assert.throws(TypeError, () => { 1 * 1n; }, "mix type mul"); +assert.throws(TypeError, () => { 1n * 1; }, "mix type mul"); +assert.throws(TypeError, () => { 1 / 1n; }, "mix type div"); +assert.throws(TypeError, () => { 1n / 1; }, "mix type div"); +assert.throws(TypeError, () => { 1 % 1n; }, "mix type mod"); +assert.throws(TypeError, () => { 1n % 1; }, "mix type mod"); +assert.throws(TypeError, () => { 1n ** 1; }, "mix type exp"); +assert.throws(TypeError, () => { 1 ** 1n; }, "mix type exp"); +assert.throws(TypeError, () => { 1 & 1n; }, "mix type and"); +assert.throws(TypeError, () => { 1n & 1; }, "mix type and"); +assert.throws(TypeError, () => { 1 | 1n; }, "mix type or"); +assert.throws(TypeError, () => { 1n | 1; }, "mix type or"); +assert.throws(TypeError, () => { 1 ^ 1n; }, "mix type xor"); +assert.throws(TypeError, () => { 1n ^ 1; }, "mix type xor"); +assert.throws(TypeError, () => { 1 << 1n; }, "mix type lsh"); +assert.throws(TypeError, () => { 1n << 1; }, "mix type lsh"); +assert.throws(TypeError, () => { 1 >> 1n; }, "mix type rsh"); +assert.throws(TypeError, () => { 1n >> 1; }, "mix type rsh"); +assert.throws(TypeError, () => { 1 >>> 1n; }, "mix type ursh"); +assert.throws(TypeError, () => { 1n >>> 1; }, "mix type ursh"); + +assert.sameValue(1n + 1n, 2n, "add"); +assert.sameValue(1n - 1n, 0n, "sub"); +assert.sameValue(1n * 2n, 2n, "mul"); +assert.sameValue(1n / 2n, 0n, "div"); +assert.sameValue(1n % 2n, 1n, "mod"); +assert.sameValue(1n ** 2n, 1n, "exp"); +assert.sameValue(1n & 1n, 1n, "and"); +assert.sameValue(1n | 1n, 1n, "or"); +assert.sameValue(2n ^ 1n, 3n, "xor"); +assert.sameValue(1n << 1n, 2n, "lsh"); +assert.sameValue(4n << -1n, 2n, "neg lsh"); +assert.sameValue(4n >> 1n, 2n, "rsh"); +assert.sameValue(2n >> -2n, 8n, "neg rsh"); + +let a = 1n; +assert.sameValue(++a, 2n, "inc"); +assert.sameValue(--a, 1n, "dec"); + +assert.sameValue(Object(1n) - 1n, 0n, "primitive sub"); +assert.sameValue(Object(Object(1n)) - 1n, 0n, "primitive sub"); +assert.sameValue({ [Symbol.toPrimitive]: () => 1n } - 1n, 0n, "primitive sub"); +assert.sameValue({ valueOf: () => 1n } - 1n, 0n, "valueOf sub"); + +assert.sameValue(1n > 0, true, "gt"); +assert.sameValue(0 > 1n, false, "gt"); +assert.sameValue(Object(1n) > 0, true, "gt"); +assert.sameValue(0 > Object(1n), false, "gt"); + +assert.sameValue(1n < 0, false, "lt"); +assert.sameValue(0 < 1n, true, "lt"); +assert.sameValue(Object(1n) < 0, false, "lt"); +assert.sameValue(0 < Object(1n), true, "lt"); + +assert.sameValue(1n >= 0, true, "ge"); +assert.sameValue(0 >= 1n, false, "ge"); +assert.sameValue(1n <= 0, false, "le"); +assert.sameValue(0 <= 1n, true, "le"); +` + testScriptWithTestLib(SCRIPT, _undefined, t) +} diff --git a/builtin_global.go b/builtin_global.go index 5ef4176b..2c6385ae 100644 --- a/builtin_global.go +++ b/builtin_global.go @@ -2,7 +2,6 @@ package goja import ( "errors" - "github.com/dop251/goja/unistring" "io" "math" "regexp" @@ -10,6 +9,8 @@ import ( "strings" "sync" "unicode/utf8" + + "github.com/dop251/goja/unistring" ) const hexUpper = "0123456789ABCDEF" @@ -339,6 +340,7 @@ func createGlobalObjectTemplate() *objectTemplate { t.putStr("Array", func(r *Runtime) Value { return valueProp(r.getArray(), true, false, true) }) t.putStr("String", func(r *Runtime) Value { return valueProp(r.getString(), true, false, true) }) t.putStr("Number", func(r *Runtime) Value { return valueProp(r.getNumber(), true, false, true) }) + t.putStr("BigInt", func(r *Runtime) Value { return valueProp(r.getBigInt(), true, false, true) }) t.putStr("RegExp", func(r *Runtime) Value { return valueProp(r.getRegExp(), true, false, true) }) t.putStr("Date", func(r *Runtime) Value { return valueProp(r.getDate(), true, false, true) }) t.putStr("Boolean", func(r *Runtime) Value { return valueProp(r.getBoolean(), true, false, true) }) diff --git a/builtin_json.go b/builtin_json.go index e99771cf..5be14b86 100644 --- a/builtin_json.go +++ b/builtin_json.go @@ -281,8 +281,9 @@ func (ctx *_builtinJSON_stringifyContext) do(v Value) bool { func (ctx *_builtinJSON_stringifyContext) str(key Value, holder *Object) bool { value := nilSafe(holder.get(key, nil)) - if object, ok := value.(*Object); ok { - if toJSON, ok := object.self.getStr("toJSON", nil).(*Object); ok { + switch value.(type) { + case *Object, valueBigInt: + if toJSON, ok := ctx.r.getVStr(value, "toJSON").(*Object); ok { if c, ok := toJSON.self.assertCallable(); ok { value = c(FunctionCall{ This: value, @@ -333,6 +334,10 @@ func (ctx *_builtinJSON_stringifyContext) str(key Value, holder *Object) bool { } else { value = valueFalse } + case classBigInt: + if o1.valueOf != nil { + value = o1.valueOf() + } } } } @@ -357,6 +362,8 @@ func (ctx *_builtinJSON_stringifyContext) str(key Value, holder *Object) bool { } case valueNull: ctx.buf.WriteString("null") + case valueBigInt: + ctx.r.typeErrorResult(true, "Do not know how to serialize a BigInt") case *Object: for _, object := range ctx.stack { if value1.SameAs(object) { diff --git a/builtin_object.go b/builtin_object.go index 6bf1ff80..2d7242c6 100644 --- a/builtin_object.go +++ b/builtin_object.go @@ -473,6 +473,9 @@ func (r *Runtime) objectproto_toString(call FunctionCall) Value { clsName = classArray } else { clsName = obj.self.className() + if clsName == classBigInt { + clsName = classObject + } } if tag := obj.self.getSym(SymToStringTag, nil); tag != nil { if str, ok := tag.(String); ok { diff --git a/builtin_string.go b/builtin_string.go index 43369c41..067c615d 100644 --- a/builtin_string.go +++ b/builtin_string.go @@ -1,13 +1,14 @@ package goja import ( - "github.com/dop251/goja/unistring" "math" "strings" "sync" "unicode/utf16" "unicode/utf8" + "github.com/dop251/goja/unistring" + "github.com/dop251/goja/parser" "golang.org/x/text/collate" "golang.org/x/text/language" @@ -332,7 +333,7 @@ func (r *Runtime) stringproto_indexOf(call FunctionCall) Value { r.checkObjectCoercible(call.This) value := call.This.toString() target := call.Argument(0).toString() - pos := call.Argument(1).ToInteger() + pos := call.Argument(1).ToNumber().ToInteger() if pos < 0 { pos = 0 diff --git a/builtin_typedarrays.go b/builtin_typedarrays.go index 1fd672c1..bc25e325 100644 --- a/builtin_typedarrays.go +++ b/builtin_typedarrays.go @@ -4,6 +4,7 @@ import ( "fmt" "math" "sort" + "strings" "sync" "unsafe" @@ -275,6 +276,20 @@ func (r *Runtime) dataViewProto_getUint32(call FunctionCall) Value { panic(r.NewTypeError("Method DataView.prototype.getUint32 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) } +func (r *Runtime) dataViewProto_getBigInt64(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + return valueBigInt{dv.viewedArrayBuf.getBigInt64(dv.getIdxAndByteOrder(r.toIndex(call.Argument(0).ToNumber()), call.Argument(1), 8))} + } + panic(r.NewTypeError("Method DataView.prototype.getBigInt64 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) dataViewProto_getBigUint64(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + return valueBigInt{dv.viewedArrayBuf.getBigUint64(dv.getIdxAndByteOrder(r.toIndex(call.Argument(0).ToNumber()), call.Argument(1), 8))} + } + panic(r.NewTypeError("Method DataView.prototype.getBigUint64 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + func (r *Runtime) dataViewProto_setFloat32(call FunctionCall) Value { if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { idxVal := r.toIndex(call.Argument(0)) @@ -363,6 +378,28 @@ func (r *Runtime) dataViewProto_setUint32(call FunctionCall) Value { panic(r.NewTypeError("Method DataView.prototype.setUint32 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) } +func (r *Runtime) dataViewProto_setBigInt64(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + idxVal := r.toIndex(call.Argument(0)) + val := toBigInt(call.Argument(1)).i + idx, bo := dv.getIdxAndByteOrder(idxVal, call.Argument(2), 8) + dv.viewedArrayBuf.setBigInt64(idx, val, bo) + return _undefined + } + panic(r.NewTypeError("Method DataView.prototype.setBigInt64 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) dataViewProto_setBigUint64(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + idxVal := r.toIndex(call.Argument(0)) + val := toBigInt(call.Argument(1)).i + idx, bo := dv.getIdxAndByteOrder(idxVal, call.Argument(2), 8) + dv.viewedArrayBuf.setBigUint64(idx, val, bo) + return _undefined + } + panic(r.NewTypeError("Method DataView.prototype.setBigUint64 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + func (r *Runtime) typedArrayProto_getBuffer(call FunctionCall) Value { if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { return ta.viewedArrayBuf.val @@ -960,6 +997,7 @@ func (r *Runtime) typedArrayProto_set(call FunctionCall) Value { copy(ta.viewedArrayBuf.data[(ta.offset+targetOffset)*ta.elemSize:], src.viewedArrayBuf.data[src.offset*src.elemSize:(src.offset+srcLen)*src.elemSize]) } else { + checkTypedArrayMixBigInt(src.defaultCtor, ta.defaultCtor) curSrc := uintptr(unsafe.Pointer(&src.viewedArrayBuf.data[src.offset*src.elemSize])) endSrc := curSrc + uintptr(srcLen*src.elemSize) curDst := uintptr(unsafe.Pointer(&ta.viewedArrayBuf.data[(ta.offset+targetOffset)*ta.elemSize])) @@ -1323,6 +1361,15 @@ func (r *Runtime) _newTypedArrayFromArrayBuffer(ab *arrayBufferObject, args []Va return ta.val } +func checkTypedArrayMixBigInt(src, dst *Object) { + srcType := src.self.getStr("name", nil).String() + if strings.HasPrefix(srcType, "Big") { + if !strings.HasPrefix(dst.self.getStr("name", nil).String(), "Big") { + panic(errMixBigIntType) + } + } +} + func (r *Runtime) _newTypedArrayFromTypedArray(src *typedArrayObject, newTarget *Object, taCtor typedArrayObjectCtor, proto *Object) *Object { dst := r.allocateTypedArray(newTarget, 0, taCtor, proto) src.viewedArrayBuf.ensureNotDetached(true) @@ -1336,6 +1383,8 @@ func (r *Runtime) _newTypedArrayFromTypedArray(src *typedArrayObject, newTarget copy(dst.viewedArrayBuf.data, src.viewedArrayBuf.data[src.offset*src.elemSize:]) dst.length = src.length return dst.val + } else { + checkTypedArrayMixBigInt(src.defaultCtor, newTarget) } dst.length = l for i := 0; i < l; i++ { @@ -1405,6 +1454,14 @@ func (r *Runtime) newFloat64Array(args []Value, newTarget, proto *Object) *Objec return r._newTypedArray(args, newTarget, r.newFloat64ArrayObject, proto) } +func (r *Runtime) newBigInt64Array(args []Value, newTarget, proto *Object) *Object { + return r._newTypedArray(args, newTarget, r.newBigInt64ArrayObject, proto) +} + +func (r *Runtime) newBigUint64Array(args []Value, newTarget, proto *Object) *Object { + return r._newTypedArray(args, newTarget, r.newBigUint64ArrayObject, proto) +} + func (r *Runtime) createArrayBufferProto(val *Object) objectImpl { b := newBaseObjectObj(val, r.global.ObjectPrototype, classObject) byteLengthProp := &valueProperty{ @@ -1477,6 +1534,8 @@ func addTypedArrays(t *objectTemplate) { t.putStr("Int32Array", func(r *Runtime) Value { return valueProp(r.getInt32Array(), true, false, true) }) t.putStr("Float32Array", func(r *Runtime) Value { return valueProp(r.getFloat32Array(), true, false, true) }) t.putStr("Float64Array", func(r *Runtime) Value { return valueProp(r.getFloat64Array(), true, false, true) }) + t.putStr("BigInt64Array", func(r *Runtime) Value { return valueProp(r.getBigInt64Array(), true, false, true) }) + t.putStr("BigUint64Array", func(r *Runtime) Value { return valueProp(r.getBigUint64Array(), true, false, true) }) } func createTypedArrayProtoTemplate() *objectTemplate { @@ -1677,6 +1736,26 @@ func (r *Runtime) getFloat64Array() *Object { return ret } +func (r *Runtime) getBigInt64Array() *Object { + ret := r.global.BigInt64Array + if ret == nil { + ret = &Object{runtime: r} + r.global.BigInt64Array = ret + r.createTypedArrayCtor(ret, r.newBigInt64Array, "BigInt64Array", 8) + } + return ret +} + +func (r *Runtime) getBigUint64Array() *Object { + ret := r.global.BigUint64Array + if ret == nil { + ret = &Object{runtime: r} + r.global.BigUint64Array = ret + r.createTypedArrayCtor(ret, r.newBigUint64Array, "BigUint64Array", 8) + } + return ret +} + func createDataViewProtoTemplate() *objectTemplate { t := newObjectTemplate() t.protoFactory = func(r *Runtime) *Object { @@ -1715,6 +1794,8 @@ func createDataViewProtoTemplate() *objectTemplate { t.putStr("getUint8", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_getUint8, "getUint8", 1) }) t.putStr("getUint16", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_getUint16, "getUint16", 1) }) t.putStr("getUint32", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_getUint32, "getUint32", 1) }) + t.putStr("getBigInt64", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_getBigInt64, "getBigInt64", 1) }) + t.putStr("getBigUint64", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_getBigUint64, "getBigUint64", 1) }) t.putStr("setFloat32", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setFloat32, "setFloat32", 2) }) t.putStr("setFloat64", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setFloat64, "setFloat64", 2) }) t.putStr("setInt8", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setInt8, "setInt8", 2) }) @@ -1723,6 +1804,8 @@ func createDataViewProtoTemplate() *objectTemplate { t.putStr("setUint8", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setUint8, "setUint8", 2) }) t.putStr("setUint16", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setUint16, "setUint16", 2) }) t.putStr("setUint32", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setUint32, "setUint32", 2) }) + t.putStr("setBigInt64", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setBigInt64, "setBigInt64", 2) }) + t.putStr("setBigUint64", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setBigUint64, "setBigUint64", 2) }) t.putSym(SymToStringTag, func(r *Runtime) Value { return valueProp(asciiString("DataView"), false, false, true) }) diff --git a/compiler_expr.go b/compiler_expr.go index 477580ae..580a802a 100644 --- a/compiler_expr.go +++ b/compiler_expr.go @@ -1,6 +1,8 @@ package goja import ( + "math/big" + "github.com/dop251/goja/ast" "github.com/dop251/goja/file" "github.com/dop251/goja/token" @@ -1266,6 +1268,9 @@ func (e *compiledLiteral) emitGetter(putOnStack bool) { } func (e *compiledLiteral) constant() bool { + if _, ok := e.val.(valueBigInt); ok { + return false + } return true } @@ -3228,6 +3233,8 @@ func (c *compiler) compileNumberLiteral(v *ast.NumberLiteral) compiledExpr { val = intToValue(num) case float64: val = floatToValue(num) + case *big.Int: + val = valueBigInt{num} default: c.assert(false, int(v.Idx)-1, "Unsupported number literal type: %T", v.Value) panic("unreachable") diff --git a/compiler_test.go b/compiler_test.go index 05d82ca4..20edac59 100644 --- a/compiler_test.go +++ b/compiler_test.go @@ -14,9 +14,18 @@ function $ERROR(message) { throw new Error(message); } -function Test262Error() { +function Test262Error(message) { + this.message = message || ""; } +Test262Error.prototype.toString = function () { + return "Test262Error: " + this.message; +}; + +Test262Error.thrower = (message) => { + throw new Test262Error(message); +}; + function assert(mustBeTrue, message) { if (mustBeTrue === true) { return; diff --git a/object.go b/object.go index 79bd67df..a84ce35b 100644 --- a/object.go +++ b/object.go @@ -20,6 +20,7 @@ const ( classFunction = "Function" classAsyncFunction = "AsyncFunction" classNumber = "Number" + classBigInt = "BigInt" classString = "String" classBoolean = "Boolean" classError = "Error" diff --git a/parser/lexer.go b/parser/lexer.go index 68d56d20..320943a9 100644 --- a/parser/lexer.go +++ b/parser/lexer.go @@ -3,6 +3,7 @@ package parser import ( "errors" "fmt" + "math/big" "strconv" "strings" "unicode" @@ -911,7 +912,9 @@ func parseNumberLiteral(literal string) (value interface{}, err error) { err = parseIntErr if err.(*strconv.NumError).Err == strconv.ErrRange { - if len(literal) > 2 && literal[0] == '0' && (literal[1] == 'X' || literal[1] == 'x') { + if len(literal) > 2 && + literal[0] == '0' && (literal[1] == 'X' || literal[1] == 'x') && + literal[len(literal)-1] != 'n' { // Could just be a very large number (e.g. 0x8000000000000000) var value float64 literal = literal[2:] @@ -926,6 +929,21 @@ func parseNumberLiteral(literal string) (value interface{}, err error) { } } + if len(literal) > 1 && literal[len(literal)-1] == 'n' { + if literal[0] == '0' { + if len(literal) > 2 && isDecimalDigit(rune(literal[1])) { + goto error + } + } + // Parse as big.Int + bigInt := new(big.Int) + _, ok := bigInt.SetString(literal[:len(literal)-1], 0) + if !ok { + goto error + } + return bigInt, nil + } + error: return nil, errors.New("Illegal numeric literal") } @@ -1171,6 +1189,10 @@ func (self *_parser) scanNumericLiteral(decimalPoint bool) (token.Token, string) } } end: + if self.chr == 'n' || self.chr == 'N' { + self.read() + return tkn, self.str[offset:self.chrOffset] + } if isIdentifierStart(self.chr) || isDecimalDigit(self.chr) { return token.ILLEGAL, self.str[offset:self.chrOffset] } diff --git a/parser/lexer_test.go b/parser/lexer_test.go index 31254df7..c639be84 100644 --- a/parser/lexer_test.go +++ b/parser/lexer_test.go @@ -264,6 +264,23 @@ Second line \ token.NUMBER, "12.3", 5, ) + test(`1n`, + token.NUMBER, "1n", 1, + ) + + test(`1n 9007199254740991n`, + token.NUMBER, "1n", 1, + token.NUMBER, "9007199254740991n", 4, + ) + + test(`0xabn`, + token.NUMBER, "0xabn", 1, + ) + + test(`0xabcdef0123456789abcdef0123n`, + token.NUMBER, "0xabcdef0123456789abcdef0123n", 1, + ) + test("/ /=", token.SLASH, "", 1, token.QUOTIENT_ASSIGN, "", 3, diff --git a/parser/parser_test.go b/parser/parser_test.go index 43924194..e09a7d6c 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -2,6 +2,7 @@ package parser import ( "errors" + "math/big" "regexp" "strings" "testing" @@ -1041,6 +1042,18 @@ func Test_parseNumberLiteral(t *testing.T) { test("0", 0) test("0x8000000000000000", float64(9.223372036854776e+18)) + + test("1n", big.NewInt(1)) + + test("-1n", big.NewInt(-1)) + + test("0x23n", big.NewInt(35)) + + test("0xabcdef01n", big.NewInt(2882400001)) + + var n big.Int + n.SetString("0xabcdef0123456789abcdef0123", 0) + test("0xabcdef0123456789abcdef0123n", &n) }) } diff --git a/runtime.go b/runtime.go index ff7f7e6f..0aa81aea 100644 --- a/runtime.go +++ b/runtime.go @@ -7,6 +7,7 @@ import ( "go/ast" "hash/maphash" "math" + "math/big" "math/bits" "math/rand" "reflect" @@ -33,6 +34,7 @@ var ( typeValue = reflect.TypeOf((*Value)(nil)).Elem() typeObject = reflect.TypeOf((*Object)(nil)) typeTime = reflect.TypeOf(time.Time{}) + typeBigInt = reflect.TypeOf((*big.Int)(nil)) typeBytes = reflect.TypeOf(([]byte)(nil)) ) @@ -53,6 +55,7 @@ type global struct { Function *Object String *Object Number *Object + BigInt *Object Boolean *Object RegExp *Object Date *Object @@ -77,6 +80,8 @@ type global struct { Int32Array *Object Float32Array *Object Float64Array *Object + BigInt64Array *Object + BigUint64Array *Object WeakSet *Object WeakMap *Object @@ -97,6 +102,7 @@ type global struct { ObjectPrototype *Object ArrayPrototype *Object NumberPrototype *Object + BigIntPrototype *Object StringPrototype *Object BooleanPrototype *Object FunctionPrototype *Object @@ -830,7 +836,18 @@ func (r *Runtime) newPrimitiveObject(value Value, proto *Object, class string) * func (r *Runtime) builtin_Number(call FunctionCall) Value { if len(call.Arguments) > 0 { - return call.Arguments[0].ToNumber() + switch t := call.Arguments[0].(type) { + case *Object: + primValue := t.toPrimitiveNumber() + if bigint, ok := primValue.(valueBigInt); ok { + return intToValue(bigint.ToInteger()) + } + return primValue.ToNumber() + case valueBigInt: + return intToValue(t.ToInteger()) + default: + return t.ToNumber() + } } else { return valueInt(0) } @@ -839,7 +856,19 @@ func (r *Runtime) builtin_Number(call FunctionCall) Value { func (r *Runtime) builtin_newNumber(args []Value, proto *Object) *Object { var v Value if len(args) > 0 { - v = args[0].ToNumber() + switch t := args[0].(type) { + case *Object: + primValue := t.toPrimitiveNumber() + if bigint, ok := primValue.(valueBigInt); ok { + v = intToValue(bigint.ToInteger()) + } else { + v = primValue.ToNumber() + } + case valueBigInt: + v = intToValue(t.ToInteger()) + default: + v = t.ToNumber() + } } else { v = intToValue(0) } @@ -1832,6 +1861,8 @@ func (r *Runtime) toValue(i interface{}, origValue reflect.Value) Value { return floatToValue(float64(i)) case float64: return floatToValue(i) + case *big.Int: + return valueBigInt{i} case map[string]interface{}: if i == nil { return _null diff --git a/string.go b/string.go index 632f1e3b..0eaf3ef9 100644 --- a/string.go +++ b/string.go @@ -24,6 +24,7 @@ var ( stringString String = asciiString("string") stringSymbol String = asciiString("symbol") stringNumber String = asciiString("number") + stringBigInt String = asciiString("bigint") stringNaN String = asciiString("NaN") stringInfinity = asciiString("Infinity") stringNegInfinity = asciiString("-Infinity") diff --git a/string_ascii.go b/string_ascii.go index 5ff21bf7..dfac8fba 100644 --- a/string_ascii.go +++ b/string_ascii.go @@ -239,6 +239,14 @@ func (s asciiString) Equals(other Value) bool { return false } + if o, ok := other.(valueBigInt); ok { + bigInt, err := stringToBigInt(s.toTrimmedUTF8()) + if err != nil { + return false + } + return bigInt.Cmp(o.i) == 0 + } + if o, ok := other.(*Object); ok { return s.Equals(o.toPrimitive()) } diff --git a/tc39_test.go b/tc39_test.go index 964e2b57..1d8b9db1 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -178,10 +178,6 @@ var ( "test/language/expressions/class/cpn-class-expr-computed-property-name-from-integer-separators.js": true, "test/language/expressions/class/cpn-class-expr-fields-methods-computed-property-name-from-integer-separators.js": true, - // BigInt - "test/built-ins/Object/seal/seal-biguint64array.js": true, - "test/built-ins/Object/seal/seal-bigint64array.js": true, - // Regexp "test/language/literals/regexp/invalid-range-negative-lookbehind.js": true, "test/language/literals/regexp/invalid-range-lookbehind.js": true, @@ -204,7 +200,6 @@ var ( featuresBlackList = []string{ "async-iteration", "Symbol.asyncIterator", - "BigInt", "resizable-arraybuffer", "regexp-named-groups", "regexp-unicode-property-escapes", @@ -273,10 +268,6 @@ func init() { "test/language/eval-code/direct/async-gen-", - // BigInt - "test/built-ins/TypedArrayConstructors/BigUint64Array/", - "test/built-ins/TypedArrayConstructors/BigInt64Array/", - // restricted unicode regexp syntax "test/language/literals/regexp/u-", diff --git a/typedarrays.go b/typedarrays.go index 9af03503..765232e7 100644 --- a/typedarrays.go +++ b/typedarrays.go @@ -2,6 +2,7 @@ package goja import ( "math" + "math/big" "reflect" "strconv" "unsafe" @@ -65,6 +66,8 @@ type uint32Array []byte type int32Array []byte type float32Array []byte type float64Array []byte +type bigInt64Array []byte +type bigUint64Array []byte type typedArrayObject struct { baseObject @@ -630,6 +633,138 @@ func (a *float64Array) exportType() reflect.Type { return typeFloat64Array } +func (a *bigInt64Array) toRaw(value Value) uint64 { + return toBigInt(value).i.Uint64() +} + +func (a *bigInt64Array) ptr(idx int) *int64 { + p := unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(a)).Data) + return (*int64)(unsafe.Pointer(uintptr(p) + uintptr(idx)*8)) +} + +func (a *bigInt64Array) get(idx int) Value { + return valueBigInt{big.NewInt(*a.ptr(idx))} +} + +func toBigInt64(v Value) *big.Int { + n := toBigInt(v).i + int64bit := n.Mod(n, big.NewInt(2).Exp(big.NewInt(2), big.NewInt(64), nil)) + if int64bit.Cmp(big.NewInt(2).Exp(big.NewInt(2), big.NewInt(63), nil)) >= 0 { + return int64bit.Sub(int64bit, big.NewInt(2).Exp(big.NewInt(2), big.NewInt(64), nil)) + } + return int64bit +} + +func (a *bigInt64Array) set(idx int, value Value) { + *(a.ptr(idx)) = toBigInt64(value).Int64() +} + +func (a *bigInt64Array) getRaw(idx int) uint64 { + return uint64(*a.ptr(idx)) +} + +func (a *bigInt64Array) setRaw(idx int, raw uint64) { + *(a.ptr(idx)) = int64(raw) +} + +func (a *bigInt64Array) less(i, j int) bool { + return *(a.ptr(i)) < *(a.ptr(j)) +} + +func (a *bigInt64Array) swap(i, j int) { + pi, pj := a.ptr(i), a.ptr(j) + *pi, *pj = *pj, *pi +} + +func (a *bigInt64Array) typeMatch(v Value) bool { + if _, ok := v.(valueBigInt); ok { + return true + } + return false +} + +func (a *bigInt64Array) export(offset int, length int) interface{} { + var res []int64 + sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&res)) + sliceHeader.Data = (*reflect.SliceHeader)(unsafe.Pointer(a)).Data + uintptr(offset)*8 + sliceHeader.Len = length + sliceHeader.Cap = length + ret := make([]*big.Int, 0, length) + for _, v := range res { + ret = append(ret, big.NewInt(v)) + } + return ret +} + +var typeBigIntArray = reflect.TypeOf(([]*big.Int)(nil)) + +func (a *bigInt64Array) exportType() reflect.Type { + return typeBigIntArray +} + +func (a *bigUint64Array) toRaw(value Value) uint64 { + return toBigInt(value).i.Uint64() +} + +func (a *bigUint64Array) ptr(idx int) *uint64 { + p := unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(a)).Data) + return (*uint64)(unsafe.Pointer(uintptr(p) + uintptr(idx)*8)) +} + +func (a *bigUint64Array) get(idx int) Value { + return valueBigInt{new(big.Int).SetUint64(*a.ptr(idx))} +} + +func toBigUint64(v Value) *big.Int { + n := toBigInt(v).i + return n.Mod(n, big.NewInt(2).Exp(big.NewInt(2), big.NewInt(64), nil)) +} + +func (a *bigUint64Array) set(idx int, value Value) { + *(a.ptr(idx)) = toBigUint64(value).Uint64() +} + +func (a *bigUint64Array) getRaw(idx int) uint64 { + return *a.ptr(idx) +} + +func (a *bigUint64Array) setRaw(idx int, raw uint64) { + *(a.ptr(idx)) = raw +} + +func (a *bigUint64Array) less(i, j int) bool { + return *(a.ptr(i)) < *(a.ptr(j)) +} + +func (a *bigUint64Array) swap(i, j int) { + pi, pj := a.ptr(i), a.ptr(j) + *pi, *pj = *pj, *pi +} + +func (a *bigUint64Array) typeMatch(v Value) bool { + if _, ok := v.(valueBigInt); ok { + return true + } + return false +} + +func (a *bigUint64Array) export(offset int, length int) interface{} { + var res []uint64 + sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&res)) + sliceHeader.Data = (*reflect.SliceHeader)(unsafe.Pointer(a)).Data + uintptr(offset)*8 + sliceHeader.Len = length + sliceHeader.Cap = length + ret := make([]*big.Int, 0, length) + for _, v := range res { + ret = append(ret, new(big.Int).SetUint64(v)) + } + return ret +} + +func (a *bigUint64Array) exportType() reflect.Type { + return typeBigIntArray +} + func (a *typedArrayObject) _getIdx(idx int) Value { if 0 <= idx && idx < a.length { if !a.viewedArrayBuf.ensureNotDetached(false) { @@ -698,7 +833,12 @@ func (a *typedArrayObject) isValidIntegerIndex(idx int) bool { } func (a *typedArrayObject) _putIdx(idx int, v Value) { - v = v.ToNumber() + if a.defaultCtor == a.val.runtime.global.BigInt64Array || + a.defaultCtor == a.val.runtime.global.BigUint64Array { + v = toBigInt(v) + } else { + v = v.ToNumber() + } if a.isValidIntegerIndex(idx) { a.typedArray.set(idx+a.offset, v) } @@ -715,7 +855,7 @@ func (a *typedArrayObject) setOwnStr(p unistring.String, v Value, throw bool) bo return true } if idx == 0 { - v.ToNumber() // make sure it throws + toNumeric(v) // make sure it throws return true } return a.baseObject.setOwnStr(p, v, throw) @@ -938,6 +1078,14 @@ func (r *Runtime) newFloat64ArrayObject(buf *arrayBufferObject, offset, length i return r._newTypedArrayObject(buf, offset, length, 8, r.global.Float64Array, (*float64Array)(&buf.data), proto) } +func (r *Runtime) newBigInt64ArrayObject(buf *arrayBufferObject, offset, length int, proto *Object) *typedArrayObject { + return r._newTypedArrayObject(buf, offset, length, 8, r.global.BigInt64Array, (*bigInt64Array)(&buf.data), proto) +} + +func (r *Runtime) newBigUint64ArrayObject(buf *arrayBufferObject, offset, length int, proto *Object) *typedArrayObject { + return r._newTypedArrayObject(buf, offset, length, 8, r.global.BigUint64Array, (*bigUint64Array)(&buf.data), proto) +} + func (o *dataViewObject) getIdxAndByteOrder(getIdx int, littleEndianVal Value, size int) (int, byteOrder) { o.viewedArrayBuf.ensureNotDetached(true) if getIdx+size > o.byteLen { @@ -1026,6 +1174,52 @@ func (o *arrayBufferObject) setUint32(idx int, val uint32, byteOrder byteOrder) } } +func (o *arrayBufferObject) getBigInt64(idx int, byteOrder byteOrder) *big.Int { + var b []byte + if byteOrder == nativeEndian { + b = o.data[idx : idx+8] + } else { + b = make([]byte, 8) + d := o.data[idx : idx+8] + b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7] = d[7], d[6], d[5], d[4], d[3], d[2], d[1], d[0] + } + return big.NewInt(*((*int64)(unsafe.Pointer(&b[0])))) +} + +func (o *arrayBufferObject) getBigUint64(idx int, byteOrder byteOrder) *big.Int { + var b []byte + if byteOrder == nativeEndian { + b = o.data[idx : idx+8] + } else { + b = make([]byte, 8) + d := o.data[idx : idx+8] + b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7] = d[7], d[6], d[5], d[4], d[3], d[2], d[1], d[0] + } + return new(big.Int).SetUint64(*((*uint64)(unsafe.Pointer(&b[0])))) +} + +func (o *arrayBufferObject) setBigInt64(idx int, val *big.Int, byteOrder byteOrder) { + if byteOrder == nativeEndian { + *(*int64)(unsafe.Pointer(&o.data[idx])) = val.Int64() + } else { + n := val.Int64() + b := (*[8]byte)(unsafe.Pointer(&n)) + d := o.data[idx : idx+8] + d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7] = b[7], b[6], b[5], b[4], b[3], b[2], b[1], b[0] + } +} + +func (o *arrayBufferObject) setBigUint64(idx int, val *big.Int, byteOrder byteOrder) { + if byteOrder == nativeEndian { + *(*uint64)(unsafe.Pointer(&o.data[idx])) = val.Uint64() + } else { + n := val.Uint64() + b := (*[8]byte)(unsafe.Pointer(&n)) + d := o.data[idx : idx+8] + d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7] = b[7], b[6], b[5], b[4], b[3], b[2], b[1], b[0] + } +} + func (o *arrayBufferObject) getUint16(idx int, byteOrder byteOrder) uint16 { var b []byte if byteOrder == nativeEndian { diff --git a/typedarrays_test.go b/typedarrays_test.go index 2da2e8a3..d361d841 100644 --- a/typedarrays_test.go +++ b/typedarrays_test.go @@ -2,6 +2,7 @@ package goja import ( "bytes" + "math/big" "testing" ) @@ -513,4 +514,32 @@ func TestTypedArrayExport(t *testing.T) { } }) + t.Run("bigint64", func(t *testing.T) { + v, err := vm.RunString("new BigInt64Array([18446744073709551617n, 2n])") + if err != nil { + t.Fatal(err) + } + if a, ok := v.Export().([]*big.Int); ok { + if len(a) != 2 || a[0].Cmp(big.NewInt(1)) != 0 || a[1].Cmp(big.NewInt(2)) != 0 { + t.Fatal(a) + } + } else { + t.Fatal("Wrong export type") + } + }) + + t.Run("biguint64", func(t *testing.T) { + v, err := vm.RunString("new BigUint64Array([18446744073709551617n, 2n])") + if err != nil { + t.Fatal(err) + } + if a, ok := v.Export().([]*big.Int); ok { + if len(a) != 2 || a[0].Cmp(big.NewInt(1)) != 0 || a[1].Cmp(big.NewInt(2)) != 0 { + t.Fatal(a) + } + } else { + t.Fatal("Wrong export type") + } + }) + } diff --git a/value.go b/value.go index e5031e72..56f0081b 100644 --- a/value.go +++ b/value.go @@ -4,6 +4,7 @@ import ( "fmt" "hash/maphash" "math" + "math/big" "reflect" "strconv" "unsafe" @@ -141,6 +142,7 @@ type valueProperty struct { var ( errAccessBeforeInit = referenceError("Cannot access a variable before initialization") errAssignToConst = typeError("Assignment to constant variable.") + errMixBigIntType = typeError("Cannot mix BigInt and other types, use explicit conversions") ) func propGetter(o Value, v Value, r *Runtime) *Object { @@ -218,6 +220,8 @@ func (i valueInt) Equals(other Value) bool { switch o := other.(type) { case valueInt: return i == o + case valueBigInt: + return o.i.Cmp(big.NewInt(int64(i))) == 0 case valueFloat: return float64(i) == float64(o) case String: @@ -643,6 +647,15 @@ func (f valueFloat) Equals(other Value) bool { return f == o case valueInt: return float64(f) == float64(o) + case valueBigInt: + if IsInfinity(f) || math.IsNaN(float64(f)) { + return false + } + if f := big.NewFloat(float64(f)); f.IsInt() { + i, _ := f.Int(nil) + return o.i.Cmp(i) == 0 + } + return false case String, valueBool: return float64(f) == o.ToFloat() case *Object: @@ -728,7 +741,7 @@ func (o *Object) Equals(other Value) bool { } switch o1 := other.(type) { - case valueInt, valueFloat, String, *Symbol: + case valueInt, valueFloat, valueBigInt, String, *Symbol: return o.toPrimitive().Equals(other) case valueBool: return o.Equals(o1.ToNumber()) diff --git a/vm.go b/vm.go index 30b79331..d859fb49 100644 --- a/vm.go +++ b/vm.go @@ -3,6 +3,7 @@ package goja import ( "fmt" "math" + "math/big" "strconv" "strings" "sync" @@ -386,6 +387,22 @@ func assertInt64(v Value) (int64, bool) { return 0, false } +func toNumeric(value Value) Value { + switch v := value.(type) { + case valueInt, valueBigInt: + return v + case valueFloat: + return floatToValue(float64(v)) + case *Object: + primValue := v.toPrimitiveNumber() + if bigint, ok := primValue.(valueBigInt); ok { + return bigint + } + return primValue.ToNumber() + } + return value.ToNumber() +} + func (s *valueStack) expand(idx int) { if idx < len(*s) { return @@ -1194,7 +1211,7 @@ type _toNumber struct{} var toNumber _toNumber func (_toNumber) exec(vm *vm) { - vm.stack[vm.sp-1] = vm.stack[vm.sp-1].ToNumber() + vm.stack[vm.sp-1] = toNumeric(vm.stack[vm.sp-1]) vm.pc++ } @@ -1228,13 +1245,26 @@ func (_add) exec(vm *vm) { } ret = leftString.Concat(rightString) } else { - if leftInt, ok := left.(valueInt); ok { - if rightInt, ok := right.(valueInt); ok { - ret = intToValue(int64(leftInt) + int64(rightInt)) + switch left := left.(type) { + case valueInt: + switch right := right.(type) { + case valueInt: + ret = intToValue(int64(left) + int64(right)) + case valueBigInt: + panic(errMixBigIntType) + default: + ret = floatToValue(float64(left) + right.ToFloat()) + } + case valueBigInt: + if right, ok := right.(valueBigInt); ok { + ret = valueBigInt{new(big.Int).Add(left.i, right.i)} } else { - ret = floatToValue(float64(leftInt) + right.ToFloat()) + panic(errMixBigIntType) + } + default: + if _, ok := right.(valueBigInt); ok { + panic(errMixBigIntType) } - } else { ret = floatToValue(left.ToFloat() + right.ToFloat()) } } @@ -1252,13 +1282,30 @@ func (_sub) exec(vm *vm) { right := vm.stack[vm.sp-1] left := vm.stack[vm.sp-2] + left = toNumeric(left) + right = toNumeric(right) + var result Value - if left, ok := left.(valueInt); ok { - if right, ok := right.(valueInt); ok { + switch left := left.(type) { + case valueInt: + switch right := right.(type) { + case valueInt: result = intToValue(int64(left) - int64(right)) goto end + case valueBigInt: + panic(errMixBigIntType) + } + case valueFloat: + if _, ok := right.(valueBigInt); ok { + panic(errMixBigIntType) + } + case valueBigInt: + if right, ok := right.(valueBigInt); ok { + result = valueBigInt{new(big.Int).Sub(left.i, right.i)} + goto end } + panic(errMixBigIntType) } result = floatToValue(left.ToFloat() - right.ToFloat()) @@ -1273,13 +1320,15 @@ type _mul struct{} var mul _mul func (_mul) exec(vm *vm) { - left := vm.stack[vm.sp-2] - right := vm.stack[vm.sp-1] + left := toNumeric(vm.stack[vm.sp-2]) + right := toNumeric(vm.stack[vm.sp-1]) var result Value - if left, ok := assertInt64(left); ok { - if right, ok := assertInt64(right); ok { + switch left := left.(type) { + case valueInt: + switch right := right.(type) { + case valueInt: if left == 0 && right == -1 || left == -1 && right == 0 { result = _negativeZero goto end @@ -1287,11 +1336,22 @@ func (_mul) exec(vm *vm) { res := left * right // check for overflow if left == 0 || right == 0 || res/left == right { - result = intToValue(res) + result = intToValue(int64(res)) goto end } - + case valueBigInt: + panic(errMixBigIntType) + } + case valueFloat: + if _, ok := right.(valueBigInt); ok { + panic(errMixBigIntType) + } + case valueBigInt: + if right, ok := right.(valueBigInt); ok { + result = valueBigInt{new(big.Int).Mul(left.i, right.i)} + goto end } + panic(errMixBigIntType) } result = floatToValue(left.ToFloat() * right.ToFloat()) @@ -1308,7 +1368,29 @@ var exp _exp func (_exp) exec(vm *vm) { vm.sp-- - vm.stack[vm.sp-1] = pow(vm.stack[vm.sp-1], vm.stack[vm.sp]) + x := vm.stack[vm.sp-1] + y := vm.stack[vm.sp] + + x = toNumeric(x) + y = toNumeric(y) + + var result Value + if x, ok := x.(valueBigInt); ok { + if y, ok := y.(valueBigInt); ok { + if y.i.Cmp(big.NewInt(0)) < 0 { + panic(vm.r.newError(vm.r.getRangeError(), "exponent must be positive")) + } + result = valueBigInt{new(big.Int).Exp(x.i, y.i, nil)} + goto end + } + panic(errMixBigIntType) + } else if _, ok := y.(valueBigInt); ok { + panic(errMixBigIntType) + } + + result = pow(x, y) +end: + vm.stack[vm.sp-1] = result vm.pc++ } @@ -1317,10 +1399,32 @@ type _div struct{} var div _div func (_div) exec(vm *vm) { - left := vm.stack[vm.sp-2].ToFloat() - right := vm.stack[vm.sp-1].ToFloat() - - var result Value + leftValue := toNumeric(vm.stack[vm.sp-2]) + rightValue := toNumeric(vm.stack[vm.sp-1]) + + var ( + result Value + left, right float64 + ) + + if left, ok := leftValue.(valueBigInt); ok { + if right, ok := rightValue.(valueBigInt); ok { + if right.i.Cmp(big.NewInt(0)) == 0 { + panic(vm.r.newError(vm.r.getRangeError(), "Division by zero")) + } + if left.i.CmpAbs(right.i) < 0 { + result = valueBigInt{big.NewInt(0)} + } else { + i, _ := new(big.Int).QuoRem(left.i, right.i, big.NewInt(0)) + result = valueBigInt{i} + } + goto end + } + panic(errMixBigIntType) + } else if _, ok := rightValue.(valueBigInt); ok { + panic(errMixBigIntType) + } + left, right = leftValue.ToFloat(), rightValue.ToFloat() if math.IsNaN(left) || math.IsNaN(right) { result = _NaN @@ -1376,25 +1480,48 @@ type _mod struct{} var mod _mod func (_mod) exec(vm *vm) { - left := vm.stack[vm.sp-2] - right := vm.stack[vm.sp-1] + left := toNumeric(vm.stack[vm.sp-2]) + right := toNumeric(vm.stack[vm.sp-1]) var result Value - if leftInt, ok := assertInt64(left); ok { - if rightInt, ok := assertInt64(right); ok { - if rightInt == 0 { + switch left := left.(type) { + case valueInt: + switch right := right.(type) { + case valueInt: + if right == 0 { result = _NaN goto end } - r := leftInt % rightInt - if r == 0 && leftInt < 0 { + r := left % right + if r == 0 && left < 0 { result = _negativeZero } else { - result = intToValue(leftInt % rightInt) + result = intToValue(int64(left % right)) + } + goto end + case valueBigInt: + panic(errMixBigIntType) + } + case valueFloat: + if _, ok := right.(valueBigInt); ok { + panic(errMixBigIntType) + } + case valueBigInt: + if right, ok := right.(valueBigInt); ok { + switch { + case right.i.Cmp(big.NewInt(0)) == 0: + panic(vm.r.newError(vm.r.getRangeError(), "Division by zero")) + case left.i.Cmp(big.NewInt(0)) < 0: + abs := left.i.Abs(left.i) + v := new(big.Int).Mod(abs, right.i) + result = valueBigInt{v.Neg(v)} + default: + result = valueBigInt{new(big.Int).Mod(left.i, right.i)} } goto end } + panic(errMixBigIntType) } result = floatToValue(math.Mod(left.ToFloat(), right.ToFloat())) @@ -1413,13 +1540,16 @@ func (_neg) exec(vm *vm) { var result Value - if i, ok := assertInt64(operand); ok { - if i == 0 { + switch n := toNumeric(operand).(type) { + case valueBigInt: + result = valueBigInt{new(big.Int).Neg(n.i)} + case valueInt: + if n == 0 { result = _negativeZero } else { - result = valueInt(-i) + result = -n } - } else { + default: f := operand.ToFloat() if !math.IsNaN(f) { f = -f @@ -1447,14 +1577,15 @@ var inc _inc func (_inc) exec(vm *vm) { v := vm.stack[vm.sp-1] - if i, ok := assertInt64(v); ok { - v = intToValue(i + 1) - goto end + switch n := v.(type) { + case valueBigInt: + v = valueBigInt{new(big.Int).Add(n.i, big.NewInt(1))} + case valueInt: + v = intToValue(int64(n + 1)) + default: + v = valueFloat(n.ToFloat() + 1) } - v = valueFloat(v.ToFloat() + 1) - -end: vm.stack[vm.sp-1] = v vm.pc++ } @@ -1466,14 +1597,15 @@ var dec _dec func (_dec) exec(vm *vm) { v := vm.stack[vm.sp-1] - if i, ok := assertInt64(v); ok { - v = intToValue(i - 1) - goto end + switch n := v.(type) { + case valueBigInt: + v = valueBigInt{new(big.Int).Sub(n.i, big.NewInt(1))} + case valueInt: + v = intToValue(int64(n - 1)) + default: + v = valueFloat(n.ToFloat() - 1) } - v = valueFloat(v.ToFloat() - 1) - -end: vm.stack[vm.sp-1] = v vm.pc++ } @@ -1483,9 +1615,23 @@ type _and struct{} var and _and func (_and) exec(vm *vm) { - left := toInt32(vm.stack[vm.sp-2]) - right := toInt32(vm.stack[vm.sp-1]) - vm.stack[vm.sp-2] = intToValue(int64(left & right)) + left := toNumeric(vm.stack[vm.sp-2]) + right := toNumeric(vm.stack[vm.sp-1]) + var result Value + + if left, ok := left.(valueBigInt); ok { + if right, ok := right.(valueBigInt); ok { + result = valueBigInt{new(big.Int).And(left.i, right.i)} + goto end + } + panic(errMixBigIntType) + } else if _, ok := right.(valueBigInt); ok { + panic(errMixBigIntType) + } + + result = intToValue(int64(toInt32(left) & toInt32(right))) +end: + vm.stack[vm.sp-2] = result vm.sp-- vm.pc++ } @@ -1495,9 +1641,23 @@ type _or struct{} var or _or func (_or) exec(vm *vm) { - left := toInt32(vm.stack[vm.sp-2]) - right := toInt32(vm.stack[vm.sp-1]) - vm.stack[vm.sp-2] = intToValue(int64(left | right)) + left := toNumeric(vm.stack[vm.sp-2]) + right := toNumeric(vm.stack[vm.sp-1]) + var result Value + + if left, ok := left.(valueBigInt); ok { + if right, ok := right.(valueBigInt); ok { + result = valueBigInt{new(big.Int).Or(left.i, right.i)} + goto end + } + panic(errMixBigIntType) + } else if _, ok := right.(valueBigInt); ok { + panic(errMixBigIntType) + } + + result = intToValue(int64(toInt32(left) | toInt32(right))) +end: + vm.stack[vm.sp-2] = result vm.sp-- vm.pc++ } @@ -1507,9 +1667,23 @@ type _xor struct{} var xor _xor func (_xor) exec(vm *vm) { - left := toInt32(vm.stack[vm.sp-2]) - right := toInt32(vm.stack[vm.sp-1]) - vm.stack[vm.sp-2] = intToValue(int64(left ^ right)) + left := toNumeric(vm.stack[vm.sp-2]) + right := toNumeric(vm.stack[vm.sp-1]) + var result Value + + if left, ok := left.(valueBigInt); ok { + if right, ok := right.(valueBigInt); ok { + result = valueBigInt{new(big.Int).Xor(left.i, right.i)} + goto end + } + panic(errMixBigIntType) + } else if _, ok := right.(valueBigInt); ok { + panic(errMixBigIntType) + } + + result = intToValue(int64(toInt32(left) ^ toInt32(right))) +end: + vm.stack[vm.sp-2] = result vm.sp-- vm.pc++ } @@ -1519,8 +1693,14 @@ type _bnot struct{} var bnot _bnot func (_bnot) exec(vm *vm) { - op := toInt32(vm.stack[vm.sp-1]) - vm.stack[vm.sp-1] = intToValue(int64(^op)) + v := vm.stack[vm.sp-1] + switch n := toNumeric(v).(type) { + case valueBigInt: + v = valueBigInt{new(big.Int).Not(n.i)} + default: + v = intToValue(int64(^toInt32(n))) + } + vm.stack[vm.sp-1] = v vm.pc++ } @@ -1529,9 +1709,27 @@ type _sal struct{} var sal _sal func (_sal) exec(vm *vm) { - left := toInt32(vm.stack[vm.sp-2]) - right := toUint32(vm.stack[vm.sp-1]) - vm.stack[vm.sp-2] = intToValue(int64(left << (right & 0x1F))) + left := toNumeric(vm.stack[vm.sp-2]) + right := toNumeric(vm.stack[vm.sp-1]) + var result Value + + if left, ok := left.(valueBigInt); ok { + if right, ok := right.(valueBigInt); ok { + if right.i.Sign() < 0 { + result = valueBigInt{new(big.Int).Rsh(left.i, uint(right.i.Uint64()))} + } else { + result = valueBigInt{new(big.Int).Lsh(left.i, uint(right.i.Uint64()))} + } + goto end + } + panic(errMixBigIntType) + } else if _, ok := right.(valueBigInt); ok { + panic(errMixBigIntType) + } + + result = intToValue(int64(toInt32(left) << (toUint32(right) & 0x1F))) +end: + vm.stack[vm.sp-2] = result vm.sp-- vm.pc++ } @@ -1541,9 +1739,27 @@ type _sar struct{} var sar _sar func (_sar) exec(vm *vm) { - left := toInt32(vm.stack[vm.sp-2]) - right := toUint32(vm.stack[vm.sp-1]) - vm.stack[vm.sp-2] = intToValue(int64(left >> (right & 0x1F))) + left := toNumeric(vm.stack[vm.sp-2]) + right := toNumeric(vm.stack[vm.sp-1]) + var result Value + + if left, ok := left.(valueBigInt); ok { + if right, ok := right.(valueBigInt); ok { + if right.i.Sign() < 0 { + result = valueBigInt{new(big.Int).Lsh(left.i, uint(right.i.Uint64()))} + } else { + result = valueBigInt{new(big.Int).Rsh(left.i, uint(right.i.Uint64()))} + } + goto end + } + panic(errMixBigIntType) + } else if _, ok := right.(valueBigInt); ok { + panic(errMixBigIntType) + } + + result = intToValue(int64(toInt32(left) >> (toUint32(right) & 0x1F))) +end: + vm.stack[vm.sp-2] = result vm.sp-- vm.pc++ } @@ -1553,9 +1769,17 @@ type _shr struct{} var shr _shr func (_shr) exec(vm *vm) { - left := toUint32(vm.stack[vm.sp-2]) - right := toUint32(vm.stack[vm.sp-1]) - vm.stack[vm.sp-2] = intToValue(int64(left >> (right & 0x1F))) + left := toNumeric(vm.stack[vm.sp-2]) + right := toNumeric(vm.stack[vm.sp-1]) + + if _, ok := left.(valueBigInt); ok { + _ = toNumeric(right) + panic(vm.r.NewTypeError("BigInts have no unsigned right shift, use >> instead")) + } else if _, ok := right.(valueBigInt); ok { + panic(vm.r.NewTypeError("BigInts have no unsigned right shift, use >> instead")) + } + + vm.stack[vm.sp-2] = intToValue(int64(toUint32(left) >> (toUint32(right) & 0x1F))) vm.sp-- vm.pc++ } @@ -4180,31 +4404,91 @@ func toPrimitive(v Value) Value { func cmp(px, py Value) Value { var ret bool - var nx, ny float64 + xs, isPxString := px.(String) + ys, isPyString := py.(String) - if xs, ok := px.(String); ok { - if ys, ok := py.(String); ok { - ret = xs.CompareTo(ys) < 0 + if isPxString && isPyString { + ret = xs.CompareTo(ys) < 0 + goto end + } else { + if px, ok := px.(valueBigInt); ok && isPyString { + ny, err := stringToBigInt(ys.toTrimmedUTF8()) + if err != nil { + return _undefined + } + ret = px.i.Cmp(ny) < 0 + goto end + } + if py, ok := py.(valueBigInt); ok && isPxString { + nx, err := stringToBigInt(xs.toTrimmedUTF8()) + if err != nil { + return _undefined + } + ret = nx.Cmp(py.i) < 0 goto end } } - if xi, ok := px.(valueInt); ok { - if yi, ok := py.(valueInt); ok { - ret = xi < yi + switch nx := px.(type) { + case valueInt: + switch ny := py.(type) { + case valueInt: + ret = nx < ny + goto end + case valueBigInt: + ret = big.NewInt(int64(nx)).Cmp(ny.i) < 0 + goto end + } + case valueFloat: + switch ny := py.(type) { + case valueBigInt: + switch { + case math.IsNaN(float64(nx)): + return _undefined + case nx == _negativeInf: + ret = true + goto end + } + if nx := big.NewFloat(float64(nx)); nx.IsInt() { + nx, _ := nx.Int(nil) + ret = nx.Cmp(ny.i) < 0 + } else { + ret = nx.Cmp(big.NewFloat(0).SetInt(ny.i)) < 0 + } + goto end + } + case valueBigInt: + switch ny := py.(type) { + case valueInt: + ret = nx.i.Cmp(big.NewInt(int64(ny))) < 0 + goto end + case valueFloat: + switch { + case math.IsNaN(float64(ny)): + return _undefined + case ny == _positiveInf: + ret = true + goto end + } + if ny := big.NewFloat(float64(ny)); ny.IsInt() { + ny, _ := ny.Int(nil) + ret = nx.i.Cmp(ny) < 0 + } else { + ret = big.NewFloat(0).SetInt(nx.i).Cmp(ny) < 0 + } + goto end + case valueBigInt: + ret = nx.i.Cmp(ny.i) < 0 goto end } } - nx = px.ToFloat() - ny = py.ToFloat() - - if math.IsNaN(nx) || math.IsNaN(ny) { + if nx, ny := px.ToFloat(), py.ToFloat(); math.IsNaN(nx) || math.IsNaN(ny) { return _undefined + } else { + ret = nx < ny } - ret = nx < ny - end: if ret { return valueTrue @@ -4558,6 +4842,8 @@ func (_typeof) exec(vm *vm) { r = stringString case valueInt, valueFloat: r = stringNumber + case valueBigInt: + r = stringBigInt case *Symbol: r = stringSymbol default: From 04d3d60e424a38941fa2066b09b00cbf7622721c Mon Sep 17 00:00:00 2001 From: shiroyk Date: Sun, 18 Aug 2024 08:25:36 +0800 Subject: [PATCH 02/16] Fixed panic if target ArrayBuffer gets detached during with, toReversed, toSorted --- builtin_typedarrays.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/builtin_typedarrays.go b/builtin_typedarrays.go index 9ddb9c03..2688f56b 100644 --- a/builtin_typedarrays.go +++ b/builtin_typedarrays.go @@ -1211,6 +1211,7 @@ func (r *Runtime) typedArrayProto_with(call FunctionCall) Value { if !ok { panic(r.NewTypeError("%s is not a valid TypedArray", r.objectproto_toString(FunctionCall{This: call.This}))) } + ta.viewedArrayBuf.ensureNotDetached(true) length := ta.length relativeIndex := call.Argument(0).ToInteger() var actualIndex int @@ -1224,10 +1225,7 @@ func (r *Runtime) typedArrayProto_with(call FunctionCall) Value { panic(r.newError(r.getRangeError(), "Invalid typed array index")) } - // TODO BigInt - // 7. If O.[[ContentType]] is BIGINT, let numericValue be ? ToBigInt(value). - // 8. Else, let numericValue be ? ToNumber(value). - numericValue := call.Argument(1).ToNumber() + numericValue := toNumeric(call.Argument(1)) a := r.typedArrayCreate(ta.defaultCtor, intToValue(int64(length))) for k := 0; k < length; k++ { @@ -1248,6 +1246,7 @@ func (r *Runtime) typedArrayProto_toReversed(call FunctionCall) Value { if !ok { panic(r.NewTypeError("%s is not a valid TypedArray", r.objectproto_toString(FunctionCall{This: call.This}))) } + ta.viewedArrayBuf.ensureNotDetached(true) length := ta.length a := r.typedArrayCreate(ta.defaultCtor, intToValue(int64(length))) @@ -1267,6 +1266,7 @@ func (r *Runtime) typedArrayProto_toSorted(call FunctionCall) Value { if !ok { panic(r.NewTypeError("%s is not a valid TypedArray", r.objectproto_toString(FunctionCall{This: call.This}))) } + ta.viewedArrayBuf.ensureNotDetached(true) var compareFn func(FunctionCall) Value arg := call.Argument(0) From 39dc0e144ba8f346614dd1c783622463fb0411bc Mon Sep 17 00:00:00 2001 From: shiroyk Date: Sun, 18 Aug 2024 11:14:05 +0800 Subject: [PATCH 03/16] Fixed *big.Int no method Float64 in Go version 1.20 --- builtin_bigint.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin_bigint.go b/builtin_bigint.go index c41c6980..65a4d828 100644 --- a/builtin_bigint.go +++ b/builtin_bigint.go @@ -37,7 +37,7 @@ func (v valueBigInt) String() string { } func (v valueBigInt) ToFloat() float64 { - f, _ := v.i.Float64() + f, _ := new(big.Float).SetInt(v.i).Float64() return f } From b230c59988caf4b12db7a05cb4ecec1a8a79a387 Mon Sep 17 00:00:00 2001 From: shiroyk Date: Sun, 18 Aug 2024 11:25:14 +0800 Subject: [PATCH 04/16] Remove unused function assertInt64 --- vm.go | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/vm.go b/vm.go index 8835ba81..8dececa0 100644 --- a/vm.go +++ b/vm.go @@ -416,19 +416,6 @@ func floatToValue(f float64) (result Value) { return valueFloat(f) } -func assertInt64(v Value) (int64, bool) { - num := v.ToNumber() - if i, ok := num.(valueInt); ok { - return int64(i), true - } - if f, ok := num.(valueFloat); ok { - if i, ok := floatToInt(float64(f)); ok { - return i, true - } - } - return 0, false -} - func toNumeric(value Value) Value { switch v := value.(type) { case valueInt, valueBigInt: From 6017f8f1a3ed7d5b59e370fd219ad909c7787afd Mon Sep 17 00:00:00 2001 From: shiroyk Date: Sun, 18 Aug 2024 15:20:47 +0800 Subject: [PATCH 05/16] Fixed check BigInt64Array --- builtin_typedarrays.go | 7 ++++++- typedarrays.go | 9 ++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/builtin_typedarrays.go b/builtin_typedarrays.go index 2688f56b..f821f861 100644 --- a/builtin_typedarrays.go +++ b/builtin_typedarrays.go @@ -1225,7 +1225,12 @@ func (r *Runtime) typedArrayProto_with(call FunctionCall) Value { panic(r.newError(r.getRangeError(), "Invalid typed array index")) } - numericValue := toNumeric(call.Argument(1)) + var numericValue Value + if ta.typedArray.exportType() == typeBigInt64Array { + numericValue = toBigInt(call.Argument(1)) + } else { + numericValue = call.Argument(1).ToNumber() + } a := r.typedArrayCreate(ta.defaultCtor, intToValue(int64(length))) for k := 0; k < length; k++ { diff --git a/typedarrays.go b/typedarrays.go index 765232e7..2b1808ff 100644 --- a/typedarrays.go +++ b/typedarrays.go @@ -696,10 +696,10 @@ func (a *bigInt64Array) export(offset int, length int) interface{} { return ret } -var typeBigIntArray = reflect.TypeOf(([]*big.Int)(nil)) +var typeBigInt64Array = reflect.TypeOf(([]*big.Int)(nil)) func (a *bigInt64Array) exportType() reflect.Type { - return typeBigIntArray + return typeBigInt64Array } func (a *bigUint64Array) toRaw(value Value) uint64 { @@ -762,7 +762,7 @@ func (a *bigUint64Array) export(offset int, length int) interface{} { } func (a *bigUint64Array) exportType() reflect.Type { - return typeBigIntArray + return typeBigInt64Array } func (a *typedArrayObject) _getIdx(idx int) Value { @@ -833,8 +833,7 @@ func (a *typedArrayObject) isValidIntegerIndex(idx int) bool { } func (a *typedArrayObject) _putIdx(idx int, v Value) { - if a.defaultCtor == a.val.runtime.global.BigInt64Array || - a.defaultCtor == a.val.runtime.global.BigUint64Array { + if a.typedArray.exportType() == typeBigInt64Array { v = toBigInt(v) } else { v = v.ToNumber() From 76da319f091c52c45edf30e5a40e31819758a7fd Mon Sep 17 00:00:00 2001 From: shiroyk Date: Sun, 18 Aug 2024 15:34:02 +0800 Subject: [PATCH 06/16] Fixed BigInt hash --- builtin_bigint.go | 14 ++++++++++++-- map_test.go | 3 +++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/builtin_bigint.go b/builtin_bigint.go index 65a4d828..5db70844 100644 --- a/builtin_bigint.go +++ b/builtin_bigint.go @@ -109,8 +109,18 @@ func (v valueBigInt) baseObject(rt *Runtime) *Object { return rt.getBigIntPrototype() } -func (v valueBigInt) hash(*maphash.Hash) uint64 { - return v.i.Uint64() +func (v valueBigInt) hash(hash *maphash.Hash) uint64 { + var sign byte + if v.i.Sign() < 0 { + sign = 0x01 + } else { + sign = 0x00 + } + _ = hash.WriteByte(sign) + _, _ = hash.Write(v.i.Bytes()) + h := hash.Sum64() + hash.Reset() + return h } func toBigInt(value Value) valueBigInt { diff --git a/map_test.go b/map_test.go index 7d41e767..71993f42 100644 --- a/map_test.go +++ b/map_test.go @@ -3,6 +3,7 @@ package goja import ( "hash/maphash" "math" + "math/big" "strconv" "testing" ) @@ -25,6 +26,8 @@ func TestMapHash(t *testing.T) { testMapHashVal(floatToValue(1.2345), floatToValue(1.2345), true, t) testMapHashVal(SymIterator, SymToStringTag, false, t) testMapHashVal(SymIterator, SymIterator, true, t) + testMapHashVal(valueBigInt{big.NewInt(1)}, valueBigInt{big.NewInt(-1)}, false, t) + testMapHashVal(valueBigInt{big.NewInt(1)}, valueBigInt{big.NewInt(1)}, true, t) // The following tests introduce indeterministic behaviour //testMapHashVal(asciiString("Test"), asciiString("Test1"), false, t) From 9cf3c65a73f3fd70abf324190956ee7217db15cb Mon Sep 17 00:00:00 2001 From: shiroyk Date: Sun, 18 Aug 2024 16:06:50 +0800 Subject: [PATCH 07/16] Fixed BigInt64Array toRaw --- typedarrays.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/typedarrays.go b/typedarrays.go index 2b1808ff..e6ffe31d 100644 --- a/typedarrays.go +++ b/typedarrays.go @@ -634,7 +634,7 @@ func (a *float64Array) exportType() reflect.Type { } func (a *bigInt64Array) toRaw(value Value) uint64 { - return toBigInt(value).i.Uint64() + return toBigInt64(value).Uint64() } func (a *bigInt64Array) ptr(idx int) *int64 { @@ -648,7 +648,7 @@ func (a *bigInt64Array) get(idx int) Value { func toBigInt64(v Value) *big.Int { n := toBigInt(v).i - int64bit := n.Mod(n, big.NewInt(2).Exp(big.NewInt(2), big.NewInt(64), nil)) + int64bit := new(big.Int).Mod(n, big.NewInt(2).Exp(big.NewInt(2), big.NewInt(64), nil)) if int64bit.Cmp(big.NewInt(2).Exp(big.NewInt(2), big.NewInt(63), nil)) >= 0 { return int64bit.Sub(int64bit, big.NewInt(2).Exp(big.NewInt(2), big.NewInt(64), nil)) } @@ -703,7 +703,7 @@ func (a *bigInt64Array) exportType() reflect.Type { } func (a *bigUint64Array) toRaw(value Value) uint64 { - return toBigInt(value).i.Uint64() + return toBigUint64(value).Uint64() } func (a *bigUint64Array) ptr(idx int) *uint64 { @@ -717,7 +717,7 @@ func (a *bigUint64Array) get(idx int) Value { func toBigUint64(v Value) *big.Int { n := toBigInt(v).i - return n.Mod(n, big.NewInt(2).Exp(big.NewInt(2), big.NewInt(64), nil)) + return new(big.Int).Mod(n, big.NewInt(2).Exp(big.NewInt(2), big.NewInt(64), nil)) } func (a *bigUint64Array) set(idx int, value Value) { From f6b3b46897fa043228629a5022a39a5062f940de Mon Sep 17 00:00:00 2001 From: shiroyk Date: Sun, 18 Aug 2024 16:12:13 +0800 Subject: [PATCH 08/16] Fixed BigInt asUintN --- builtin_bigint.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin_bigint.go b/builtin_bigint.go index 5db70844..9dbbbc4f 100644 --- a/builtin_bigint.go +++ b/builtin_bigint.go @@ -264,7 +264,7 @@ func (r *Runtime) bigint_asUintN(call FunctionCall) Value { panic(r.NewTypeError("Invalid value: not (convertible to) a safe integer")) } bigint := toBigInt(call.Argument(1)) - return valueBigInt{bigint.i.Mod(bigint.i, big.NewInt(2).Lsh(big.NewInt(1), uint(bits)))} + return valueBigInt{new(big.Int).Mod(bigint.i, big.NewInt(2).Lsh(big.NewInt(1), uint(bits)))} } var bigintTemplate *objectTemplate From 53edc480e06267d28eb7ebc24cd046ead8232d78 Mon Sep 17 00:00:00 2001 From: shiroyk Date: Sun, 18 Aug 2024 20:03:36 +0800 Subject: [PATCH 09/16] Improved BigInt --- builtin_bigint.go | 10 +++++----- builtin_typedarrays.go | 4 ++-- typedarrays.go | 12 ++++++++---- vm.go | 6 +++--- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/builtin_bigint.go b/builtin_bigint.go index 9dbbbc4f..14dfc7e2 100644 --- a/builtin_bigint.go +++ b/builtin_bigint.go @@ -246,10 +246,10 @@ func (r *Runtime) bigint_asIntN(call FunctionCall) Value { } bigint := toBigInt(call.Argument(1)) - lsh := big.NewInt(2).Lsh(big.NewInt(1), uint(bits)) - mod := new(big.Int).Mod(bigint.i, lsh) - if bits > 0 && mod.Cmp(big.NewInt(2).Lsh(big.NewInt(1), uint(bits-1))) >= 0 { - return valueBigInt{mod.Sub(mod, lsh)} + twoToBits := new(big.Int).Lsh(big.NewInt(1), uint(bits)) + mod := new(big.Int).Mod(bigint.i, twoToBits) + if bits > 0 && mod.Cmp(new(big.Int).Lsh(big.NewInt(1), uint(bits-1))) >= 0 { + return valueBigInt{mod.Sub(mod, twoToBits)} } else { return valueBigInt{mod} } @@ -264,7 +264,7 @@ func (r *Runtime) bigint_asUintN(call FunctionCall) Value { panic(r.NewTypeError("Invalid value: not (convertible to) a safe integer")) } bigint := toBigInt(call.Argument(1)) - return valueBigInt{new(big.Int).Mod(bigint.i, big.NewInt(2).Lsh(big.NewInt(1), uint(bits)))} + return valueBigInt{new(big.Int).Mod(bigint.i, new(big.Int).Lsh(big.NewInt(1), uint(bits)))} } var bigintTemplate *objectTemplate diff --git a/builtin_typedarrays.go b/builtin_typedarrays.go index f821f861..aa6c68c8 100644 --- a/builtin_typedarrays.go +++ b/builtin_typedarrays.go @@ -396,7 +396,7 @@ func (r *Runtime) dataViewProto_setUint32(call FunctionCall) Value { func (r *Runtime) dataViewProto_setBigInt64(call FunctionCall) Value { if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { idxVal := r.toIndex(call.Argument(0)) - val := toBigInt(call.Argument(1)).i + val := toBigInt64(call.Argument(1)) idx, bo := dv.getIdxAndByteOrder(idxVal, call.Argument(2), 8) dv.viewedArrayBuf.setBigInt64(idx, val, bo) return _undefined @@ -407,7 +407,7 @@ func (r *Runtime) dataViewProto_setBigInt64(call FunctionCall) Value { func (r *Runtime) dataViewProto_setBigUint64(call FunctionCall) Value { if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { idxVal := r.toIndex(call.Argument(0)) - val := toBigInt(call.Argument(1)).i + val := toBigUint64(call.Argument(1)) idx, bo := dv.getIdxAndByteOrder(idxVal, call.Argument(2), 8) dv.viewedArrayBuf.setBigUint64(idx, val, bo) return _undefined diff --git a/typedarrays.go b/typedarrays.go index e6ffe31d..6ee41786 100644 --- a/typedarrays.go +++ b/typedarrays.go @@ -648,9 +648,13 @@ func (a *bigInt64Array) get(idx int) Value { func toBigInt64(v Value) *big.Int { n := toBigInt(v).i - int64bit := new(big.Int).Mod(n, big.NewInt(2).Exp(big.NewInt(2), big.NewInt(64), nil)) - if int64bit.Cmp(big.NewInt(2).Exp(big.NewInt(2), big.NewInt(63), nil)) >= 0 { - return int64bit.Sub(int64bit, big.NewInt(2).Exp(big.NewInt(2), big.NewInt(64), nil)) + + twoTo64 := new(big.Int).Lsh(big.NewInt(1), 64) + twoTo63 := new(big.Int).Lsh(big.NewInt(1), 63) + + int64bit := new(big.Int).Mod(n, twoTo64) + if int64bit.Cmp(twoTo63) >= 0 { + return int64bit.Sub(int64bit, twoTo64) } return int64bit } @@ -717,7 +721,7 @@ func (a *bigUint64Array) get(idx int) Value { func toBigUint64(v Value) *big.Int { n := toBigInt(v).i - return new(big.Int).Mod(n, big.NewInt(2).Exp(big.NewInt(2), big.NewInt(64), nil)) + return new(big.Int).Mod(n, new(big.Int).Lsh(big.NewInt(1), 64)) } func (a *bigUint64Array) set(idx int, value Value) { diff --git a/vm.go b/vm.go index 8dececa0..885cb5e3 100644 --- a/vm.go +++ b/vm.go @@ -1542,7 +1542,7 @@ func (_mod) exec(vm *vm) { case right.i.Cmp(big.NewInt(0)) == 0: panic(vm.r.newError(vm.r.getRangeError(), "Division by zero")) case left.i.Cmp(big.NewInt(0)) < 0: - abs := left.i.Abs(left.i) + abs := new(big.Int).Abs(left.i) v := new(big.Int).Mod(abs, right.i) result = valueBigInt{v.Neg(v)} default: @@ -4463,7 +4463,7 @@ func cmp(px, py Value) Value { nx, _ := nx.Int(nil) ret = nx.Cmp(ny.i) < 0 } else { - ret = nx.Cmp(big.NewFloat(0).SetInt(ny.i)) < 0 + ret = nx.Cmp(new(big.Float).SetInt(ny.i)) < 0 } goto end } @@ -4484,7 +4484,7 @@ func cmp(px, py Value) Value { ny, _ := ny.Int(nil) ret = nx.i.Cmp(ny) < 0 } else { - ret = big.NewFloat(0).SetInt(nx.i).Cmp(ny) < 0 + ret = new(big.Float).SetInt(nx.i).Cmp(ny) < 0 } goto end case valueBigInt: From 56089c0a6aa868f073c655944ef160bd73f7a9e8 Mon Sep 17 00:00:00 2001 From: shiroyk Date: Thu, 22 Aug 2024 08:41:20 +0800 Subject: [PATCH 10/16] Improved BigInt --- builtin_bigint.go | 111 +++++++++++++------------ builtin_bigint_test.go | 2 +- builtin_json.go | 4 +- builtin_typedarrays.go | 4 +- compiler_expr.go | 4 +- map_test.go | 4 +- runtime.go | 10 +-- string_ascii.go | 5 +- typedarrays.go | 12 +-- value.go | 10 +-- vm.go | 182 +++++++++++++++++++++-------------------- 11 files changed, 175 insertions(+), 173 deletions(-) diff --git a/builtin_bigint.go b/builtin_bigint.go index 14dfc7e2..54374473 100644 --- a/builtin_bigint.go +++ b/builtin_bigint.go @@ -12,67 +12,65 @@ import ( "github.com/dop251/goja/unistring" ) -type valueBigInt struct { - i *big.Int -} +type valueBigInt big.Int -func (v valueBigInt) ToInteger() int64 { - return v.i.Int64() +func (v *valueBigInt) ToInteger() int64 { + return (*big.Int)(v).Int64() } -func (v valueBigInt) toString() String { - return asciiString(v.i.String()) +func (v *valueBigInt) toString() String { + return asciiString((*big.Int)(v).String()) } -func (v valueBigInt) string() unistring.String { +func (v *valueBigInt) string() unistring.String { return unistring.String(v.String()) } -func (v valueBigInt) ToString() Value { +func (v *valueBigInt) ToString() Value { return v } -func (v valueBigInt) String() string { - return v.i.String() +func (v *valueBigInt) String() string { + return (*big.Int)(v).String() } -func (v valueBigInt) ToFloat() float64 { - f, _ := new(big.Float).SetInt(v.i).Float64() +func (v *valueBigInt) ToFloat() float64 { + f, _ := new(big.Float).SetInt((*big.Int)(v)).Float64() return f } -func (v valueBigInt) ToNumber() Value { +func (v *valueBigInt) ToNumber() Value { panic(typeError("Cannot convert a BigInt value to a number")) } -func (v valueBigInt) ToBoolean() bool { - return v.i.Sign() != 0 +func (v *valueBigInt) ToBoolean() bool { + return (*big.Int)(v).Sign() != 0 } -func (v valueBigInt) ToObject(r *Runtime) *Object { +func (v *valueBigInt) ToObject(r *Runtime) *Object { return r.newPrimitiveObject(v, r.getBigIntPrototype(), classBigInt) } -func (v valueBigInt) SameAs(other Value) bool { - if o, ok := other.(valueBigInt); ok { - return v.i.Cmp(o.i) == 0 +func (v *valueBigInt) SameAs(other Value) bool { + if o, ok := other.(*valueBigInt); ok { + return (*big.Int)(v).Cmp((*big.Int)(o)) == 0 } return false } -func (v valueBigInt) Equals(other Value) bool { +func (v *valueBigInt) Equals(other Value) bool { switch o := other.(type) { - case valueBigInt: - return v.i.Cmp(o.i) == 0 + case *valueBigInt: + return (*big.Int)(v).Cmp((*big.Int)(o)) == 0 case valueInt: - return v.i.Cmp(big.NewInt(int64(o))) == 0 + return (*big.Int)(v).Cmp(big.NewInt(int64(o))) == 0 case valueFloat: if IsInfinity(o) || math.IsNaN(float64(o)) { return false } if f := big.NewFloat(float64(o)); f.IsInt() { i, _ := f.Int(nil) - return v.i.Cmp(i) == 0 + return (*big.Int)(v).Cmp(i) == 0 } return false case String: @@ -80,50 +78,50 @@ func (v valueBigInt) Equals(other Value) bool { if err != nil { return false } - return bigInt.Cmp(v.i) == 0 + return bigInt.Cmp((*big.Int)(v)) == 0 case valueBool: - return v.i.Int64() == o.ToInteger() + return (*big.Int)(v).Int64() == o.ToInteger() case *Object: return v.Equals(o.toPrimitiveNumber()) } return false } -func (v valueBigInt) StrictEquals(other Value) bool { - o, ok := other.(valueBigInt) +func (v *valueBigInt) StrictEquals(other Value) bool { + o, ok := other.(*valueBigInt) if ok { - return v.i.Cmp(o.i) == 0 + return (*big.Int)(v).Cmp((*big.Int)(o)) == 0 } return false } -func (v valueBigInt) Export() interface{} { - return v.i +func (v *valueBigInt) Export() interface{} { + return new(big.Int).Set((*big.Int)(v)) } -func (v valueBigInt) ExportType() reflect.Type { +func (v *valueBigInt) ExportType() reflect.Type { return typeBigInt } -func (v valueBigInt) baseObject(rt *Runtime) *Object { +func (v *valueBigInt) baseObject(rt *Runtime) *Object { return rt.getBigIntPrototype() } -func (v valueBigInt) hash(hash *maphash.Hash) uint64 { +func (v *valueBigInt) hash(hash *maphash.Hash) uint64 { var sign byte - if v.i.Sign() < 0 { + if (*big.Int)(v).Sign() < 0 { sign = 0x01 } else { sign = 0x00 } _ = hash.WriteByte(sign) - _, _ = hash.Write(v.i.Bytes()) + _, _ = hash.Write((*big.Int)(v).Bytes()) h := hash.Sum64() hash.Reset() return h } -func toBigInt(value Value) valueBigInt { +func toBigInt(value Value) *valueBigInt { // Undefined Throw a TypeError exception. // Null Throw a TypeError exception. // Boolean Return 1n if prim is true and 0n if prim is false. @@ -134,16 +132,16 @@ func toBigInt(value Value) valueBigInt { // 3. Return n. // Symbol Throw a TypeError exception. switch prim := value.(type) { - case valueBigInt: + case *valueBigInt: return prim case String: bigInt, err := stringToBigInt(prim.toTrimmedUTF8()) if err != nil { panic(syntaxError(fmt.Sprintf("Cannot convert %s to a BigInt", prim))) } - return valueBigInt{bigInt} + return (*valueBigInt)(bigInt) case valueBool: - return valueBigInt{big.NewInt(prim.ToInteger())} + return (*valueBigInt)(big.NewInt(prim.ToInteger())) case *Symbol: panic(typeError("Cannot convert Symbol to a BigInt")) case *Object: @@ -153,19 +151,19 @@ func toBigInt(value Value) valueBigInt { } } -func numberToBigInt(v Value) valueBigInt { +func numberToBigInt(v Value) *valueBigInt { switch v := toNumeric(v).(type) { - case valueBigInt: + case *valueBigInt: return v case valueInt: - return valueBigInt{big.NewInt(v.ToInteger())} + return (*valueBigInt)(big.NewInt(v.ToInteger())) case valueFloat: if IsInfinity(v) || math.IsNaN(float64(v)) { panic(rangeError(fmt.Sprintf("Cannot convert %s to a BigInt", v))) } if f := big.NewFloat(float64(v)); f.IsInt() { n, _ := f.Int(nil) - return valueBigInt{n} + return (*valueBigInt)(n) } panic(rangeError(fmt.Sprintf("Cannot convert %s to a BigInt", v))) case *Object: @@ -200,7 +198,7 @@ func stringToBigInt(str string) (*big.Int, error) { func (r *Runtime) thisBigIntValue(value Value) Value { switch t := value.(type) { - case valueBigInt: + case *valueBigInt: return t case *Object: switch t := t.self.(type) { @@ -220,7 +218,7 @@ func (r *Runtime) bigintproto_valueOf(call FunctionCall) Value { } func (r *Runtime) bigintproto_toString(call FunctionCall) Value { - x := r.thisBigIntValue(call.This).(valueBigInt) + x := (*big.Int)(r.thisBigIntValue(call.This).(*valueBigInt)) radix := call.Argument(0) var radixMV int @@ -233,7 +231,7 @@ func (r *Runtime) bigintproto_toString(call FunctionCall) Value { } } - return asciiString(x.i.Text(radixMV)) + return asciiString(x.Text(radixMV)) } func (r *Runtime) bigint_asIntN(call FunctionCall) Value { @@ -247,11 +245,11 @@ func (r *Runtime) bigint_asIntN(call FunctionCall) Value { bigint := toBigInt(call.Argument(1)) twoToBits := new(big.Int).Lsh(big.NewInt(1), uint(bits)) - mod := new(big.Int).Mod(bigint.i, twoToBits) + mod := new(big.Int).Mod((*big.Int)(bigint), twoToBits) if bits > 0 && mod.Cmp(new(big.Int).Lsh(big.NewInt(1), uint(bits-1))) >= 0 { - return valueBigInt{mod.Sub(mod, twoToBits)} + return (*valueBigInt)(mod.Sub(mod, twoToBits)) } else { - return valueBigInt{mod} + return (*valueBigInt)(mod) } } @@ -263,8 +261,9 @@ func (r *Runtime) bigint_asUintN(call FunctionCall) Value { if bits < 0 { panic(r.NewTypeError("Invalid value: not (convertible to) a safe integer")) } - bigint := toBigInt(call.Argument(1)) - return valueBigInt{new(big.Int).Mod(bigint.i, new(big.Int).Lsh(big.NewInt(1), uint(bits)))} + bigint := (*big.Int)(toBigInt(call.Argument(1))) + ret := new(big.Int).Mod(bigint, new(big.Int).Lsh(big.NewInt(1), uint(bits))) + return (*valueBigInt)(ret) } var bigintTemplate *objectTemplate @@ -296,13 +295,13 @@ func createBigIntTemplate() *objectTemplate { func (r *Runtime) builtin_BigInt(call FunctionCall) Value { if len(call.Arguments) > 0 { switch v := call.Argument(0).(type) { - case valueBigInt, valueInt, valueFloat, *Object: + case *valueBigInt, valueInt, valueFloat, *Object: return numberToBigInt(v) default: return toBigInt(v) } } - return valueBigInt{big.NewInt(0)} + return (*valueBigInt)(big.NewInt(0)) } func (r *Runtime) builtin_newBigInt(args []Value, newTarget *Object) *Object { @@ -313,7 +312,7 @@ func (r *Runtime) builtin_newBigInt(args []Value, newTarget *Object) *Object { if len(args) > 0 { v = numberToBigInt(args[0]) } else { - v = valueBigInt{big.NewInt(0)} + v = (*valueBigInt)(big.NewInt(0)) } return r.newPrimitiveObject(v, newTarget, classBigInt) } diff --git a/builtin_bigint_test.go b/builtin_bigint_test.go index 1de35cb6..79df861a 100644 --- a/builtin_bigint_test.go +++ b/builtin_bigint_test.go @@ -9,7 +9,7 @@ func TestBigInt(t *testing.T) { const SCRIPT = `0xabcdef0123456789abcdef0123n` b := new(big.Int) b.SetString("0xabcdef0123456789abcdef0123", 0) - testScript(SCRIPT, valueBigInt{b}, t) + testScript(SCRIPT, (*valueBigInt)(b), t) } func TestBigIntExportTo(t *testing.T) { diff --git a/builtin_json.go b/builtin_json.go index 5be14b86..0f3e665d 100644 --- a/builtin_json.go +++ b/builtin_json.go @@ -282,7 +282,7 @@ func (ctx *_builtinJSON_stringifyContext) str(key Value, holder *Object) bool { value := nilSafe(holder.get(key, nil)) switch value.(type) { - case *Object, valueBigInt: + case *Object, *valueBigInt: if toJSON, ok := ctx.r.getVStr(value, "toJSON").(*Object); ok { if c, ok := toJSON.self.assertCallable(); ok { value = c(FunctionCall{ @@ -362,7 +362,7 @@ func (ctx *_builtinJSON_stringifyContext) str(key Value, holder *Object) bool { } case valueNull: ctx.buf.WriteString("null") - case valueBigInt: + case *valueBigInt: ctx.r.typeErrorResult(true, "Do not know how to serialize a BigInt") case *Object: for _, object := range ctx.stack { diff --git a/builtin_typedarrays.go b/builtin_typedarrays.go index aa6c68c8..1da178c7 100644 --- a/builtin_typedarrays.go +++ b/builtin_typedarrays.go @@ -293,14 +293,14 @@ func (r *Runtime) dataViewProto_getUint32(call FunctionCall) Value { func (r *Runtime) dataViewProto_getBigInt64(call FunctionCall) Value { if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { - return valueBigInt{dv.viewedArrayBuf.getBigInt64(dv.getIdxAndByteOrder(r.toIndex(call.Argument(0).ToNumber()), call.Argument(1), 8))} + return (*valueBigInt)(dv.viewedArrayBuf.getBigInt64(dv.getIdxAndByteOrder(r.toIndex(call.Argument(0).ToNumber()), call.Argument(1), 8))) } panic(r.NewTypeError("Method DataView.prototype.getBigInt64 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) } func (r *Runtime) dataViewProto_getBigUint64(call FunctionCall) Value { if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { - return valueBigInt{dv.viewedArrayBuf.getBigUint64(dv.getIdxAndByteOrder(r.toIndex(call.Argument(0).ToNumber()), call.Argument(1), 8))} + return (*valueBigInt)(dv.viewedArrayBuf.getBigUint64(dv.getIdxAndByteOrder(r.toIndex(call.Argument(0).ToNumber()), call.Argument(1), 8))) } panic(r.NewTypeError("Method DataView.prototype.getBigUint64 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) } diff --git a/compiler_expr.go b/compiler_expr.go index 580a802a..3e4fa192 100644 --- a/compiler_expr.go +++ b/compiler_expr.go @@ -1268,7 +1268,7 @@ func (e *compiledLiteral) emitGetter(putOnStack bool) { } func (e *compiledLiteral) constant() bool { - if _, ok := e.val.(valueBigInt); ok { + if _, ok := e.val.(*valueBigInt); ok { return false } return true @@ -3234,7 +3234,7 @@ func (c *compiler) compileNumberLiteral(v *ast.NumberLiteral) compiledExpr { case float64: val = floatToValue(num) case *big.Int: - val = valueBigInt{num} + val = (*valueBigInt)(num) default: c.assert(false, int(v.Idx)-1, "Unsupported number literal type: %T", v.Value) panic("unreachable") diff --git a/map_test.go b/map_test.go index 71993f42..8a743e41 100644 --- a/map_test.go +++ b/map_test.go @@ -26,8 +26,8 @@ func TestMapHash(t *testing.T) { testMapHashVal(floatToValue(1.2345), floatToValue(1.2345), true, t) testMapHashVal(SymIterator, SymToStringTag, false, t) testMapHashVal(SymIterator, SymIterator, true, t) - testMapHashVal(valueBigInt{big.NewInt(1)}, valueBigInt{big.NewInt(-1)}, false, t) - testMapHashVal(valueBigInt{big.NewInt(1)}, valueBigInt{big.NewInt(1)}, true, t) + testMapHashVal((*valueBigInt)(big.NewInt(1)), (*valueBigInt)(big.NewInt(-1)), false, t) + testMapHashVal((*valueBigInt)(big.NewInt(1)), (*valueBigInt)(big.NewInt(1)), true, t) // The following tests introduce indeterministic behaviour //testMapHashVal(asciiString("Test"), asciiString("Test1"), false, t) diff --git a/runtime.go b/runtime.go index 50505123..2816f4a1 100644 --- a/runtime.go +++ b/runtime.go @@ -838,11 +838,11 @@ func (r *Runtime) builtin_Number(call FunctionCall) Value { switch t := call.Arguments[0].(type) { case *Object: primValue := t.toPrimitiveNumber() - if bigint, ok := primValue.(valueBigInt); ok { + if bigint, ok := primValue.(*valueBigInt); ok { return intToValue(bigint.ToInteger()) } return primValue.ToNumber() - case valueBigInt: + case *valueBigInt: return intToValue(t.ToInteger()) default: return t.ToNumber() @@ -858,12 +858,12 @@ func (r *Runtime) builtin_newNumber(args []Value, proto *Object) *Object { switch t := args[0].(type) { case *Object: primValue := t.toPrimitiveNumber() - if bigint, ok := primValue.(valueBigInt); ok { + if bigint, ok := primValue.(*valueBigInt); ok { v = intToValue(bigint.ToInteger()) } else { v = primValue.ToNumber() } - case valueBigInt: + case *valueBigInt: v = intToValue(t.ToInteger()) default: v = t.ToNumber() @@ -1861,7 +1861,7 @@ func (r *Runtime) toValue(i interface{}, origValue reflect.Value) Value { case float64: return floatToValue(i) case *big.Int: - return valueBigInt{i} + return (*valueBigInt)(new(big.Int).Set(i)) case map[string]interface{}: if i == nil { return _null diff --git a/string_ascii.go b/string_ascii.go index 2d27df32..ebe1eed2 100644 --- a/string_ascii.go +++ b/string_ascii.go @@ -4,6 +4,7 @@ import ( "hash/maphash" "io" "math" + "math/big" "reflect" "strconv" "strings" @@ -246,12 +247,12 @@ func (s asciiString) Equals(other Value) bool { return false } - if o, ok := other.(valueBigInt); ok { + if o, ok := other.(*valueBigInt); ok { bigInt, err := stringToBigInt(s.toTrimmedUTF8()) if err != nil { return false } - return bigInt.Cmp(o.i) == 0 + return bigInt.Cmp((*big.Int)(o)) == 0 } if o, ok := other.(*Object); ok { diff --git a/typedarrays.go b/typedarrays.go index 6ee41786..cc589e15 100644 --- a/typedarrays.go +++ b/typedarrays.go @@ -643,11 +643,11 @@ func (a *bigInt64Array) ptr(idx int) *int64 { } func (a *bigInt64Array) get(idx int) Value { - return valueBigInt{big.NewInt(*a.ptr(idx))} + return (*valueBigInt)(big.NewInt(*a.ptr(idx))) } func toBigInt64(v Value) *big.Int { - n := toBigInt(v).i + n := (*big.Int)(toBigInt(v)) twoTo64 := new(big.Int).Lsh(big.NewInt(1), 64) twoTo63 := new(big.Int).Lsh(big.NewInt(1), 63) @@ -681,7 +681,7 @@ func (a *bigInt64Array) swap(i, j int) { } func (a *bigInt64Array) typeMatch(v Value) bool { - if _, ok := v.(valueBigInt); ok { + if _, ok := v.(*valueBigInt); ok { return true } return false @@ -716,11 +716,11 @@ func (a *bigUint64Array) ptr(idx int) *uint64 { } func (a *bigUint64Array) get(idx int) Value { - return valueBigInt{new(big.Int).SetUint64(*a.ptr(idx))} + return (*valueBigInt)(new(big.Int).SetUint64(*a.ptr(idx))) } func toBigUint64(v Value) *big.Int { - n := toBigInt(v).i + n := (*big.Int)(toBigInt(v)) return new(big.Int).Mod(n, new(big.Int).Lsh(big.NewInt(1), 64)) } @@ -746,7 +746,7 @@ func (a *bigUint64Array) swap(i, j int) { } func (a *bigUint64Array) typeMatch(v Value) bool { - if _, ok := v.(valueBigInt); ok { + if _, ok := v.(*valueBigInt); ok { return true } return false diff --git a/value.go b/value.go index 56f0081b..6ca3d72d 100644 --- a/value.go +++ b/value.go @@ -220,8 +220,8 @@ func (i valueInt) Equals(other Value) bool { switch o := other.(type) { case valueInt: return i == o - case valueBigInt: - return o.i.Cmp(big.NewInt(int64(i))) == 0 + case *valueBigInt: + return (*big.Int)(o).Cmp(big.NewInt(int64(i))) == 0 case valueFloat: return float64(i) == float64(o) case String: @@ -647,13 +647,13 @@ func (f valueFloat) Equals(other Value) bool { return f == o case valueInt: return float64(f) == float64(o) - case valueBigInt: + case *valueBigInt: if IsInfinity(f) || math.IsNaN(float64(f)) { return false } if f := big.NewFloat(float64(f)); f.IsInt() { i, _ := f.Int(nil) - return o.i.Cmp(i) == 0 + return (*big.Int)(o).Cmp(i) == 0 } return false case String, valueBool: @@ -741,7 +741,7 @@ func (o *Object) Equals(other Value) bool { } switch o1 := other.(type) { - case valueInt, valueFloat, valueBigInt, String, *Symbol: + case valueInt, valueFloat, *valueBigInt, String, *Symbol: return o.toPrimitive().Equals(other) case valueBool: return o.Equals(o1.ToNumber()) diff --git a/vm.go b/vm.go index 885cb5e3..d445cc72 100644 --- a/vm.go +++ b/vm.go @@ -418,13 +418,13 @@ func floatToValue(f float64) (result Value) { func toNumeric(value Value) Value { switch v := value.(type) { - case valueInt, valueBigInt: + case valueInt, *valueBigInt: return v case valueFloat: return floatToValue(float64(v)) case *Object: primValue := v.toPrimitiveNumber() - if bigint, ok := primValue.(valueBigInt); ok { + if bigint, ok := primValue.(*valueBigInt); ok { return bigint } return primValue.ToNumber() @@ -1279,19 +1279,19 @@ func (_add) exec(vm *vm) { switch right := right.(type) { case valueInt: ret = intToValue(int64(left) + int64(right)) - case valueBigInt: + case *valueBigInt: panic(errMixBigIntType) default: ret = floatToValue(float64(left) + right.ToFloat()) } - case valueBigInt: - if right, ok := right.(valueBigInt); ok { - ret = valueBigInt{new(big.Int).Add(left.i, right.i)} + case *valueBigInt: + if right, ok := right.(*valueBigInt); ok { + ret = (*valueBigInt)(new(big.Int).Add((*big.Int)(left), (*big.Int)(right))) } else { panic(errMixBigIntType) } default: - if _, ok := right.(valueBigInt); ok { + if _, ok := right.(*valueBigInt); ok { panic(errMixBigIntType) } ret = floatToValue(left.ToFloat() + right.ToFloat()) @@ -1322,16 +1322,16 @@ func (_sub) exec(vm *vm) { case valueInt: result = intToValue(int64(left) - int64(right)) goto end - case valueBigInt: + case *valueBigInt: panic(errMixBigIntType) } case valueFloat: - if _, ok := right.(valueBigInt); ok { + if _, ok := right.(*valueBigInt); ok { panic(errMixBigIntType) } - case valueBigInt: - if right, ok := right.(valueBigInt); ok { - result = valueBigInt{new(big.Int).Sub(left.i, right.i)} + case *valueBigInt: + if right, ok := right.(*valueBigInt); ok { + result = (*valueBigInt)(new(big.Int).Sub((*big.Int)(left), (*big.Int)(right))) goto end } panic(errMixBigIntType) @@ -1368,16 +1368,16 @@ func (_mul) exec(vm *vm) { result = intToValue(int64(res)) goto end } - case valueBigInt: + case *valueBigInt: panic(errMixBigIntType) } case valueFloat: - if _, ok := right.(valueBigInt); ok { + if _, ok := right.(*valueBigInt); ok { panic(errMixBigIntType) } - case valueBigInt: - if right, ok := right.(valueBigInt); ok { - result = valueBigInt{new(big.Int).Mul(left.i, right.i)} + case *valueBigInt: + if right, ok := right.(*valueBigInt); ok { + result = (*valueBigInt)(new(big.Int).Mul((*big.Int)(left), (*big.Int)(right))) goto end } panic(errMixBigIntType) @@ -1404,16 +1404,16 @@ func (_exp) exec(vm *vm) { y = toNumeric(y) var result Value - if x, ok := x.(valueBigInt); ok { - if y, ok := y.(valueBigInt); ok { - if y.i.Cmp(big.NewInt(0)) < 0 { + if x, ok := x.(*valueBigInt); ok { + if y, ok := y.(*valueBigInt); ok { + if (*big.Int)(y).Cmp(big.NewInt(0)) < 0 { panic(vm.r.newError(vm.r.getRangeError(), "exponent must be positive")) } - result = valueBigInt{new(big.Int).Exp(x.i, y.i, nil)} + result = (*valueBigInt)(new(big.Int).Exp((*big.Int)(x), (*big.Int)(y), nil)) goto end } panic(errMixBigIntType) - } else if _, ok := y.(valueBigInt); ok { + } else if _, ok := y.(*valueBigInt); ok { panic(errMixBigIntType) } @@ -1436,21 +1436,21 @@ func (_div) exec(vm *vm) { left, right float64 ) - if left, ok := leftValue.(valueBigInt); ok { - if right, ok := rightValue.(valueBigInt); ok { - if right.i.Cmp(big.NewInt(0)) == 0 { + if left, ok := leftValue.(*valueBigInt); ok { + if right, ok := rightValue.(*valueBigInt); ok { + if (*big.Int)(right).Cmp(big.NewInt(0)) == 0 { panic(vm.r.newError(vm.r.getRangeError(), "Division by zero")) } - if left.i.CmpAbs(right.i) < 0 { - result = valueBigInt{big.NewInt(0)} + if (*big.Int)(left).CmpAbs((*big.Int)(right)) < 0 { + result = (*valueBigInt)(big.NewInt(0)) } else { - i, _ := new(big.Int).QuoRem(left.i, right.i, big.NewInt(0)) - result = valueBigInt{i} + i, _ := new(big.Int).QuoRem((*big.Int)(left), (*big.Int)(right), big.NewInt(0)) + result = (*valueBigInt)(i) } goto end } panic(errMixBigIntType) - } else if _, ok := rightValue.(valueBigInt); ok { + } else if _, ok := rightValue.(*valueBigInt); ok { panic(errMixBigIntType) } left, right = leftValue.ToFloat(), rightValue.ToFloat() @@ -1529,24 +1529,24 @@ func (_mod) exec(vm *vm) { result = intToValue(int64(left % right)) } goto end - case valueBigInt: + case *valueBigInt: panic(errMixBigIntType) } case valueFloat: - if _, ok := right.(valueBigInt); ok { + if _, ok := right.(*valueBigInt); ok { panic(errMixBigIntType) } - case valueBigInt: - if right, ok := right.(valueBigInt); ok { + case *valueBigInt: + if right, ok := right.(*valueBigInt); ok { switch { - case right.i.Cmp(big.NewInt(0)) == 0: + case (*big.Int)(right).Cmp(big.NewInt(0)) == 0: panic(vm.r.newError(vm.r.getRangeError(), "Division by zero")) - case left.i.Cmp(big.NewInt(0)) < 0: - abs := new(big.Int).Abs(left.i) - v := new(big.Int).Mod(abs, right.i) - result = valueBigInt{v.Neg(v)} + case (*big.Int)(left).Cmp(big.NewInt(0)) < 0: + abs := new(big.Int).Abs((*big.Int)(left)) + v := new(big.Int).Mod(abs, (*big.Int)(right)) + result = (*valueBigInt)(v.Neg(v)) default: - result = valueBigInt{new(big.Int).Mod(left.i, right.i)} + result = (*valueBigInt)(new(big.Int).Mod((*big.Int)(left), (*big.Int)(right))) } goto end } @@ -1570,8 +1570,8 @@ func (_neg) exec(vm *vm) { var result Value switch n := toNumeric(operand).(type) { - case valueBigInt: - result = valueBigInt{new(big.Int).Neg(n.i)} + case *valueBigInt: + result = (*valueBigInt)(new(big.Int).Neg((*big.Int)(n))) case valueInt: if n == 0 { result = _negativeZero @@ -1607,8 +1607,8 @@ func (_inc) exec(vm *vm) { v := vm.stack[vm.sp-1] switch n := v.(type) { - case valueBigInt: - v = valueBigInt{new(big.Int).Add(n.i, big.NewInt(1))} + case *valueBigInt: + v = (*valueBigInt)(new(big.Int).Add((*big.Int)(n), big.NewInt(1))) case valueInt: v = intToValue(int64(n + 1)) default: @@ -1627,8 +1627,8 @@ func (_dec) exec(vm *vm) { v := vm.stack[vm.sp-1] switch n := v.(type) { - case valueBigInt: - v = valueBigInt{new(big.Int).Sub(n.i, big.NewInt(1))} + case *valueBigInt: + v = (*valueBigInt)(new(big.Int).Sub((*big.Int)(n), big.NewInt(1))) case valueInt: v = intToValue(int64(n - 1)) default: @@ -1648,13 +1648,13 @@ func (_and) exec(vm *vm) { right := toNumeric(vm.stack[vm.sp-1]) var result Value - if left, ok := left.(valueBigInt); ok { - if right, ok := right.(valueBigInt); ok { - result = valueBigInt{new(big.Int).And(left.i, right.i)} + if left, ok := left.(*valueBigInt); ok { + if right, ok := right.(*valueBigInt); ok { + result = (*valueBigInt)(new(big.Int).And((*big.Int)(left), (*big.Int)(right))) goto end } panic(errMixBigIntType) - } else if _, ok := right.(valueBigInt); ok { + } else if _, ok := right.(*valueBigInt); ok { panic(errMixBigIntType) } @@ -1674,13 +1674,13 @@ func (_or) exec(vm *vm) { right := toNumeric(vm.stack[vm.sp-1]) var result Value - if left, ok := left.(valueBigInt); ok { - if right, ok := right.(valueBigInt); ok { - result = valueBigInt{new(big.Int).Or(left.i, right.i)} + if left, ok := left.(*valueBigInt); ok { + if right, ok := right.(*valueBigInt); ok { + result = (*valueBigInt)(new(big.Int).Or((*big.Int)(left), (*big.Int)(right))) goto end } panic(errMixBigIntType) - } else if _, ok := right.(valueBigInt); ok { + } else if _, ok := right.(*valueBigInt); ok { panic(errMixBigIntType) } @@ -1700,13 +1700,13 @@ func (_xor) exec(vm *vm) { right := toNumeric(vm.stack[vm.sp-1]) var result Value - if left, ok := left.(valueBigInt); ok { - if right, ok := right.(valueBigInt); ok { - result = valueBigInt{new(big.Int).Xor(left.i, right.i)} + if left, ok := left.(*valueBigInt); ok { + if right, ok := right.(*valueBigInt); ok { + result = (*valueBigInt)(new(big.Int).Xor((*big.Int)(left), (*big.Int)(right))) goto end } panic(errMixBigIntType) - } else if _, ok := right.(valueBigInt); ok { + } else if _, ok := right.(*valueBigInt); ok { panic(errMixBigIntType) } @@ -1724,8 +1724,8 @@ var bnot _bnot func (_bnot) exec(vm *vm) { v := vm.stack[vm.sp-1] switch n := toNumeric(v).(type) { - case valueBigInt: - v = valueBigInt{new(big.Int).Not(n.i)} + case *valueBigInt: + v = (*valueBigInt)(new(big.Int).Not((*big.Int)(n))) default: v = intToValue(int64(^toInt32(n))) } @@ -1742,17 +1742,18 @@ func (_sal) exec(vm *vm) { right := toNumeric(vm.stack[vm.sp-1]) var result Value - if left, ok := left.(valueBigInt); ok { - if right, ok := right.(valueBigInt); ok { - if right.i.Sign() < 0 { - result = valueBigInt{new(big.Int).Rsh(left.i, uint(right.i.Uint64()))} + if left, ok := left.(*valueBigInt); ok { + if right, ok := right.(*valueBigInt); ok { + n := uint((*big.Int)(right).Uint64()) + if (*big.Int)(right).Sign() < 0 { + result = (*valueBigInt)(new(big.Int).Rsh((*big.Int)(left), n)) } else { - result = valueBigInt{new(big.Int).Lsh(left.i, uint(right.i.Uint64()))} + result = (*valueBigInt)(new(big.Int).Lsh((*big.Int)(left), n)) } goto end } panic(errMixBigIntType) - } else if _, ok := right.(valueBigInt); ok { + } else if _, ok := right.(*valueBigInt); ok { panic(errMixBigIntType) } @@ -1772,17 +1773,18 @@ func (_sar) exec(vm *vm) { right := toNumeric(vm.stack[vm.sp-1]) var result Value - if left, ok := left.(valueBigInt); ok { - if right, ok := right.(valueBigInt); ok { - if right.i.Sign() < 0 { - result = valueBigInt{new(big.Int).Lsh(left.i, uint(right.i.Uint64()))} + if left, ok := left.(*valueBigInt); ok { + if right, ok := right.(*valueBigInt); ok { + n := uint((*big.Int)(right).Uint64()) + if (*big.Int)(right).Sign() < 0 { + result = (*valueBigInt)(new(big.Int).Lsh((*big.Int)(left), n)) } else { - result = valueBigInt{new(big.Int).Rsh(left.i, uint(right.i.Uint64()))} + result = (*valueBigInt)(new(big.Int).Rsh((*big.Int)(left), n)) } goto end } panic(errMixBigIntType) - } else if _, ok := right.(valueBigInt); ok { + } else if _, ok := right.(*valueBigInt); ok { panic(errMixBigIntType) } @@ -1801,10 +1803,10 @@ func (_shr) exec(vm *vm) { left := toNumeric(vm.stack[vm.sp-2]) right := toNumeric(vm.stack[vm.sp-1]) - if _, ok := left.(valueBigInt); ok { + if _, ok := left.(*valueBigInt); ok { _ = toNumeric(right) panic(vm.r.NewTypeError("BigInts have no unsigned right shift, use >> instead")) - } else if _, ok := right.(valueBigInt); ok { + } else if _, ok := right.(*valueBigInt); ok { panic(vm.r.NewTypeError("BigInts have no unsigned right shift, use >> instead")) } @@ -4421,20 +4423,20 @@ func cmp(px, py Value) Value { ret = xs.CompareTo(ys) < 0 goto end } else { - if px, ok := px.(valueBigInt); ok && isPyString { + if px, ok := px.(*valueBigInt); ok && isPyString { ny, err := stringToBigInt(ys.toTrimmedUTF8()) if err != nil { return _undefined } - ret = px.i.Cmp(ny) < 0 + ret = (*big.Int)(px).Cmp(ny) < 0 goto end } - if py, ok := py.(valueBigInt); ok && isPxString { + if py, ok := py.(*valueBigInt); ok && isPxString { nx, err := stringToBigInt(xs.toTrimmedUTF8()) if err != nil { return _undefined } - ret = nx.Cmp(py.i) < 0 + ret = nx.Cmp((*big.Int)(py)) < 0 goto end } } @@ -4445,13 +4447,13 @@ func cmp(px, py Value) Value { case valueInt: ret = nx < ny goto end - case valueBigInt: - ret = big.NewInt(int64(nx)).Cmp(ny.i) < 0 + case *valueBigInt: + ret = big.NewInt(int64(nx)).Cmp((*big.Int)(ny)) < 0 goto end } case valueFloat: switch ny := py.(type) { - case valueBigInt: + case *valueBigInt: switch { case math.IsNaN(float64(nx)): return _undefined @@ -4461,16 +4463,16 @@ func cmp(px, py Value) Value { } if nx := big.NewFloat(float64(nx)); nx.IsInt() { nx, _ := nx.Int(nil) - ret = nx.Cmp(ny.i) < 0 + ret = nx.Cmp((*big.Int)(ny)) < 0 } else { - ret = nx.Cmp(new(big.Float).SetInt(ny.i)) < 0 + ret = nx.Cmp(new(big.Float).SetInt((*big.Int)(ny))) < 0 } goto end } - case valueBigInt: + case *valueBigInt: switch ny := py.(type) { case valueInt: - ret = nx.i.Cmp(big.NewInt(int64(ny))) < 0 + ret = (*big.Int)(nx).Cmp(big.NewInt(int64(ny))) < 0 goto end case valueFloat: switch { @@ -4482,13 +4484,13 @@ func cmp(px, py Value) Value { } if ny := big.NewFloat(float64(ny)); ny.IsInt() { ny, _ := ny.Int(nil) - ret = nx.i.Cmp(ny) < 0 + ret = (*big.Int)(nx).Cmp(ny) < 0 } else { - ret = new(big.Float).SetInt(nx.i).Cmp(ny) < 0 + ret = new(big.Float).SetInt((*big.Int)(nx)).Cmp(ny) < 0 } goto end - case valueBigInt: - ret = nx.i.Cmp(ny.i) < 0 + case *valueBigInt: + ret = (*big.Int)(nx).Cmp((*big.Int)(ny)) < 0 goto end } } @@ -4852,7 +4854,7 @@ func (_typeof) exec(vm *vm) { r = stringString case valueInt, valueFloat: r = stringNumber - case valueBigInt: + case *valueBigInt: r = stringBigInt case *Symbol: r = stringSymbol From c1f36574d689433421db3478e969d3251339e52e Mon Sep 17 00:00:00 2001 From: shiroyk Date: Thu, 22 Aug 2024 09:03:38 +0800 Subject: [PATCH 11/16] BigInt64Array/BigUint64Array now export []int64/[]uint64 --- builtin_typedarrays.go | 3 ++- typedarrays.go | 21 ++++++++------------- typedarrays_test.go | 9 ++++----- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/builtin_typedarrays.go b/builtin_typedarrays.go index 1da178c7..a05a507a 100644 --- a/builtin_typedarrays.go +++ b/builtin_typedarrays.go @@ -1226,7 +1226,8 @@ func (r *Runtime) typedArrayProto_with(call FunctionCall) Value { } var numericValue Value - if ta.typedArray.exportType() == typeBigInt64Array { + if ta.typedArray.exportType() == typeBigInt64Array || + ta.typedArray.exportType() == typeBigUint64Array { numericValue = toBigInt(call.Argument(1)) } else { numericValue = call.Argument(1).ToNumber() diff --git a/typedarrays.go b/typedarrays.go index cc589e15..f305b1c0 100644 --- a/typedarrays.go +++ b/typedarrays.go @@ -693,14 +693,10 @@ func (a *bigInt64Array) export(offset int, length int) interface{} { sliceHeader.Data = (*reflect.SliceHeader)(unsafe.Pointer(a)).Data + uintptr(offset)*8 sliceHeader.Len = length sliceHeader.Cap = length - ret := make([]*big.Int, 0, length) - for _, v := range res { - ret = append(ret, big.NewInt(v)) - } - return ret + return res } -var typeBigInt64Array = reflect.TypeOf(([]*big.Int)(nil)) +var typeBigInt64Array = reflect.TypeOf(([]int64)(nil)) func (a *bigInt64Array) exportType() reflect.Type { return typeBigInt64Array @@ -758,15 +754,13 @@ func (a *bigUint64Array) export(offset int, length int) interface{} { sliceHeader.Data = (*reflect.SliceHeader)(unsafe.Pointer(a)).Data + uintptr(offset)*8 sliceHeader.Len = length sliceHeader.Cap = length - ret := make([]*big.Int, 0, length) - for _, v := range res { - ret = append(ret, new(big.Int).SetUint64(v)) - } - return ret + return res } +var typeBigUint64Array = reflect.TypeOf(([]uint64)(nil)) + func (a *bigUint64Array) exportType() reflect.Type { - return typeBigInt64Array + return typeBigUint64Array } func (a *typedArrayObject) _getIdx(idx int) Value { @@ -837,7 +831,8 @@ func (a *typedArrayObject) isValidIntegerIndex(idx int) bool { } func (a *typedArrayObject) _putIdx(idx int, v Value) { - if a.typedArray.exportType() == typeBigInt64Array { + if a.typedArray.exportType() == typeBigInt64Array || + a.typedArray.exportType() == typeBigUint64Array { v = toBigInt(v) } else { v = v.ToNumber() diff --git a/typedarrays_test.go b/typedarrays_test.go index d361d841..92653d6a 100644 --- a/typedarrays_test.go +++ b/typedarrays_test.go @@ -2,7 +2,6 @@ package goja import ( "bytes" - "math/big" "testing" ) @@ -519,8 +518,8 @@ func TestTypedArrayExport(t *testing.T) { if err != nil { t.Fatal(err) } - if a, ok := v.Export().([]*big.Int); ok { - if len(a) != 2 || a[0].Cmp(big.NewInt(1)) != 0 || a[1].Cmp(big.NewInt(2)) != 0 { + if a, ok := v.Export().([]int64); ok { + if len(a) != 2 || a[0] != 1 || a[1] != 2 { t.Fatal(a) } } else { @@ -533,8 +532,8 @@ func TestTypedArrayExport(t *testing.T) { if err != nil { t.Fatal(err) } - if a, ok := v.Export().([]*big.Int); ok { - if len(a) != 2 || a[0].Cmp(big.NewInt(1)) != 0 || a[1].Cmp(big.NewInt(2)) != 0 { + if a, ok := v.Export().([]uint64); ok { + if len(a) != 2 || a[0] != 1 || a[1] != 2 { t.Fatal(a) } } else { From 5d79cc763651751ed845e4cabb7395398fffc3cc Mon Sep 17 00:00:00 2001 From: shiroyk Date: Thu, 22 Aug 2024 09:17:56 +0800 Subject: [PATCH 12/16] valueBigInt ToInteger throw TypeError --- builtin_bigint.go | 2 +- runtime.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/builtin_bigint.go b/builtin_bigint.go index 54374473..8703037f 100644 --- a/builtin_bigint.go +++ b/builtin_bigint.go @@ -15,7 +15,7 @@ import ( type valueBigInt big.Int func (v *valueBigInt) ToInteger() int64 { - return (*big.Int)(v).Int64() + panic(typeError("Cannot convert a BigInt value to a number")) } func (v *valueBigInt) toString() String { diff --git a/runtime.go b/runtime.go index 2816f4a1..fe889944 100644 --- a/runtime.go +++ b/runtime.go @@ -839,11 +839,11 @@ func (r *Runtime) builtin_Number(call FunctionCall) Value { case *Object: primValue := t.toPrimitiveNumber() if bigint, ok := primValue.(*valueBigInt); ok { - return intToValue(bigint.ToInteger()) + return intToValue((*big.Int)(bigint).Int64()) } return primValue.ToNumber() case *valueBigInt: - return intToValue(t.ToInteger()) + return intToValue((*big.Int)(t).Int64()) default: return t.ToNumber() } @@ -859,12 +859,12 @@ func (r *Runtime) builtin_newNumber(args []Value, proto *Object) *Object { case *Object: primValue := t.toPrimitiveNumber() if bigint, ok := primValue.(*valueBigInt); ok { - v = intToValue(bigint.ToInteger()) + v = intToValue((*big.Int)(bigint).Int64()) } else { v = primValue.ToNumber() } case *valueBigInt: - v = intToValue(t.ToInteger()) + v = intToValue((*big.Int)(t).Int64()) default: v = t.ToNumber() } From e9606b3ef540e848c2b3dcaddd30cedb9d15cbbf Mon Sep 17 00:00:00 2001 From: shiroyk Date: Thu, 22 Aug 2024 09:33:38 +0800 Subject: [PATCH 13/16] valueBigInt ToFloat throw TypeError --- builtin_bigint.go | 7 ++++--- vm.go | 3 +++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/builtin_bigint.go b/builtin_bigint.go index 8703037f..e05c29dc 100644 --- a/builtin_bigint.go +++ b/builtin_bigint.go @@ -15,7 +15,8 @@ import ( type valueBigInt big.Int func (v *valueBigInt) ToInteger() int64 { - panic(typeError("Cannot convert a BigInt value to a number")) + v.ToNumber() + return 0 } func (v *valueBigInt) toString() String { @@ -35,8 +36,8 @@ func (v *valueBigInt) String() string { } func (v *valueBigInt) ToFloat() float64 { - f, _ := new(big.Float).SetInt((*big.Int)(v)).Float64() - return f + v.ToNumber() + return 0 } func (v *valueBigInt) ToNumber() Value { diff --git a/vm.go b/vm.go index d445cc72..35e55940 100644 --- a/vm.go +++ b/vm.go @@ -4441,6 +4441,9 @@ func cmp(px, py Value) Value { } } + px = toNumeric(px) + py = toNumeric(py) + switch nx := px.(type) { case valueInt: switch ny := py.(type) { From b02b9b5e96d5672d0c8fa172f795e95b94c6b4f2 Mon Sep 17 00:00:00 2001 From: shiroyk Date: Thu, 22 Aug 2024 18:23:30 +0800 Subject: [PATCH 14/16] Bigint compile RangeError --- compiler_expr.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/compiler_expr.go b/compiler_expr.go index 3e4fa192..5245f266 100644 --- a/compiler_expr.go +++ b/compiler_expr.go @@ -1268,9 +1268,6 @@ func (e *compiledLiteral) emitGetter(putOnStack bool) { } func (e *compiledLiteral) constant() bool { - if _, ok := e.val.(*valueBigInt); ok { - return false - } return true } @@ -2451,7 +2448,7 @@ func (c *compiler) emitThrow(v Value) { if o, ok := v.(*Object); ok { t := nilSafe(o.self.getStr("name", nil)).toString().String() switch t { - case "TypeError": + case "TypeError", "RangeError": c.emit(loadDynamic(t)) msg := o.self.getStr("message", nil) if msg != nil { From 8e088dc4f49adadf4e5444ebefc20e776f5f55a5 Mon Sep 17 00:00:00 2001 From: shiroyk Date: Thu, 22 Aug 2024 18:40:49 +0800 Subject: [PATCH 15/16] Remove classBigInt --- builtin_bigint.go | 12 ++++++------ builtin_json.go | 7 +++---- builtin_object.go | 3 --- object.go | 1 - 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/builtin_bigint.go b/builtin_bigint.go index e05c29dc..a50ebcf2 100644 --- a/builtin_bigint.go +++ b/builtin_bigint.go @@ -49,7 +49,7 @@ func (v *valueBigInt) ToBoolean() bool { } func (v *valueBigInt) ToObject(r *Runtime) *Object { - return r.newPrimitiveObject(v, r.getBigIntPrototype(), classBigInt) + return r.newPrimitiveObject(v, r.getBigIntPrototype(), classObject) } func (v *valueBigInt) SameAs(other Value) bool { @@ -206,7 +206,7 @@ func (r *Runtime) thisBigIntValue(value Value) Value { case *primitiveValueObject: return r.thisBigIntValue(t.pValue) case *objectGoReflect: - if t.class == classBigInt && t.valueOf != nil { + if t.exportType() == typeBigInt && t.valueOf != nil { return t.valueOf() } } @@ -315,7 +315,7 @@ func (r *Runtime) builtin_newBigInt(args []Value, newTarget *Object) *Object { } else { v = (*valueBigInt)(big.NewInt(0)) } - return r.newPrimitiveObject(v, newTarget, classBigInt) + return r.newPrimitiveObject(v, newTarget, classObject) } func (r *Runtime) getBigInt() *Object { @@ -336,13 +336,13 @@ func createBigIntProtoTemplate() *objectTemplate { } t.putStr("length", func(r *Runtime) Value { return valueProp(intToValue(0), false, false, true) }) - t.putStr("name", func(r *Runtime) Value { return valueProp(asciiString(classBigInt), false, false, true) }) + t.putStr("name", func(r *Runtime) Value { return valueProp(asciiString("BigInt"), false, false, true) }) t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getBigInt(), true, false, true) }) t.putStr("toLocaleString", func(r *Runtime) Value { return r.methodProp(r.bigintproto_toString, "toLocaleString", 0) }) t.putStr("toString", func(r *Runtime) Value { return r.methodProp(r.bigintproto_toString, "toString", 0) }) t.putStr("valueOf", func(r *Runtime) Value { return r.methodProp(r.bigintproto_valueOf, "valueOf", 0) }) - t.putSym(SymToStringTag, func(r *Runtime) Value { return valueProp(asciiString(classBigInt), false, false, true) }) + t.putSym(SymToStringTag, func(r *Runtime) Value { return valueProp(asciiString("BigInt"), false, false, true) }) return t } @@ -363,7 +363,7 @@ func (r *Runtime) getBigIntPrototype() *Object { ret = &Object{runtime: r} r.global.BigIntPrototype = ret o := r.newTemplatedObject(getBigIntProtoTemplate(), ret) - o.class = classBigInt + o.class = classObject } return ret } diff --git a/builtin_json.go b/builtin_json.go index 0f3e665d..cd4a7bca 100644 --- a/builtin_json.go +++ b/builtin_json.go @@ -334,10 +334,9 @@ func (ctx *_builtinJSON_stringifyContext) str(key Value, holder *Object) bool { } else { value = valueFalse } - case classBigInt: - if o1.valueOf != nil { - value = o1.valueOf() - } + } + if o1.exportType() == typeBigInt { + value = o1.val.ordinaryToPrimitiveNumber() } } } diff --git a/builtin_object.go b/builtin_object.go index 2d7242c6..6bf1ff80 100644 --- a/builtin_object.go +++ b/builtin_object.go @@ -473,9 +473,6 @@ func (r *Runtime) objectproto_toString(call FunctionCall) Value { clsName = classArray } else { clsName = obj.self.className() - if clsName == classBigInt { - clsName = classObject - } } if tag := obj.self.getSym(SymToStringTag, nil); tag != nil { if str, ok := tag.(String); ok { diff --git a/object.go b/object.go index a84ce35b..79bd67df 100644 --- a/object.go +++ b/object.go @@ -20,7 +20,6 @@ const ( classFunction = "Function" classAsyncFunction = "AsyncFunction" classNumber = "Number" - classBigInt = "BigInt" classString = "String" classBoolean = "Boolean" classError = "Error" From e21ce199df6d11c390b089b1feda27e84af4d738 Mon Sep 17 00:00:00 2001 From: shiroyk Date: Thu, 22 Aug 2024 18:44:10 +0800 Subject: [PATCH 16/16] Improved TypedArray --- builtin_typedarrays.go | 6 +++--- typedarrays.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/builtin_typedarrays.go b/builtin_typedarrays.go index a05a507a..38c0376b 100644 --- a/builtin_typedarrays.go +++ b/builtin_typedarrays.go @@ -1226,10 +1226,10 @@ func (r *Runtime) typedArrayProto_with(call FunctionCall) Value { } var numericValue Value - if ta.typedArray.exportType() == typeBigInt64Array || - ta.typedArray.exportType() == typeBigUint64Array { + switch ta.typedArray.(type) { + case *bigInt64Array, *bigUint64Array: numericValue = toBigInt(call.Argument(1)) - } else { + default: numericValue = call.Argument(1).ToNumber() } diff --git a/typedarrays.go b/typedarrays.go index f305b1c0..67c46772 100644 --- a/typedarrays.go +++ b/typedarrays.go @@ -831,10 +831,10 @@ func (a *typedArrayObject) isValidIntegerIndex(idx int) bool { } func (a *typedArrayObject) _putIdx(idx int, v Value) { - if a.typedArray.exportType() == typeBigInt64Array || - a.typedArray.exportType() == typeBigUint64Array { + switch a.typedArray.(type) { + case *bigInt64Array, *bigUint64Array: v = toBigInt(v) - } else { + default: v = v.ToNumber() } if a.isValidIntegerIndex(idx) {