package goja import ( "errors" "fmt" "math" "reflect" "runtime" "strconv" "strings" "testing" "time" "apigo.cc/ai/ai/goja/parser" ) func TestGlobalObjectProto(t *testing.T) { const SCRIPT = ` this instanceof Object ` testScript(SCRIPT, valueTrue, t) } func TestUnicodeString(t *testing.T) { const SCRIPT = ` var s = "Тест"; s.length === 4 && s[1] === "е"; ` testScript(SCRIPT, valueTrue, t) } func Test2TierHierarchyProp(t *testing.T) { const SCRIPT = ` var a = {}; Object.defineProperty(a, "test", { value: 42, writable: false, enumerable: false, configurable: true }); var b = Object.create(a); var c = Object.create(b); c.test = 43; c.test === 42 && !b.hasOwnProperty("test"); ` testScript(SCRIPT, valueTrue, t) } func TestConstStringIter(t *testing.T) { const SCRIPT = ` var count = 0; for (var i in "1234") { for (var j in "1234567") { count++ } } count; ` testScript(SCRIPT, intToValue(28), t) } func TestUnicodeConcat(t *testing.T) { const SCRIPT = ` var s = "тест"; var s1 = "test"; var s2 = "абвгд"; s.concat(s1) === "тестtest" && s.concat(s1, s2) === "тестtestабвгд" && s1.concat(s, s2) === "testтестабвгд" && s.concat(s2) === "тестабвгд"; ` testScript(SCRIPT, valueTrue, t) } func TestIndexOf(t *testing.T) { const SCRIPT = ` "abc".indexOf("", 4) ` testScript(SCRIPT, intToValue(3), t) } func TestUnicodeIndexOf(t *testing.T) { const SCRIPT = ` "абвгд".indexOf("вг", 1) === 2 && '中国'.indexOf('国') === 1 ` testScript(SCRIPT, valueTrue, t) } func TestLastIndexOf(t *testing.T) { const SCRIPT = ` "abcabab".lastIndexOf("ab", 3) ` testScript(SCRIPT, intToValue(3), t) } func TestUnicodeLastIndexOf(t *testing.T) { const SCRIPT = ` "абвабаб".lastIndexOf("аб", 3) ` testScript(SCRIPT, intToValue(3), t) } func TestUnicodeLastIndexOf1(t *testing.T) { const SCRIPT = ` "abꞐcde".lastIndexOf("cd"); ` testScript(SCRIPT, intToValue(3), t) } func TestNumber(t *testing.T) { const SCRIPT = ` (new Number(100111122133144155)).toString() ` testScript(SCRIPT, asciiString("100111122133144160"), t) } func TestFractionalNumberToStringRadix(t *testing.T) { const SCRIPT = ` (new Number(123.456)).toString(36) ` testScript(SCRIPT, asciiString("3f.gez4w97ry"), t) } func TestNumberFormatRounding(t *testing.T) { const SCRIPT = ` assert.sameValue((123.456).toExponential(undefined), "1.23456e+2", "undefined"); assert.sameValue((0.000001).toPrecision(2), "0.0000010") assert.sameValue((-7).toPrecision(1), "-7"); assert.sameValue((-42).toPrecision(1), "-4e+1"); assert.sameValue((0.000001).toPrecision(1), "0.000001"); assert.sameValue((123.456).toPrecision(1), "1e+2", "1"); assert.sameValue((123.456).toPrecision(2), "1.2e+2", "2"); var n = new Number("0.000000000000000000001"); // 1e-21 assert.sameValue((n).toPrecision(1), "1e-21"); assert.sameValue((25).toExponential(0), "3e+1"); assert.sameValue((-25).toExponential(0), "-3e+1"); assert.sameValue((12345).toExponential(3), "1.235e+4"); assert.sameValue((25.5).toFixed(0), "26"); assert.sameValue((-25.5).toFixed(0), "-26"); assert.sameValue((99.9).toFixed(0), "100"); assert.sameValue((99.99).toFixed(1), "100.0"); ` testScriptWithTestLib(SCRIPT, _undefined, t) } func TestBinOctalNumbers(t *testing.T) { const SCRIPT = ` 0b111; ` testScript(SCRIPT, valueInt(7), t) } func TestSetFunc(t *testing.T) { const SCRIPT = ` sum(40, 2); ` r := New() err := r.Set("sum", func(call FunctionCall) Value { return r.ToValue(call.Argument(0).ToInteger() + call.Argument(1).ToInteger()) }) if err != nil { t.Fatal(err) } v, err := r.RunString(SCRIPT) if err != nil { t.Fatal(err) } if i := v.ToInteger(); i != 42 { t.Fatalf("Expected 42, got: %d", i) } } func ExampleRuntime_Set_lexical() { r := New() _, err := r.RunString("let x") if err != nil { panic(err) } err = r.Set("x", 1) if err != nil { panic(err) } fmt.Print(r.Get("x"), r.GlobalObject().Get("x")) // Output: 1 } func TestRecursiveRun(t *testing.T) { // Make sure that a recursive call to Run*() correctly sets the environment and no stash or stack // corruptions occur. vm := New() vm.Set("f", func() (Value, error) { return vm.RunString("let x = 1; { let z = 100, z1 = 200, z2 = 300, z3 = 400; x = x + z3} x;") }) res, err := vm.RunString(` function f1() { let x = 2; eval(''); { let y = 3; let res = f(); if (x !== 2) { // check for stash corruption throw new Error("x="+x); } if (y !== 3) { // check for stack corruption throw new Error("y="+y); } return res; } }; f1(); `) if err != nil { t.Fatal(err) } if !res.SameAs(valueInt(401)) { t.Fatal(res) } } func TestRecursiveRunWithNArgs(t *testing.T) { vm := New() vm.Set("f", func() (Value, error) { return vm.RunString(` { let a = 0; let b = 1; a = 2; // this used to to corrupt b, because its location on the stack was off by vm.args (1 in our case) b; } `) }) _, err := vm.RunString(` (function(arg) { // need an ES function call with an argument to set vm.args let res = f(); if (res !== 1) { throw new Error(res); } })(123); `) if err != nil { t.Fatal(err) } } func TestRecursiveRunCallee(t *testing.T) { // Make sure that a recursive call to Run*() correctly sets the callee (i.e. stack[sb-1]) vm := New() vm.Set("f", func() (Value, error) { return vm.RunString("this; (() => 1)()") }) res, err := vm.RunString(` f(123, 123); `) if err != nil { t.Fatal(err) } if !res.SameAs(valueInt(1)) { t.Fatal(res) } } func TestObjectGetSet(t *testing.T) { const SCRIPT = ` input.test++; input; ` r := New() o := r.NewObject() o.Set("test", 42) r.Set("input", o) v, err := r.RunString(SCRIPT) if err != nil { t.Fatal(err) } if o1, ok := v.(*Object); ok { if v1 := o1.Get("test"); v1.Export() != int64(43) { t.Fatalf("Unexpected test value: %v (%T)", v1, v1.Export()) } } } func TestThrowFromNativeFunc(t *testing.T) { const SCRIPT = ` var thrown; try { f(); } catch (e) { thrown = e; } thrown; ` r := New() r.Set("f", func(call FunctionCall) Value { panic(r.ToValue("testError")) }) v, err := r.RunString(SCRIPT) if err != nil { t.Fatal(err) } if !v.Equals(asciiString("testError")) { t.Fatalf("Unexpected result: %v", v) } } func TestSetGoFunc(t *testing.T) { const SCRIPT = ` f(40, 2) ` r := New() r.Set("f", func(a, b int) int { return a + b }) v, err := r.RunString(SCRIPT) if err != nil { t.Fatal(err) } if v.ToInteger() != 42 { t.Fatalf("Unexpected result: %v", v) } } func TestSetFuncVariadic(t *testing.T) { vm := New() vm.Set("f", func(s string, g ...Value) { something := g[0].ToObject(vm).Get(s).ToInteger() if something != 5 { t.Fatal() } }) _, err := vm.RunString(` f("something", {something: 5}) `) if err != nil { t.Fatal(err) } } func TestSetFuncVariadicFuncArg(t *testing.T) { vm := New() vm.Set("f", func(s string, g ...Value) { if f, ok := AssertFunction(g[0]); ok { v, err := f(nil) if err != nil { t.Fatal(err) } if v != valueTrue { t.Fatal(v) } } }) _, err := vm.RunString(` f("something", () => true) `) if err != nil { t.Fatal(err) } } func TestArgsKeys(t *testing.T) { const SCRIPT = ` function testArgs2(x, y, z) { // Properties of the arguments object are enumerable. return Object.keys(arguments); } testArgs2(1,2).length ` testScript(SCRIPT, intToValue(2), t) } func TestIPowOverflow(t *testing.T) { const SCRIPT = ` assert.sameValue(Math.pow(65536, 6), 7.922816251426434e+28); assert.sameValue(Math.pow(10, 19), 1e19); assert.sameValue(Math.pow(2097151, 3), 9223358842721534000); assert.sameValue(Math.pow(2097152, 3), 9223372036854776000); assert.sameValue(Math.pow(-2097151, 3), -9223358842721534000); assert.sameValue(Math.pow(-2097152, 3), -9223372036854776000); assert.sameValue(Math.pow(9007199254740992, 0), 1); assert.sameValue(Math.pow(-9007199254740992, 0), 1); assert.sameValue(Math.pow(0, 0), 1); ` testScriptWithTestLib(SCRIPT, _undefined, t) } func TestIPow(t *testing.T) { if res := ipow(-9223372036854775808, 1); res != -9223372036854775808 { t.Fatal(res) } if res := ipow(9223372036854775807, 1); res != 9223372036854775807 { t.Fatal(res) } if res := ipow(-9223372036854775807, 1); res != -9223372036854775807 { t.Fatal(res) } if res := ipow(9223372036854775807, 0); res != 1 { t.Fatal(res) } if res := ipow(-9223372036854775807, 0); res != 1 { t.Fatal(res) } if res := ipow(-9223372036854775808, 0); res != 1 { t.Fatal(res) } } func TestInterrupt(t *testing.T) { const SCRIPT = ` var i = 0; for (;;) { i++; } ` vm := New() time.AfterFunc(200*time.Millisecond, func() { vm.Interrupt("halt") }) _, err := vm.RunString(SCRIPT) if err == nil { t.Fatal("Err is nil") } } func TestRuntime_ExportToNumbers(t *testing.T) { vm := New() t.Run("int8/no overflow", func(t *testing.T) { var i8 int8 err := vm.ExportTo(vm.ToValue(-123), &i8) if err != nil { t.Fatal(err) } if i8 != -123 { t.Fatalf("i8: %d", i8) } }) t.Run("int8/overflow", func(t *testing.T) { var i8 int8 err := vm.ExportTo(vm.ToValue(333), &i8) if err != nil { t.Fatal(err) } if i8 != 77 { t.Fatalf("i8: %d", i8) } }) t.Run("int64/uint64", func(t *testing.T) { var ui64 uint64 err := vm.ExportTo(vm.ToValue(-1), &ui64) if err != nil { t.Fatal(err) } if ui64 != math.MaxUint64 { t.Fatalf("ui64: %d", ui64) } }) t.Run("int8/float", func(t *testing.T) { var i8 int8 err := vm.ExportTo(vm.ToValue(333.9234), &i8) if err != nil { t.Fatal(err) } if i8 != 77 { t.Fatalf("i8: %d", i8) } }) t.Run("int8/object", func(t *testing.T) { var i8 int8 err := vm.ExportTo(vm.NewObject(), &i8) if err != nil { t.Fatal(err) } if i8 != 0 { t.Fatalf("i8: %d", i8) } }) t.Run("int/object_cust_valueOf", func(t *testing.T) { var i int obj, err := vm.RunString(` ({ valueOf: function() { return 42; } }) `) if err != nil { t.Fatal(err) } err = vm.ExportTo(obj, &i) if err != nil { t.Fatal(err) } if i != 42 { t.Fatalf("i: %d", i) } }) t.Run("float32/no_trunc", func(t *testing.T) { var f float32 err := vm.ExportTo(vm.ToValue(1.234567), &f) if err != nil { t.Fatal(err) } if f != 1.234567 { t.Fatalf("f: %f", f) } }) t.Run("float32/trunc", func(t *testing.T) { var f float32 err := vm.ExportTo(vm.ToValue(1.234567890), &f) if err != nil { t.Fatal(err) } if f != float32(1.234567890) { t.Fatalf("f: %f", f) } }) t.Run("float64", func(t *testing.T) { var f float64 err := vm.ExportTo(vm.ToValue(1.234567), &f) if err != nil { t.Fatal(err) } if f != 1.234567 { t.Fatalf("f: %f", f) } }) t.Run("float32/object", func(t *testing.T) { var f float32 err := vm.ExportTo(vm.NewObject(), &f) if err != nil { t.Fatal(err) } if f == f { // expecting NaN t.Fatalf("f: %f", f) } }) t.Run("float64/object", func(t *testing.T) { var f float64 err := vm.ExportTo(vm.NewObject(), &f) if err != nil { t.Fatal(err) } if f == f { // expecting NaN t.Fatalf("f: %f", f) } }) } func TestRuntime_ExportToSlice(t *testing.T) { const SCRIPT = ` var a = [1, 2, 3]; a; ` vm := New() v, err := vm.RunString(SCRIPT) if err != nil { t.Fatal(err) } var a []string err = vm.ExportTo(v, &a) if err != nil { t.Fatal(err) } if l := len(a); l != 3 { t.Fatalf("Unexpected len: %d", l) } if a[0] != "1" || a[1] != "2" || a[2] != "3" { t.Fatalf("Unexpected value: %+v", a) } } func TestRuntime_ExportToMap(t *testing.T) { const SCRIPT = ` var m = { "0": 1, "1": 2, "2": 3, } m; ` vm := New() v, err := vm.RunString(SCRIPT) if err != nil { t.Fatal(err) } var m map[int]string err = vm.ExportTo(v, &m) if err != nil { t.Fatal(err) } if l := len(m); l != 3 { t.Fatalf("Unexpected len: %d", l) } if m[0] != "1" || m[1] != "2" || m[2] != "3" { t.Fatalf("Unexpected value: %+v", m) } } func TestRuntime_ExportToMap1(t *testing.T) { const SCRIPT = ` var m = { "0": 1, "1": 2, "2": 3, } m; ` vm := New() v, err := vm.RunString(SCRIPT) if err != nil { t.Fatal(err) } var m map[string]string err = vm.ExportTo(v, &m) if err != nil { t.Fatal(err) } if l := len(m); l != 3 { t.Fatalf("Unexpected len: %d", l) } if m["0"] != "1" || m["1"] != "2" || m["2"] != "3" { t.Fatalf("Unexpected value: %+v", m) } } func TestRuntime_ExportToStruct(t *testing.T) { const SCRIPT = ` var m = { Test: 1, } m; ` vm := New() v, err := vm.RunString(SCRIPT) if err != nil { t.Fatal(err) } var o testGoReflectMethod_O err = vm.ExportTo(v, &o) if err != nil { t.Fatal(err) } if o.Test != "1" { t.Fatalf("Unexpected value: '%s'", o.Test) } } func TestRuntime_ExportToStructPtr(t *testing.T) { const SCRIPT = ` var m = { Test: 1, } m; ` vm := New() v, err := vm.RunString(SCRIPT) if err != nil { t.Fatal(err) } var o *testGoReflectMethod_O err = vm.ExportTo(v, &o) if err != nil { t.Fatal(err) } if o.Test != "1" { t.Fatalf("Unexpected value: '%s'", o.Test) } } func TestRuntime_ExportToStructAnonymous(t *testing.T) { type BaseTestStruct struct { A int64 B int64 } type TestStruct struct { BaseTestStruct C string } const SCRIPT = ` var m = { A: 1, B: 2, C: "testC" } m; ` vm := New() v, err := vm.RunString(SCRIPT) if err != nil { t.Fatal(err) } test := &TestStruct{} err = vm.ExportTo(v, test) if err != nil { t.Fatal(err) } if test.A != 1 { t.Fatalf("Unexpected value: '%d'", test.A) } if test.B != 2 { t.Fatalf("Unexpected value: '%d'", test.B) } if test.C != "testC" { t.Fatalf("Unexpected value: '%s'", test.C) } } func TestRuntime_ExportToStructFromPtr(t *testing.T) { vm := New() v := vm.ToValue(&testGoReflectMethod_O{ field: "5", Test: "12", }) var o testGoReflectMethod_O err := vm.ExportTo(v, &o) if err != nil { t.Fatal(err) } if o.Test != "12" { t.Fatalf("Unexpected value: '%s'", o.Test) } if o.field != "5" { t.Fatalf("Unexpected value for field: '%s'", o.field) } } func TestRuntime_ExportToStructWithPtrValues(t *testing.T) { type BaseTestStruct struct { A int64 B *int64 } type TestStruct2 struct { E string } type TestStruct struct { BaseTestStruct C *string D *TestStruct2 } const SCRIPT = ` var m = { A: 1, B: 2, C: "testC", D: { E: "testE", } } m; ` vm := New() v, err := vm.RunString(SCRIPT) if err != nil { t.Fatal(err) } test := &TestStruct{} err = vm.ExportTo(v, test) if err != nil { t.Fatal(err) } if test.A != 1 { t.Fatalf("Unexpected value: '%d'", test.A) } if test.B == nil || *test.B != 2 { t.Fatalf("Unexpected value: '%v'", test.B) } if test.C == nil || *test.C != "testC" { t.Fatalf("Unexpected value: '%v'", test.C) } if test.D == nil || test.D.E != "testE" { t.Fatalf("Unexpected value: '%s'", test.D.E) } } func TestRuntime_ExportToTime(t *testing.T) { const SCRIPT = ` var dateStr = "2018-08-13T15:02:13+02:00"; var str = "test123"; ` vm := New() _, err := vm.RunString(SCRIPT) if err != nil { t.Fatal(err) } var ti time.Time err = vm.ExportTo(vm.Get("dateStr"), &ti) if err != nil { t.Fatal(err) } if ti.Format(time.RFC3339) != "2018-08-13T15:02:13+02:00" { t.Fatalf("Unexpected value: '%s'", ti.Format(time.RFC3339)) } err = vm.ExportTo(vm.Get("str"), &ti) if err == nil { t.Fatal("Expected err to not be nil") } var str string err = vm.ExportTo(vm.Get("dateStr"), &str) if err != nil { t.Fatal(err) } if str != "2018-08-13T15:02:13+02:00" { t.Fatalf("Unexpected value: '%s'", str) } d, err := vm.RunString(`new Date(1000)`) if err != nil { t.Fatal(err) } ti = time.Time{} err = vm.ExportTo(d, &ti) if err != nil { t.Fatal(err) } if ti.UnixNano() != 1000*1e6 { t.Fatal(ti) } if ti.Location() != time.Local { t.Fatalf("Wrong location: %v", ti) } } func ExampleRuntime_ExportTo_func() { const SCRIPT = ` function f(param) { return +param + 2; } ` vm := New() _, err := vm.RunString(SCRIPT) if err != nil { panic(err) } var fn func(string) string err = vm.ExportTo(vm.Get("f"), &fn) if err != nil { panic(err) } fmt.Println(fn("40")) // note, _this_ value in the function will be undefined. // Output: 42 } func ExampleRuntime_ExportTo_funcThrow() { const SCRIPT = ` function f(param) { throw new Error("testing"); } ` vm := New() _, err := vm.RunString(SCRIPT) if err != nil { panic(err) } var fn func(string) (string, error) err = vm.ExportTo(vm.Get("f"), &fn) if err != nil { panic(err) } _, err = fn("") fmt.Println(err) // Output: Error: testing at f (:3:9(3)) } func ExampleRuntime_ExportTo_funcVariadic() { const SCRIPT = ` function f(...args) { return args.join("#"); } ` vm := New() _, err := vm.RunString(SCRIPT) if err != nil { panic(err) } var fn func(args ...interface{}) string err = vm.ExportTo(vm.Get("f"), &fn) if err != nil { panic(err) } fmt.Println(fn("a", "b", 42)) // Output: a#b#42 } func TestRuntime_ExportTo_funcVariadic(t *testing.T) { const SCRIPT = ` function f(...args) { return args.join("#"); } ` vm := New() _, err := vm.RunString(SCRIPT) if err != nil { panic(err) } t.Run("no args", func(t *testing.T) { var fn func(args ...any) string err = vm.ExportTo(vm.Get("f"), &fn) if err != nil { panic(err) } res := fn() if res != "" { t.Fatal(res) } }) t.Run("non-variadic args", func(t *testing.T) { var fn func(firstArg any, args ...any) string err = vm.ExportTo(vm.Get("f"), &fn) if err != nil { panic(err) } res := fn("first") if res != "first" { t.Fatal(res) } }) t.Run("non-variadic and variadic args", func(t *testing.T) { var fn func(firstArg any, args ...any) string err = vm.ExportTo(vm.Get("f"), &fn) if err != nil { panic(err) } res := fn("first", "second") if res != "first#second" { t.Fatal(res) } }) } func TestRuntime_ExportToFuncFail(t *testing.T) { const SCRIPT = ` function f(param) { return +param + 2; } ` type T struct { Field1 int } var fn func(string) (T, error) vm := New() _, err := vm.RunString(SCRIPT) if err != nil { t.Fatal(err) } err = vm.ExportTo(vm.Get("f"), &fn) if err != nil { t.Fatal(err) } if _, err := fn("40"); err == nil { t.Fatal("Expected error") } } func TestRuntime_ExportToCallable(t *testing.T) { const SCRIPT = ` function f(param) { return +param + 2; } ` vm := New() _, err := vm.RunString(SCRIPT) if err != nil { t.Fatal(err) } var c Callable err = vm.ExportTo(vm.Get("f"), &c) if err != nil { t.Fatal(err) } res, err := c(Undefined(), vm.ToValue("40")) if err != nil { t.Fatal(err) } else if !res.StrictEquals(vm.ToValue(42)) { t.Fatalf("Unexpected value: %v", res) } } func TestRuntime_ExportToObject(t *testing.T) { const SCRIPT = ` var o = {"test": 42}; o; ` vm := New() _, err := vm.RunString(SCRIPT) if err != nil { t.Fatal(err) } var o *Object err = vm.ExportTo(vm.Get("o"), &o) if err != nil { t.Fatal(err) } if v := o.Get("test"); !v.StrictEquals(vm.ToValue(42)) { t.Fatalf("Unexpected value: %v", v) } } func ExampleAssertFunction() { vm := New() _, err := vm.RunString(` function sum(a, b) { return a+b; } `) if err != nil { panic(err) } sum, ok := AssertFunction(vm.Get("sum")) if !ok { panic("Not a function") } res, err := sum(Undefined(), vm.ToValue(40), vm.ToValue(2)) if err != nil { panic(err) } fmt.Println(res) // Output: 42 } func TestGoFuncError(t *testing.T) { const SCRIPT = ` try { f(); } catch (e) { if (!(e instanceof GoError)) { throw(e); } if (e.value.Error() !== "Test") { throw("Unexpected value: " + e.value.Error()); } } ` f := func() error { return errors.New("Test") } vm := New() vm.Set("f", f) _, err := vm.RunString(SCRIPT) if err != nil { t.Fatal(err) } } func TestToValueNil(t *testing.T) { type T struct{} var a *T vm := New() if v := vm.ToValue(nil); !IsNull(v) { t.Fatalf("nil: %v", v) } if v := vm.ToValue(a); !IsNull(v) { t.Fatalf("struct ptr: %v", v) } var m map[string]interface{} if v := vm.ToValue(m); !IsNull(v) { t.Fatalf("map[string]interface{}: %v", v) } var ar []interface{} if v := vm.ToValue(ar); IsNull(v) { t.Fatalf("[]interface{}: %v", v) } var arptr *[]interface{} if v := vm.ToValue(arptr); !IsNull(v) { t.Fatalf("*[]interface{}: %v", v) } } func TestToValueFloat(t *testing.T) { vm := New() vm.Set("f64", float64(123)) vm.Set("f32", float32(321)) v, err := vm.RunString("f64 === 123 && f32 === 321") if err != nil { t.Fatal(err) } if v.Export().(bool) != true { t.Fatalf("StrictEquals for golang float failed") } } func TestToValueInterface(t *testing.T) { f := func(i interface{}) bool { return i == t } vm := New() vm.Set("f", f) vm.Set("t", t) v, err := vm.RunString(`f(t)`) if err != nil { t.Fatal(err) } if v != valueTrue { t.Fatalf("v: %v", v) } } func TestJSONEscape(t *testing.T) { const SCRIPT = ` var a = "\\+1"; JSON.stringify(a); ` testScript(SCRIPT, asciiString(`"\\+1"`), t) } func TestJSONObjectInArray(t *testing.T) { const SCRIPT = ` var a = "[{\"a\":1},{\"a\":2}]"; JSON.stringify(JSON.parse(a)) == a; ` testScript(SCRIPT, valueTrue, t) } func TestJSONQuirkyNumbers(t *testing.T) { const SCRIPT = ` var s; s = JSON.stringify(NaN); if (s != "null") { throw new Error("NaN: " + s); } s = JSON.stringify(Infinity); if (s != "null") { throw new Error("Infinity: " + s); } s = JSON.stringify(-Infinity); if (s != "null") { throw new Error("-Infinity: " + s); } ` testScript(SCRIPT, _undefined, t) } func TestJSONNil(t *testing.T) { const SCRIPT = ` JSON.stringify(i); ` vm := New() var i interface{} vm.Set("i", i) ret, err := vm.RunString(SCRIPT) if err != nil { t.Fatal(err) } if ret.String() != "null" { t.Fatalf("Expected 'null', got: %v", ret) } } type customJsonEncodable struct{} func (*customJsonEncodable) JsonEncodable() interface{} { return "Test" } func TestJsonEncodable(t *testing.T) { var s customJsonEncodable vm := New() vm.Set("s", &s) ret, err := vm.RunString("JSON.stringify(s)") if err != nil { t.Fatal(err) } if !ret.StrictEquals(vm.ToValue("\"Test\"")) { t.Fatalf("Expected \"Test\", got: %v", ret) } } func TestSortComparatorReturnValues(t *testing.T) { const SCRIPT = ` var a = []; for (var i = 0; i < 12; i++) { a[i] = i; } a.sort(function(x, y) { return y - x }); for (var i = 0; i < 12; i++) { if (a[i] !== 11-i) { throw new Error("Value at index " + i + " is incorrect: " + a[i]); } } ` testScript(SCRIPT, _undefined, t) } func TestSortComparatorReturnValueFloats(t *testing.T) { const SCRIPT = ` var a = [ 5.97, 9.91, 4.13, 9.28, 3.29, ]; a.sort( function(a, b) { return a - b; } ); for (var i = 1; i < a.length; i++) { if (a[i] < a[i-1]) { throw new Error("Array is not sorted: " + a); } } ` testScript(SCRIPT, _undefined, t) } func TestSortComparatorReturnValueNegZero(t *testing.T) { const SCRIPT = ` var a = [2, 1]; a.sort( function(a, b) { return a > b ? 0 : -0; } ); for (var i = 1; i < a.length; i++) { if (a[i] < a[i-1]) { throw new Error("Array is not sorted: " + a); } } ` testScript(SCRIPT, _undefined, t) } func TestNilApplyArg(t *testing.T) { const SCRIPT = ` (function x(a, b) { return a === undefined && b === 1; }).apply(this, [,1]) ` testScript(SCRIPT, valueTrue, t) } func TestNilCallArg(t *testing.T) { const SCRIPT = ` "use strict"; function f(a) { return this === undefined && a === undefined; } ` vm := New() prg := MustCompile("test.js", SCRIPT, false) vm.RunProgram(prg) if f, ok := AssertFunction(vm.Get("f")); ok { v, err := f(nil, nil) if err != nil { t.Fatal(err) } if !v.StrictEquals(valueTrue) { t.Fatalf("Unexpected result: %v", v) } } } func TestNullCallArg(t *testing.T) { const SCRIPT = ` f(null); ` vm := New() prg := MustCompile("test.js", SCRIPT, false) vm.Set("f", func(x *int) bool { return x == nil }) v, err := vm.RunProgram(prg) if err != nil { t.Fatal(err) } if !v.StrictEquals(valueTrue) { t.Fatalf("Unexpected result: %v", v) } } func TestObjectKeys(t *testing.T) { const SCRIPT = ` var o = { a: 1, b: 2, c: 3, d: 4 }; o; ` vm := New() prg := MustCompile("test.js", SCRIPT, false) res, err := vm.RunProgram(prg) if err != nil { t.Fatal(err) } if o, ok := res.(*Object); ok { keys := o.Keys() if !reflect.DeepEqual(keys, []string{"a", "b", "c", "d"}) { t.Fatalf("Unexpected keys: %v", keys) } } } func TestReflectCallExtraArgs(t *testing.T) { const SCRIPT = ` f(41, "extra") ` f := func(x int) int { return x + 1 } vm := New() vm.Set("f", f) prg := MustCompile("test.js", SCRIPT, false) res, err := vm.RunProgram(prg) if err != nil { t.Fatal(err) } if !res.StrictEquals(intToValue(42)) { t.Fatalf("Unexpected result: %v", res) } } func TestReflectCallNotEnoughArgs(t *testing.T) { const SCRIPT = ` f(42) ` vm := New() f := func(x, y int, z *int, s string) (int, error) { if z != nil { return 0, fmt.Errorf("z is not nil") } if s != "" { return 0, fmt.Errorf("s is not \"\"") } return x + y, nil } vm.Set("f", f) prg := MustCompile("test.js", SCRIPT, false) res, err := vm.RunProgram(prg) if err != nil { t.Fatal(err) } if !res.StrictEquals(intToValue(42)) { t.Fatalf("Unexpected result: %v", res) } } func TestReflectCallVariadic(t *testing.T) { const SCRIPT = ` var r = f("Hello %s, %d", "test", 42); if (r !== "Hello test, 42") { throw new Error("test 1 has failed: " + r); } r = f("Hello %s, %s", "test"); if (r !== "Hello test, %!s(MISSING)") { throw new Error("test 2 has failed: " + r); } r = f(); if (r !== "") { throw new Error("test 3 has failed: " + r); } ` vm := New() vm.Set("f", fmt.Sprintf) prg := MustCompile("test.js", SCRIPT, false) _, err := vm.RunProgram(prg) if err != nil { t.Fatal(err) } } func TestReflectNullValueArgument(t *testing.T) { rt := New() rt.Set("fn", func(v Value) { if v == nil { t.Error("null becomes nil") } if !IsNull(v) { t.Error("null is not null") } }) rt.RunString(`fn(null);`) } type testNativeConstructHelper struct { rt *Runtime base int64 // any other state } func (t *testNativeConstructHelper) calc(call FunctionCall) Value { return t.rt.ToValue(t.base + call.Argument(0).ToInteger()) } func TestNativeConstruct(t *testing.T) { const SCRIPT = ` var f = new F(40); f instanceof F && f.method() === 42 && f.calc(2) === 42; ` rt := New() method := func(call FunctionCall) Value { return rt.ToValue(42) } rt.Set("F", func(call ConstructorCall) *Object { // constructor signature (as opposed to 'func(FunctionCall) Value') h := &testNativeConstructHelper{ rt: rt, base: call.Argument(0).ToInteger(), } call.This.Set("method", method) call.This.Set("calc", h.calc) return nil // or any other *Object which will be used instead of call.This }) prg := MustCompile("test.js", SCRIPT, false) res, err := rt.RunProgram(prg) if err != nil { t.Fatal(err) } if !res.StrictEquals(valueTrue) { t.Fatalf("Unexpected result: %v", res) } if fn, ok := AssertFunction(rt.Get("F")); ok { v, err := fn(nil, rt.ToValue(42)) if err != nil { t.Fatal(err) } if o, ok := v.(*Object); ok { if o.Get("method") == nil { t.Fatal("No method") } } else { t.Fatal("Not an object") } } else { t.Fatal("Not a function") } resp := &testNativeConstructHelper{} value := rt.ToValue(resp) if value.Export() != resp { t.Fatal("no") } } func TestCreateObject(t *testing.T) { const SCRIPT = ` inst instanceof C; ` r := New() c := r.ToValue(func(call ConstructorCall) *Object { return nil }) proto := c.(*Object).Get("prototype").(*Object) inst := r.CreateObject(proto) r.Set("C", c) r.Set("inst", inst) prg := MustCompile("test.js", SCRIPT, false) res, err := r.RunProgram(prg) if err != nil { t.Fatal(err) } if !res.StrictEquals(valueTrue) { t.Fatalf("Unexpected result: %v", res) } } func TestInterruptInWrappedFunction(t *testing.T) { rt := New() v, err := rt.RunString(` var fn = function() { while (true) {} }; fn; `) if err != nil { t.Fatal(err) } fn, ok := AssertFunction(v) if !ok { t.Fatal("Not a function") } go func() { <-time.After(10 * time.Millisecond) rt.Interrupt(errors.New("hi")) }() _, err = fn(nil) if err == nil { t.Fatal("expected error") } if _, ok := err.(*InterruptedError); !ok { t.Fatalf("Wrong error type: %T", err) } } func TestInterruptInWrappedFunction2(t *testing.T) { rt := New() // this test panics as otherwise goja will recover and possibly loop var called bool rt.Set("v", rt.ToValue(func() { if called { go func() { panic("this should never get called twice") }() } called = true rt.Interrupt("here is the error") })) rt.Set("s", rt.ToValue(func(a Callable) (Value, error) { return a(nil) })) rt.Set("k", rt.ToValue(func(e Value) { go func() { panic("this should never get called actually") }() })) _, err := rt.RunString(` Promise.resolve().then(()=>k()); // this should never resolve while(true) { try{ s(() =>{ v(); }) break; } catch (e) { k(e); } } `) if err == nil { t.Fatal("expected error but got no error") } intErr := new(InterruptedError) if !errors.As(err, &intErr) { t.Fatalf("Wrong error type: %T", err) } if !strings.Contains(intErr.Error(), "here is the error") { t.Fatalf("Wrong error message: %q", intErr.Error()) } _, err = rt.RunString(`Promise.resolve().then(()=>globalThis.S=5)`) if err != nil { t.Fatal(err) } s := rt.Get("S") if s == nil || s.ToInteger() != 5 { t.Fatalf("Wrong value for S %v", s) } } func TestInterruptInWrappedFunction2Recover(t *testing.T) { rt := New() // this test panics as otherwise goja will recover and possibly loop var vCalled int rt.Set("v", rt.ToValue(func() { if vCalled == 0 { rt.Interrupt("here is the error") } vCalled++ })) rt.Set("s", rt.ToValue(func(a Callable) (Value, error) { v, err := a(nil) if err != nil { intErr := new(InterruptedError) if errors.As(err, &intErr) { rt.ClearInterrupt() return nil, errors.New("oops we got interrupted let's not that") } } return v, err })) var kCalled int rt.Set("k", rt.ToValue(func(e Value) { kCalled++ })) _, err := rt.RunString(` Promise.resolve().then(()=>k()); while(true) { try{ s(() => { v(); }) break; } catch (e) { k(e); } } `) if err != nil { t.Fatal(err) } if vCalled != 2 { t.Fatalf("v was not called exactly twice but %d times", vCalled) } if kCalled != 2 { t.Fatalf("k was not called exactly twice but %d times", kCalled) } _, err = rt.RunString(`Promise.resolve().then(()=>globalThis.S=5)`) if err != nil { t.Fatal(err) } s := rt.Get("S") if s == nil || s.ToInteger() != 5 { t.Fatalf("Wrong value for S %v", s) } } func TestInterruptInWrappedFunctionExpectInteruptError(t *testing.T) { rt := New() // this test panics as otherwise goja will recover and possibly loop rt.Set("v", rt.ToValue(func() { rt.Interrupt("here is the error") })) rt.Set("s", rt.ToValue(func(a Callable) (Value, error) { return a(nil) })) _, err := rt.RunString(` s(() =>{ v(); }) `) if err == nil { t.Fatal("expected error but got no error") } var intErr *InterruptedError if !errors.As(err, &intErr) { t.Fatalf("Wrong error type: %T", err) } if !strings.Contains(intErr.Error(), "here is the error") { t.Fatalf("Wrong error message: %q", intErr.Error()) } } func TestInterruptInWrappedFunctionExpectStackOverflowError(t *testing.T) { rt := New() rt.SetMaxCallStackSize(5) // this test panics as otherwise goja will recover and possibly loop rt.Set("v", rt.ToValue(func() { _, err := rt.RunString(` (function loop() { loop() })(); `) if err != nil { panic(err) } })) rt.Set("s", rt.ToValue(func(a Callable) (Value, error) { return a(nil) })) _, err := rt.RunString(` s(() =>{ v(); }) `) if err == nil { t.Fatal("expected error but got no error") } var soErr *StackOverflowError if !errors.As(err, &soErr) { t.Fatalf("Wrong error type: %T", err) } } func TestRunLoopPreempt(t *testing.T) { vm := New() v, err := vm.RunString("(function() {for (;;) {}})") if err != nil { t.Fatal(err) } fn, ok := AssertFunction(v) if !ok { t.Fatal("Not a function") } go func() { <-time.After(100 * time.Millisecond) runtime.GC() // this hangs if the vm loop does not have any preemption points vm.Interrupt(errors.New("hi")) }() _, err = fn(nil) if err == nil { t.Fatal("expected error") } if _, ok := err.(*InterruptedError); !ok { t.Fatalf("Wrong error type: %T", err) } } func TestNaN(t *testing.T) { if !IsNaN(_NaN) { t.Fatal("IsNaN() doesn't detect NaN") } if IsNaN(Undefined()) { t.Fatal("IsNaN() says undefined is a NaN") } if !IsNaN(NaN()) { t.Fatal("NaN() doesn't return NaN") } } func TestInf(t *testing.T) { if !IsInfinity(_positiveInf) { t.Fatal("IsInfinity() doesn't detect +Inf") } if !IsInfinity(_negativeInf) { t.Fatal("IsInfinity() doesn't detect -Inf") } if IsInfinity(Undefined()) { t.Fatal("IsInfinity() says undefined is a Infinity") } if !IsInfinity(PositiveInf()) { t.Fatal("PositiveInfinity() doesn't return Inf") } if !IsInfinity(NegativeInf()) { t.Fatal("NegativeInfinity() doesn't return Inf") } } func TestRuntimeNew(t *testing.T) { vm := New() v, err := vm.New(vm.Get("Number"), vm.ToValue("12345")) if err != nil { t.Fatal(err) } if n, ok := v.Export().(int64); ok { if n != 12345 { t.Fatalf("n: %v", n) } } else { t.Fatalf("v: %T", v) } } func TestAutoBoxing(t *testing.T) { const SCRIPT = ` function f() { 'use strict'; var a = 1; var thrown1 = false; var thrown2 = false; try { a.test = 42; } catch (e) { thrown1 = e instanceof TypeError; } try { a["test1"] = 42; } catch (e) { thrown2 = e instanceof TypeError; } return thrown1 && thrown2; } var a = 1; a.test = 42; // should not throw a["test1"] = 42; // should not throw a.test === undefined && a.test1 === undefined && f(); ` testScript(SCRIPT, valueTrue, t) } func TestProtoGetter(t *testing.T) { const SCRIPT = ` ({}).__proto__ === Object.prototype && [].__proto__ === Array.prototype; ` testScript(SCRIPT, valueTrue, t) } func TestSymbol1(t *testing.T) { const SCRIPT = ` Symbol.toPrimitive[Symbol.toPrimitive]() === Symbol.toPrimitive; ` testScript(SCRIPT, valueTrue, t) } func TestFreezeSymbol(t *testing.T) { const SCRIPT = ` var s = Symbol(1); var o = {}; o[s] = 42; Object.freeze(o); o[s] = 43; o[s] === 42 && Object.isFrozen(o); ` testScript(SCRIPT, valueTrue, t) } func TestToPropertyKey(t *testing.T) { const SCRIPT = ` var sym = Symbol(42); var callCount = 0; var wrapper = { toString: function() { callCount += 1; return sym; }, valueOf: function() { $ERROR("valueOf() called"); } }; var o = {}; o[wrapper] = function() { return "test" }; assert.sameValue(o[wrapper], o[sym], "o[wrapper] === o[sym]"); assert.sameValue(o[wrapper](), "test", "o[wrapper]()"); assert.sameValue(o[sym](), "test", "o[sym]()"); var wrapper1 = {}; wrapper1[Symbol.toPrimitive] = function(hint) { if (hint === "string" || hint === "default") { return "1"; } if (hint === "number") { return 2; } $ERROR("Unknown hint value "+hint); }; var a = []; a[wrapper1] = 42; assert.sameValue(a[1], 42, "a[1]"); assert.sameValue(a[1], a[wrapper1], "a[1] === a[wrapper1]"); ` testScriptWithTestLib(SCRIPT, _undefined, t) } func TestPrimThisValue(t *testing.T) { const SCRIPT = ` function t() { 'use strict'; Boolean.prototype.toString = function() { return typeof this; }; assert.sameValue(true.toLocaleString(), "boolean"); Boolean.prototype[Symbol.iterator] = function() { return [typeof this][Symbol.iterator](); } var s = new Set(true); assert.sameValue(s.size, 1, "size"); assert.sameValue(s.has("boolean"), true, "s.has('boolean')"); } t(); ` testScriptWithTestLib(SCRIPT, _undefined, t) } func TestPrimThisValueGetter(t *testing.T) { const SCRIPT = ` function t() { 'use strict'; Object.defineProperty(Boolean.prototype, "toString", { get: function() { var v = typeof this; return function() { return v; }; } }); assert.sameValue(true.toLocaleString(), "boolean"); } t(); ` testScriptWithTestLib(SCRIPT, _undefined, t) } func TestObjSetSym(t *testing.T) { const SCRIPT = ` 'use strict'; var sym = Symbol(true); var p1 = Object.create(null); var p2 = Object.create(p1); Object.defineProperty(p1, sym, { value: 42 }); Object.defineProperty(p2, sym, { value: 43, writable: true, }); var o = Object.create(p2); o[sym] = 44; o[sym]; ` testScript(SCRIPT, intToValue(44), t) } func TestObjSet(t *testing.T) { const SCRIPT = ` 'use strict'; var p1 = Object.create(null); var p2 = Object.create(p1); Object.defineProperty(p1, "test", { value: 42 }); Object.defineProperty(p2, "test", { value: 43, writable: true, }); var o = Object.create(p2); o.test = 44; o.test; ` testScript(SCRIPT, intToValue(44), t) } func TestToValueNilValue(t *testing.T) { r := New() var a Value r.Set("a", a) ret, err := r.RunString(` ""+a; `) if err != nil { t.Fatal(err) } if !asciiString("null").SameAs(ret) { t.Fatalf("ret: %v", ret) } } func TestDateConversion(t *testing.T) { now := time.Now() vm := New() val, err := vm.New(vm.Get("Date").ToObject(vm), vm.ToValue(now.UnixNano()/1e6)) if err != nil { t.Fatal(err) } vm.Set("d", val) res, err := vm.RunString(`+d`) if err != nil { t.Fatal(err) } if exp := res.Export(); exp != now.UnixNano()/1e6 { t.Fatalf("Value does not match: %v", exp) } vm.Set("goval", now) res, err = vm.RunString(`+(new Date(goval.UnixNano()/1e6))`) if err != nil { t.Fatal(err) } if exp := res.Export(); exp != now.UnixNano()/1e6 { t.Fatalf("Value does not match: %v", exp) } } func TestNativeCtorNewTarget(t *testing.T) { const SCRIPT = ` function NewTarget() { } var o = Reflect.construct(Number, [1], NewTarget); o.__proto__ === NewTarget.prototype && o.toString() === "[object Number]"; ` testScript(SCRIPT, valueTrue, t) } func TestNativeCtorNonNewCall(t *testing.T) { vm := New() vm.Set(`Animal`, func(call ConstructorCall) *Object { obj := call.This obj.Set(`name`, call.Argument(0).String()) obj.Set(`eat`, func(call FunctionCall) Value { self := call.This.(*Object) return vm.ToValue(fmt.Sprintf("%s eat", self.Get(`name`))) }) return nil }) v, err := vm.RunString(` function __extends(d, b){ function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } var Cat = (function (_super) { __extends(Cat, _super); function Cat() { return _super.call(this, "cat") || this; } return Cat; }(Animal)); var cat = new Cat(); cat instanceof Cat && cat.eat() === "cat eat"; `) if err != nil { t.Fatal(err) } if v != valueTrue { t.Fatal(v) } } func ExampleNewSymbol() { sym1 := NewSymbol("66") sym2 := NewSymbol("66") fmt.Printf("%s %s %v", sym1, sym2, sym1.Equals(sym2)) // Output: 66 66 false } func ExampleObject_SetSymbol() { type IterResult struct { Done bool Value Value } vm := New() vm.SetFieldNameMapper(UncapFieldNameMapper()) // to use IterResult o := vm.NewObject() o.SetSymbol(SymIterator, func() *Object { count := 0 iter := vm.NewObject() iter.Set("next", func() IterResult { if count < 10 { count++ return IterResult{ Value: vm.ToValue(count), } } return IterResult{ Done: true, } }) return iter }) vm.Set("o", o) res, err := vm.RunString(` var acc = ""; for (var v of o) { acc += v + " "; } acc; `) if err != nil { panic(err) } fmt.Println(res) // Output: 1 2 3 4 5 6 7 8 9 10 } func ExampleRuntime_NewArray() { vm := New() array := vm.NewArray(1, 2, true) vm.Set("array", array) res, err := vm.RunString(` var acc = ""; for (var v of array) { acc += v + " "; } acc; `) if err != nil { panic(err) } fmt.Println(res) // Output: 1 2 true } func ExampleRuntime_SetParserOptions() { vm := New() vm.SetParserOptions(parser.WithDisableSourceMaps) res, err := vm.RunString(` "I did not hang!"; //# sourceMappingURL=/dev/zero`) if err != nil { panic(err) } fmt.Println(res.String()) // Output: I did not hang! } func TestRuntime_SetParserOptions_Eval(t *testing.T) { vm := New() vm.SetParserOptions(parser.WithDisableSourceMaps) _, err := vm.RunString(` eval("//# sourceMappingURL=/dev/zero"); `) if err != nil { t.Fatal(err) } } func TestNativeCallWithRuntimeParameter(t *testing.T) { vm := New() vm.Set("f", func(_ FunctionCall, r *Runtime) Value { if r == vm { return valueTrue } return valueFalse }) ret, err := vm.RunString(`f()`) if err != nil { t.Fatal(err) } if ret != valueTrue { t.Fatal(ret) } } func TestNestedEnumerate(t *testing.T) { const SCRIPT = ` var o = {baz: true, foo: true, bar: true}; var res = ""; for (var i in o) { delete o.baz; Object.defineProperty(o, "hidden", {value: true, configurable: true}); for (var j in o) { Object.defineProperty(o, "0", {value: true, configurable: true}); Object.defineProperty(o, "1", {value: true, configurable: true}); for (var k in o) {} res += i + "-" + j + " "; } } assert(compareArray(Reflect.ownKeys(o), ["0","1","foo","bar","hidden"]), "keys"); res; ` testScriptWithTestLib(SCRIPT, asciiString("baz-foo baz-bar foo-foo foo-bar bar-foo bar-bar "), t) } func TestAbandonedEnumerate(t *testing.T) { const SCRIPT = ` var o = {baz: true, foo: true, bar: true}; var res = ""; for (var i in o) { delete o.baz; for (var j in o) { res += i + "-" + j + " "; break; } } res; ` testScript(SCRIPT, asciiString("baz-foo foo-foo bar-foo "), t) } func TestIterCloseThrows(t *testing.T) { const SCRIPT = ` var returnCount = 0; var iterable = {}; var iterator = { next: function() { return { value: true }; }, return: function() { returnCount += 1; throw new Error(); } }; iterable[Symbol.iterator] = function() { return iterator; }; try { for (var i of iterable) { break; } } catch (e) {}; returnCount; ` testScript(SCRIPT, valueInt(1), t) } func TestDeclareGlobalFunc(t *testing.T) { const SCRIPT = ` var initial; Object.defineProperty(this, 'f', { enumerable: true, writable: true, configurable: false }); (0,eval)('initial = f; function f() { return 2222; }'); var desc = Object.getOwnPropertyDescriptor(this, "f"); assert(desc.enumerable, "enumerable"); assert(desc.writable, "writable"); assert(!desc.configurable, "configurable"); assert.sameValue(initial(), 2222); ` testScriptWithTestLib(SCRIPT, _undefined, t) } func TestStackOverflowError(t *testing.T) { vm := New() vm.SetMaxCallStackSize(3) _, err := vm.RunString(` function f() { f(); } f(); `) if _, ok := err.(*StackOverflowError); !ok { t.Fatal(err) } } func TestStacktraceLocationThrowFromCatch(t *testing.T) { vm := New() _, err := vm.RunString(` function main(arg) { try { if (arg === 1) { return f1(); } if (arg === 2) { return f2(); } if (arg === 3) { return f3(); } } catch (e) { throw e; } } function f1() {} function f2() { throw new Error(); } function f3() {} main(2); `) if err == nil { t.Fatal("Expected error") } stack := err.(*Exception).stack if len(stack) != 3 { t.Fatalf("Unexpected stack len: %v", stack) } if frame := stack[0]; frame.funcName != "f2" || frame.pc != 2 { t.Fatalf("Unexpected stack frame 0: %#v", frame) } if frame := stack[1]; frame.funcName != "main" || frame.pc != 17 { t.Fatalf("Unexpected stack frame 1: %#v", frame) } if frame := stack[2]; frame.funcName != "" || frame.pc != 7 { t.Fatalf("Unexpected stack frame 2: %#v", frame) } } func TestErrorStackRethrow(t *testing.T) { const SCRIPT = ` function f(e) { throw e; } try { f(new Error()); } catch(e) { assertStack(e, [["test.js", "", 6, 5]]); } ` testScriptWithTestLibX(SCRIPT, _undefined, t) } func TestStacktraceLocationThrowFromGo(t *testing.T) { vm := New() f := func() { panic(vm.ToValue("Test")) } vm.Set("f", f) _, err := vm.RunString(` function main() { (function noop() {})(); return callee(); } function callee() { return f(); } main(); `) if err == nil { t.Fatal("Expected error") } stack := err.(*Exception).stack if len(stack) != 4 { t.Fatalf("Unexpected stack len: %v", stack) } if frame := stack[0]; !strings.HasSuffix(frame.funcName.String(), "TestStacktraceLocationThrowFromGo.func1") { t.Fatalf("Unexpected stack frame 0: %#v", frame) } if frame := stack[1]; frame.funcName != "callee" || frame.pc != 2 { t.Fatalf("Unexpected stack frame 1: %#v", frame) } if frame := stack[2]; frame.funcName != "main" || frame.pc != 6 { t.Fatalf("Unexpected stack frame 2: %#v", frame) } if frame := stack[3]; frame.funcName != "" || frame.pc != 4 { t.Fatalf("Unexpected stack frame 3: %#v", frame) } } func TestStacktraceLocationThrowNativeInTheMiddle(t *testing.T) { vm := New() v, err := vm.RunString(`(function f1() { throw new Error("test") })`) if err != nil { t.Fatal(err) } var f1 func() err = vm.ExportTo(v, &f1) if err != nil { t.Fatal(err) } f := func() { f1() } vm.Set("f", f) _, err = vm.RunString(` function main() { (function noop() {})(); return callee(); } function callee() { return f(); } main(); `) if err == nil { t.Fatal("Expected error") } stack := err.(*Exception).stack if len(stack) != 5 { t.Fatalf("Unexpected stack len: %v", stack) } if frame := stack[0]; frame.funcName != "f1" || frame.pc != 7 { t.Fatalf("Unexpected stack frame 0: %#v", frame) } if frame := stack[1]; !strings.HasSuffix(frame.funcName.String(), "TestStacktraceLocationThrowNativeInTheMiddle.func1") { t.Fatalf("Unexpected stack frame 1: %#v", frame) } if frame := stack[2]; frame.funcName != "callee" || frame.pc != 2 { t.Fatalf("Unexpected stack frame 2: %#v", frame) } if frame := stack[3]; frame.funcName != "main" || frame.pc != 6 { t.Fatalf("Unexpected stack frame 3: %#v", frame) } if frame := stack[4]; frame.funcName != "" || frame.pc != 4 { t.Fatalf("Unexpected stack frame 4: %#v", frame) } } func TestStrToInt64(t *testing.T) { if _, ok := strToInt64(""); ok { t.Fatal("") } if n, ok := strToInt64("0"); !ok || n != 0 { t.Fatal("0", n, ok) } if n, ok := strToInt64("-0"); ok { t.Fatal("-0", n, ok) } if n, ok := strToInt64("-1"); !ok || n != -1 { t.Fatal("-1", n, ok) } if n, ok := strToInt64("9223372036854775808"); ok { t.Fatal("max+1", n, ok) } if n, ok := strToInt64("9223372036854775817"); ok { t.Fatal("9223372036854775817", n, ok) } if n, ok := strToInt64("-9223372036854775818"); ok { t.Fatal("-9223372036854775818", n, ok) } if n, ok := strToInt64("9223372036854775807"); !ok || n != 9223372036854775807 { t.Fatal("max", n, ok) } if n, ok := strToInt64("-9223372036854775809"); ok { t.Fatal("min-1", n, ok) } if n, ok := strToInt64("-9223372036854775808"); !ok || n != -9223372036854775808 { t.Fatal("min", n, ok) } if n, ok := strToInt64("-00"); ok { t.Fatal("-00", n, ok) } if n, ok := strToInt64("-01"); ok { t.Fatal("-01", n, ok) } } func TestStrToInt32(t *testing.T) { if _, ok := strToInt32(""); ok { t.Fatal("") } if n, ok := strToInt32("0"); !ok || n != 0 { t.Fatal("0", n, ok) } if n, ok := strToInt32("-0"); ok { t.Fatal("-0", n, ok) } if n, ok := strToInt32("-1"); !ok || n != -1 { t.Fatal("-1", n, ok) } if n, ok := strToInt32("2147483648"); ok { t.Fatal("max+1", n, ok) } if n, ok := strToInt32("2147483657"); ok { t.Fatal("2147483657", n, ok) } if n, ok := strToInt32("-2147483658"); ok { t.Fatal("-2147483658", n, ok) } if n, ok := strToInt32("2147483647"); !ok || n != 2147483647 { t.Fatal("max", n, ok) } if n, ok := strToInt32("-2147483649"); ok { t.Fatal("min-1", n, ok) } if n, ok := strToInt32("-2147483648"); !ok || n != -2147483648 { t.Fatal("min", n, ok) } if n, ok := strToInt32("-00"); ok { t.Fatal("-00", n, ok) } if n, ok := strToInt32("-01"); ok { t.Fatal("-01", n, ok) } } func TestDestructSymbol(t *testing.T) { const SCRIPT = ` var S = Symbol("S"); var s, rest; ({[S]: s, ...rest} = {[S]: true, test: 1}); assert.sameValue(s, true, "S"); assert(deepEqual(rest, {test: 1}), "rest"); ` testScriptWithTestLibX(SCRIPT, _undefined, t) } func TestAccessorFuncName(t *testing.T) { const SCRIPT = ` const namedSym = Symbol('test262'); const emptyStrSym = Symbol(""); const anonSym = Symbol(); const o = { get id() {}, get [anonSym]() {}, get [namedSym]() {}, get [emptyStrSym]() {}, set id(v) {}, set [anonSym](v) {}, set [namedSym](v) {}, set [emptyStrSym](v) {} }; let prop; prop = Object.getOwnPropertyDescriptor(o, 'id'); assert.sameValue(prop.get.name, 'get id'); assert.sameValue(prop.set.name, 'set id'); prop = Object.getOwnPropertyDescriptor(o, anonSym); assert.sameValue(prop.get.name, 'get '); assert.sameValue(prop.set.name, 'set '); prop = Object.getOwnPropertyDescriptor(o, emptyStrSym); assert.sameValue(prop.get.name, 'get []'); assert.sameValue(prop.set.name, 'set []'); prop = Object.getOwnPropertyDescriptor(o, namedSym); assert.sameValue(prop.get.name, 'get [test262]'); assert.sameValue(prop.set.name, 'set [test262]'); ` testScriptWithTestLib(SCRIPT, _undefined, t) } func TestCoverFuncName(t *testing.T) { const SCRIPT = ` var namedSym = Symbol(''); var anonSym = Symbol(); var o; o = { xId: (0, function() {}), id: (function() {}), id1: function x() {}, [anonSym]: (function() {}), [namedSym]: (function() {}) }; assert(o.xId.name !== 'xId'); assert.sameValue(o.id1.name, 'x'); assert.sameValue(o.id.name, 'id', 'via IdentifierName'); assert.sameValue(o[anonSym].name, '', 'via anonymous Symbol'); assert.sameValue(o[namedSym].name, '[]', 'via Symbol'); ` testScriptWithTestLib(SCRIPT, _undefined, t) } func TestAnonFuncName(t *testing.T) { const SCRIPT = ` const d = Object.getOwnPropertyDescriptor((function() {}), 'name'); d !== undefined && d.value === ''; ` testScript(SCRIPT, valueTrue, t) } func TestStringToBytesConversion(t *testing.T) { vm := New() v := vm.ToValue("Test") var b []byte err := vm.ExportTo(v, &b) if err != nil { t.Fatal(err) } if string(b) != "Test" { t.Fatal(b) } } func TestPromiseAll(t *testing.T) { const SCRIPT = ` var p1 = new Promise(function() {}); var p2 = new Promise(function() {}); var p3 = new Promise(function() {}); var callCount = 0; var currentThis = p1; var nextThis = p2; var afterNextThis = p3; p1.then = p2.then = p3.then = function(a, b) { assert.sameValue(typeof a, 'function', 'type of first argument'); assert.sameValue( a.length, 1, 'ES6 25.4.1.3.2: The length property of a promise resolve function is 1.' ); assert.sameValue(typeof b, 'function', 'type of second argument'); assert.sameValue( b.length, 1, 'ES6 25.4.1.3.1: The length property of a promise reject function is 1.' ); assert.sameValue(arguments.length, 2, '"then"" invoked with two arguments'); assert.sameValue(this, currentThis, '"this" value'); currentThis = nextThis; nextThis = afterNextThis; afterNextThis = null; callCount += 1; }; Promise.all([p1, p2, p3]); assert.sameValue(callCount, 3, '"then"" invoked once for every iterated value'); ` testScriptWithTestLib(SCRIPT, _undefined, t) } func TestPromiseExport(t *testing.T) { vm := New() p, _, _ := vm.NewPromise() pv := vm.ToValue(p) if actual := pv.ExportType(); actual != reflect.TypeOf((*Promise)(nil)) { t.Fatalf("Export type: %v", actual) } if ev := pv.Export(); ev != p { t.Fatalf("Export value: %v", ev) } } func TestErrorStack(t *testing.T) { const SCRIPT = ` const err = new Error("test"); if (!("stack" in err)) { throw new Error("in"); } if (Reflect.ownKeys(err)[0] !== "stack") { throw new Error("property order"); } const stack = err.stack; if (stack !== "Error: test\n\tat test.js:2:14(3)\n") { throw new Error(stack); } delete err.stack; if ("stack" in err) { throw new Error("stack still in err after delete"); } ` testScript(SCRIPT, _undefined, t) } func TestErrorFormatSymbols(t *testing.T) { vm := New() vm.Set("a", func() (Value, error) { return nil, errors.New("something %s %f") }) _, err := vm.RunString("a()") if !strings.Contains(err.Error(), "something %s %f") { t.Fatalf("Wrong value %q", err.Error()) } } func TestPanicPassthrough(t *testing.T) { const panicString = "Test panic" r := New() r.Set("f", func() { panic(panicString) }) defer func() { if x := recover(); x != nil { if x != panicString { t.Fatalf("Wrong panic value: %v", x) } if len(r.vm.callStack) > 0 { t.Fatal("vm.callStack is not empty") } } else { t.Fatal("No panic") } }() _, _ = r.RunString("f()") t.Fatal("Should not reach here") } func TestSuspendResumeRelStackLen(t *testing.T) { const SCRIPT = ` async function f2() { throw new Error("test"); } async function f1() { let a = [1]; for (let i of a) { try { await f2(); } catch { return true; } } } async function f() { let a = [1]; for (let i of a) { return await f1(); } } return f(); ` testAsyncFunc(SCRIPT, valueTrue, t) } func TestSuspendResumeStacks(t *testing.T) { const SCRIPT = ` async function f1() { throw new Error(); } async function f() { try { await f1(); } catch {} } result = await f(); ` testAsyncFunc(SCRIPT, _undefined, t) } func TestNestedTopLevelConstructorCall(t *testing.T) { r := New() c := func(call ConstructorCall, rt *Runtime) *Object { if _, err := rt.RunString("(5)"); err != nil { panic(err) } return nil } if err := r.Set("C", c); err != nil { panic(err) } if _, err := r.RunString("new C()"); err != nil { panic(err) } } func TestNestedTopLevelConstructorPanicAsync(t *testing.T) { r := New() c := func(call ConstructorCall, rt *Runtime) *Object { c, ok := AssertFunction(rt.ToValue(func() {})) if !ok { panic("wat") } if _, err := c(Undefined()); err != nil { panic(err) } return nil } if err := r.Set("C", c); err != nil { panic(err) } if _, err := r.RunString("new C()"); err != nil { panic(err) } } func TestAsyncFuncThrow(t *testing.T) { const SCRIPT = ` class TestError extends Error { } async function f() { throw new TestError(); } async function f1() { try { await f(); } catch (e) { assert.sameValue(e.constructor.name, TestError.name); return; } throw new Error("No exception was thrown"); } await f1(); return undefined; ` testAsyncFuncWithTestLib(SCRIPT, _undefined, t) } func TestAsyncStacktrace(t *testing.T) { // Do not reformat, assertions depend on the line and column numbers const SCRIPT = ` let ex; async function foo(x) { await bar(x); } async function bar(x) { await x; throw new Error("Let's have a look..."); } try { await foo(1); } catch (e) { assertStack(e, [ ["test.js", "bar", 9, 10], ["test.js", "foo", 4, 13], ["test.js", "test", 13, 12], ]); } ` testAsyncFuncWithTestLibX(SCRIPT, _undefined, t) } func TestPanicPropagation(t *testing.T) { r := New() r.Set("doPanic", func() { panic(true) }) v, err := r.RunString(`(function() { doPanic(); })`) if err != nil { t.Fatal(err) } f, ok := AssertFunction(v) if !ok { t.Fatal("not a function") } defer func() { if x := recover(); x != nil { if x != true { t.Fatal("Invalid panic value") } } }() _, _ = f(nil) t.Fatal("Expected panic") } func TestAwaitInParameters(t *testing.T) { _, err := Compile("", ` async function g() { async function inner(a = 1 + await 1) { } } `, false) if err == nil { t.Fatal("Expected error") } } func ExampleRuntime_ForOf() { r := New() v, err := r.RunString(` new Map().set("a", 1).set("b", 2); `) if err != nil { panic(err) } var sb strings.Builder ex := r.Try(func() { r.ForOf(v, func(v Value) bool { o := v.ToObject(r) key := o.Get("0") value := o.Get("1") sb.WriteString(key.String()) sb.WriteString("=") sb.WriteString(value.String()) sb.WriteString(",") return true }) }) if ex != nil { panic(ex) } fmt.Println(sb.String()) // Output: a=1,b=2, } func TestDestructAssignToSymbol(t *testing.T) { const SCRIPT = ` const s = Symbol('s'); const target = {}; ({a: target[s]} = {a: 42}); assert.sameValue(target[s], 42); ` testScriptWithTestLib(SCRIPT, _undefined, t) } func TestToNumber(t *testing.T) { const SCRIPT = ` assert(isNaN(Number("+"))); assert(isNaN(Number("++"))); assert(isNaN(Number("-"))); assert(isNaN(Number("0xfp1"))); assert(isNaN(Number("0Xfp1"))); assert(isNaN(Number("+0xfp1"))); assert(isNaN(Number(" +0xfp1"))); assert(isNaN(Number(" + 0xfp1"))); assert(isNaN(Number(" 0xfp1"))); assert(isNaN(Number("-0xfp1"))); assert(isNaN(Number("- 0xfp1"))); assert(isNaN(Number(" - 0xfp1"))); assert.sameValue(Number("0."), 0); assert.sameValue(Number(" "), 0); assert.sameValue(Number(" Infinity"), Infinity); let a = [1]; assert.sameValue(1, a.at("0xfp1")); assert.sameValue(1, a.at(" 0xfp1")); ` testScriptWithTestLib(SCRIPT, _undefined, t) } /* func TestArrayConcatSparse(t *testing.T) { function foo(a,b,c) { arguments[0] = 1; arguments[1] = 'str'; arguments[2] = 2.1; if(1 === a && 'str' === b && 2.1 === c) return true; } const SCRIPT = ` var a1 = []; var a2 = []; a1[500000] = 1; a2[1000000] = 2; var a3 = a1.concat(a2); a3.length === 1500002 && a3[500000] === 1 && a3[1500001] == 2; ` testScript(SCRIPT, valueTrue, t) } */ func BenchmarkCallReflect(b *testing.B) { vm := New() vm.Set("f", func(v Value) { }) prg := MustCompile("test.js", "f(null)", true) b.ResetTimer() for i := 0; i < b.N; i++ { vm.RunProgram(prg) } } func BenchmarkCallNative(b *testing.B) { vm := New() vm.Set("f", func(call FunctionCall) (ret Value) { return }) prg := MustCompile("test.js", "f(null)", true) b.ResetTimer() for i := 0; i < b.N; i++ { vm.RunProgram(prg) } } func BenchmarkCallJS(b *testing.B) { vm := New() _, err := vm.RunString(` function f() { return 42; } `) if err != nil { b.Fatal(err) } prg := MustCompile("test.js", "f(null)", true) b.ResetTimer() for i := 0; i < b.N; i++ { vm.RunProgram(prg) } } func BenchmarkMainLoop(b *testing.B) { vm := New() const SCRIPT = ` for (var i=0; i<100000; i++) { } ` prg := MustCompile("test.js", SCRIPT, true) b.ResetTimer() for i := 0; i < b.N; i++ { vm.RunProgram(prg) } } func BenchmarkStringMapGet(b *testing.B) { m := make(map[string]Value) for i := 0; i < 100; i++ { m[strconv.Itoa(i)] = intToValue(int64(i)) } b.ResetTimer() for i := 0; i < b.N; i++ { if m["50"] == nil { b.Fatal() } } } func BenchmarkValueStringMapGet(b *testing.B) { m := make(map[String]Value) for i := 0; i < 100; i++ { m[asciiString(strconv.Itoa(i))] = intToValue(int64(i)) } b.ResetTimer() var key String = asciiString("50") for i := 0; i < b.N; i++ { if m[key] == nil { b.Fatal() } } } func BenchmarkAsciiStringMapGet(b *testing.B) { m := make(map[asciiString]Value) for i := 0; i < 100; i++ { m[asciiString(strconv.Itoa(i))] = intToValue(int64(i)) } b.ResetTimer() var key = asciiString("50") for i := 0; i < b.N; i++ { if m[key] == nil { b.Fatal() } } } func BenchmarkNew(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { New() } }