更新goja版本,修复bug

This commit is contained in:
Star 2025-11-30 21:28:09 +08:00
parent 8e728e2ed4
commit db8742de7e
116 changed files with 35792 additions and 500 deletions

21
args.go
View File

@ -2,7 +2,6 @@ package gojs
import (
"errors"
"path/filepath"
"apigo.cc/gojs/goja"
"github.com/ssgo/log"
@ -38,22 +37,6 @@ func GetLogger(vm *goja.Runtime) *log.Logger {
return logger
}
func FindPath(vm *goja.Runtime, filename string) string {
if u.FileExists(filename) {
return filename
}
if !filepath.IsAbs(filename) {
startPath := u.String(vm.GetData("startPath"))
if startPath != "" {
tryFilename := filepath.Join(startPath, filename)
if u.FileExists(tryFilename) {
return tryFilename
}
}
}
return filename
}
func MakeArgs(args *goja.FunctionCall, vm *goja.Runtime) *Arr {
return &Arr{
This: args.This,
@ -176,7 +159,7 @@ func (args *Arr) Map2Array(index int) []any {
}
func (args *Arr) Path(index int) string {
return FindPath(args.vm, args.Str(index))
return FixPath(args.vm, args.Str(index))
}
func GetFunc(v goja.Value) goja.Callable {
@ -296,7 +279,7 @@ func (obj *Obj) Map(name string) map[string]any {
}
func (obj *Obj) Path(name string) string {
return FindPath(obj.vm, obj.Str(name))
return FixPath(obj.vm, obj.Str(name))
}
func (obj *Obj) Func(name string) goja.Callable {

View File

@ -101,6 +101,7 @@ func go2js(in any, n int, vm *goja.Runtime) any {
return in
}
// 将go的函数转换为js的函数
t := v.Type()
return func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
needArgs := make([]reflect.Type, 0)
@ -108,6 +109,7 @@ func go2js(in any, n int, vm *goja.Runtime) any {
needArgsIndex := map[int]int{}
hasLastVariadicArg := false
// fmt.Println(t.NumIn(), t.String(), u.BYellow(u.JsonP(argsIn.Arguments)))
for i := 0; i < t.NumIn(); i++ {
inTypeString := t.In(i).String()
@ -153,6 +155,7 @@ func go2js(in any, n int, vm *goja.Runtime) any {
}
funcType := realNeedArgType
// 传入的参数是函数自动转换为go函数
argValue = MakeFunc(vm, funcType, jsFunc, argsIn.This)
// argValue = reflect.MakeFunc(funcType, func(goArgs []reflect.Value) []reflect.Value {
// ins := make([]goja.Value, 0)
@ -236,7 +239,8 @@ func go2js(in any, n int, vm *goja.Runtime) any {
jsValue := argsIn.Arguments[i]
anyV := jsValue.Export()
recovered := false
if jsValue.ExportType().Kind() == reflect.Map {
et := jsValue.ExportType()
if et != nil && et.Kind() == reflect.Map {
mapV := reflect.ValueOf(anyV)
// 尝试使用传递的objId直接恢复对象
if mapV.IsValid() && !mapV.IsNil() {
@ -351,7 +355,8 @@ func go2js(in any, n int, vm *goja.Runtime) any {
}
outs = append(outs, mapMap)
} else {
outs = append(outs, outValue.Interface())
// 其他返回内容转换JS主要是Func自动转换
outs = append(outs, ToJS(outValue.Interface()))
}
}
if len(outs) == 1 {

11
go.mod
View File

@ -3,20 +3,25 @@ module apigo.cc/gojs
go 1.24.0
require (
github.com/Masterminds/semver/v3 v3.2.1
github.com/dlclark/regexp2 v1.11.5
github.com/dop251/base64dec v0.0.0-20231022112746-c6c9f9a96217
github.com/go-sourcemap/sourcemap v2.1.4+incompatible
github.com/google/pprof v0.0.0-20250903194437-c28834ac2320
github.com/ssgo/log v1.7.9
github.com/ssgo/tool v0.4.29
github.com/ssgo/u v1.7.23
golang.org/x/net v0.44.0
golang.org/x/text v0.29.0
go.uber.org/goleak v1.3.0
golang.org/x/net v0.47.0
golang.org/x/text v0.31.0
gopkg.in/yaml.v2 v2.4.0
)
require (
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/ssgo/config v1.7.10 // indirect
github.com/ssgo/standard v1.7.7 // indirect
golang.org/x/sys v0.36.0 // indirect
golang.org/x/sys v0.38.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

15
goja/LICENSE Normal file
View File

@ -0,0 +1,15 @@
Copyright (c) 2016 Dmitry Panov
Copyright (c) 2012 Robert Krimen
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

335
goja/README.md Normal file
View File

@ -0,0 +1,335 @@
goja
====
ECMAScript 5.1(+) implementation in Go.
[![Go Reference](https://pkg.go.dev/badge/apigo.cc/gojs/goja.svg)](https://pkg.go.dev/apigo.cc/gojs/goja)
Goja is an implementation of ECMAScript 5.1 in pure Go with emphasis on standard compliance and
performance.
This project was largely inspired by [otto](https://github.com/robertkrimen/otto).
The minimum required Go version is 1.20.
Features
--------
* Full ECMAScript 5.1 support (including regex and strict mode).
* Passes nearly all [tc39 tests](https://github.com/tc39/test262) for the features implemented so far. The goal is to
pass all of them. See .tc39_test262_checkout.sh for the latest working commit id.
* Capable of running Babel, Typescript compiler and pretty much anything written in ES5.
* Sourcemaps.
* Most of ES6 functionality, still work in progress, see https://apigo.cc/gojs/goja/milestone/1?closed=1
Known incompatibilities and caveats
-----------------------------------
### WeakMap
WeakMap is implemented by embedding references to the values into the keys. This means that as long
as the key is reachable all values associated with it in any weak maps also remain reachable and therefore
cannot be garbage collected even if they are not otherwise referenced, even after the WeakMap is gone.
The reference to the value is dropped either when the key is explicitly removed from the WeakMap or when the
key becomes unreachable.
To illustrate this:
```javascript
var m = new WeakMap();
var key = {};
var value = {/* a very large object */};
m.set(key, value);
value = undefined;
m = undefined; // The value does NOT become garbage-collectable at this point
key = undefined; // Now it does
// m.delete(key); // This would work too
```
The reason for it is the limitation of the Go runtime. At the time of writing (version 1.15) having a finalizer
set on an object which is part of a reference cycle makes the whole cycle non-garbage-collectable. The solution
above is the only reasonable way I can think of without involving finalizers. This is the third attempt
(see https://apigo.cc/gojs/goja/issues/250 and https://apigo.cc/gojs/goja/issues/199 for more details).
Note, this does not have any effect on the application logic, but may cause a higher-than-expected memory usage.
### WeakRef and FinalizationRegistry
For the reason mentioned above implementing WeakRef and FinalizationRegistry does not seem to be possible at this stage.
### JSON
`JSON.parse()` uses the standard Go library which operates in UTF-8. Therefore, it cannot correctly parse broken UTF-16
surrogate pairs, for example:
```javascript
JSON.parse(`"\\uD800"`).charCodeAt(0).toString(16) // returns "fffd" instead of "d800"
```
### Date
Conversion from calendar date to epoch timestamp uses the standard Go library which uses `int`, rather than `float` as per
ECMAScript specification. This means if you pass arguments that overflow int to the `Date()` constructor or if there is
an integer overflow, the result will be incorrect, for example:
```javascript
Date.UTC(1970, 0, 1, 80063993375, 29, 1, -288230376151711740) // returns 29256 instead of 29312
```
FAQ
---
### How fast is it?
Although it's faster than many scripting language implementations in Go I have seen
(for example it's 6-7 times faster than otto on average) it is not a
replacement for V8 or SpiderMonkey or any other general-purpose JavaScript engine.
You can find some benchmarks [here](https://apigo.cc/gojs/goja/issues/2).
### Why would I want to use it over a V8 wrapper?
It greatly depends on your usage scenario. If most of the work is done in javascript
(for example crypto or any other heavy calculations) you are definitely better off with V8.
If you need a scripting language that drives an engine written in Go so that
you need to make frequent calls between Go and javascript passing complex data structures
then the cgo overhead may outweigh the benefits of having a faster javascript engine.
Because it's written in pure Go there are no cgo dependencies, it's very easy to build and it
should run on any platform supported by Go.
It gives you a much better control over execution environment so can be useful for research.
### Is it goroutine-safe?
No. An instance of goja.Runtime can only be used by a single goroutine
at a time. You can create as many instances of Runtime as you like but
it's not possible to pass object values between runtimes.
### Where is setTimeout()/setInterval()?
setTimeout() and setInterval() are common functions to provide concurrent execution in ECMAScript environments, but the two functions are not part of the ECMAScript standard.
Browsers and NodeJS just happen to provide similar, but not identical, functions. The hosting application need to control the environment for concurrent execution, e.g. an event loop, and supply the functionality to script code.
There is a [separate project](https://apigo.cc/gojs/goja_nodejs) aimed at providing some NodeJS functionality,
and it includes an event loop.
### Can you implement (feature X from ES6 or higher)?
I will be adding features in their dependency order and as quickly as time permits. Please do not ask
for ETAs. Features that are open in the [milestone](https://apigo.cc/gojs/goja/milestone/1) are either in progress
or will be worked on next.
The ongoing work is done in separate feature branches which are merged into master when appropriate.
Every commit in these branches represents a relatively stable state (i.e. it compiles and passes all enabled tc39 tests),
however because the version of tc39 tests I use is quite old, it may be not as well tested as the ES5.1 functionality. Because there are (usually) no major breaking changes between ECMAScript revisions
it should not break your existing code. You are encouraged to give it a try and report any bugs found. Please do not submit fixes though without discussing it first, as the code could be changed in the meantime.
### How do I contribute?
Before submitting a pull request please make sure that:
- You followed ECMA standard as close as possible. If adding a new feature make sure you've read the specification,
do not just base it on a couple of examples that work fine.
- Your change does not have a significant negative impact on performance (unless it's a bugfix and it's unavoidable)
- It passes all relevant tc39 tests.
Current Status
--------------
* There should be no breaking changes in the API, however it may be extended.
* Some of the AnnexB functionality is missing.
Basic Example
-------------
Run JavaScript and get the result value.
```go
vm := goja.New()
v, err := vm.RunString("2 + 2")
if err != nil {
panic(err)
}
if num := v.Export().(int64); num != 4 {
panic(num)
}
```
Passing Values to JS
--------------------
Any Go value can be passed to JS using Runtime.ToValue() method. See the method's [documentation](https://pkg.go.dev/apigo.cc/gojs/goja#Runtime.ToValue) for more details.
Exporting Values from JS
------------------------
A JS value can be exported into its default Go representation using Value.Export() method.
Alternatively it can be exported into a specific Go variable using [Runtime.ExportTo()](https://pkg.go.dev/apigo.cc/gojs/goja#Runtime.ExportTo) method.
Within a single export operation the same Object will be represented by the same Go value (either the same map, slice or
a pointer to the same struct). This includes circular objects and makes it possible to export them.
Calling JS functions from Go
----------------------------
There are 2 approaches:
- Using [AssertFunction()](https://pkg.go.dev/apigo.cc/gojs/goja#AssertFunction):
```go
const SCRIPT = `
function sum(a, b) {
return +a + b;
}
`
vm := goja.New()
_, err := vm.RunString(SCRIPT)
if err != nil {
panic(err)
}
sum, ok := goja.AssertFunction(vm.Get("sum"))
if !ok {
panic("Not a function")
}
res, err := sum(goja.Undefined(), vm.ToValue(40), vm.ToValue(2))
if err != nil {
panic(err)
}
fmt.Println(res)
// Output: 42
```
- Using [Runtime.ExportTo()](https://pkg.go.dev/apigo.cc/gojs/goja#Runtime.ExportTo):
```go
const SCRIPT = `
function sum(a, b) {
return +a + b;
}
`
vm := goja.New()
_, err := vm.RunString(SCRIPT)
if err != nil {
panic(err)
}
var sum func(int, int) int
err = vm.ExportTo(vm.Get("sum"), &sum)
if err != nil {
panic(err)
}
fmt.Println(sum(40, 2)) // note, _this_ value in the function will be undefined.
// Output: 42
```
The first one is more low level and allows specifying _this_ value, whereas the second one makes the function look like
a normal Go function.
Mapping struct field and method names
-------------------------------------
By default, the names are passed through as is which means they are capitalised. This does not match
the standard JavaScript naming convention, so if you need to make your JS code look more natural or if you are
dealing with a 3rd party library, you can use a [FieldNameMapper](https://pkg.go.dev/apigo.cc/gojs/goja#FieldNameMapper):
```go
vm := goja.New()
vm.SetFieldNameMapper(TagFieldNameMapper("json", true))
type S struct {
Field int `json:"field"`
}
vm.Set("s", S{Field: 42})
res, _ := vm.RunString(`s.field`) // without the mapper it would have been s.Field
fmt.Println(res.Export())
// Output: 42
```
There are two standard mappers: [TagFieldNameMapper](https://pkg.go.dev/apigo.cc/gojs/goja#TagFieldNameMapper) and
[UncapFieldNameMapper](https://pkg.go.dev/apigo.cc/gojs/goja#UncapFieldNameMapper), or you can use your own implementation.
Native Constructors
-------------------
In order to implement a constructor function in Go use `func (goja.ConstructorCall) *goja.Object`.
See [Runtime.ToValue()](https://pkg.go.dev/apigo.cc/gojs/goja#Runtime.ToValue) documentation for more details.
Regular Expressions
-------------------
Goja uses the embedded Go regexp library where possible, otherwise it falls back to [regexp2](https://github.com/dlclark/regexp2).
Exceptions
----------
Any exception thrown in JavaScript is returned as an error of type *Exception. It is possible to extract the value thrown
by using the Value() method:
```go
vm := goja.New()
_, err := vm.RunString(`
throw("Test");
`)
if jserr, ok := err.(*Exception); ok {
if jserr.Value().Export() != "Test" {
panic("wrong value")
}
} else {
panic("wrong type")
}
```
If a native Go function panics with a Value, it is thrown as a Javascript exception (and therefore can be caught):
```go
var vm *Runtime
func Test() {
panic(vm.ToValue("Error"))
}
vm = goja.New()
vm.Set("Test", Test)
_, err := vm.RunString(`
try {
Test();
} catch(e) {
if (e !== "Error") {
throw e;
}
}
`)
if err != nil {
panic(err)
}
```
Interrupting
------------
```go
func TestInterrupt(t *testing.T) {
const SCRIPT = `
var i = 0;
for (;;) {
i++;
}
`
vm := goja.New()
time.AfterFunc(200 * time.Millisecond, func() {
vm.Interrupt("halt")
})
_, err := vm.RunString(SCRIPT)
if err == nil {
t.Fatal("Err is nil")
}
// err is of type *InterruptError and its Value() method returns whatever has been passed to vm.Interrupt()
}
```
NodeJS Compatibility
--------------------
There is a [separate project](https://apigo.cc/gojs/goja_nodejs) aimed at providing some of the NodeJS functionality.

264
goja/array_sparse_test.go Normal file
View File

@ -0,0 +1,264 @@
package goja
import (
"testing"
)
func TestSparseArraySetLengthWithPropItems(t *testing.T) {
const SCRIPT = `
var a = [1,2,3,4];
a[100000] = 5;
var thrown = false;
Object.defineProperty(a, "2", {value: 42, configurable: false, writable: false});
try {
Object.defineProperty(a, "length", {value: 0, writable: false});
} catch (e) {
thrown = e instanceof TypeError;
}
thrown && a.length === 3;
`
testScript(SCRIPT, valueTrue, t)
}
func TestSparseArraySwitch(t *testing.T) {
vm := New()
_, err := vm.RunString(`
var a = [];
a[20470] = 5; // switch to sparse`)
if err != nil {
t.Fatal(err)
}
a := vm.Get("a").(*Object)
if _, ok := a.self.(*sparseArrayObject); !ok {
t.Fatal("1: array is not sparse")
}
_, err = vm.RunString(`
var cutoffIdx = Math.round(20470 - 20470/8);
for (var i = a.length - 1; i >= cutoffIdx; i--) {
a[i] = i;
}
// At this point it will have switched to a normal array
if (a.length != 20471) {
throw new Error("Invalid length: " + a.length);
}
for (var i = 0; i < cutoffIdx; i++) {
if (a[i] !== undefined) {
throw new Error("Invalid value at " + i + ": " + a[i]);
}
}
for (var i = cutoffIdx; i < a.length; i++) {
if (a[i] !== i) {
throw new Error("Invalid value at " + i + ": " + a[i]);
}
}`)
if err != nil {
t.Fatal(err)
}
if _, ok := a.self.(*arrayObject); !ok {
t.Fatal("2: array is not normal")
}
_, err = vm.RunString(`
// Now try to expand. Should stay a normal array
a[20471] = 20471;
if (a.length != 20472) {
throw new Error("Invalid length: " + a.length);
}
for (var i = 0; i < cutoffIdx; i++) {
if (a[i] !== undefined) {
throw new Error("Invalid value at " + i + ": " + a[i]);
}
}
for (var i = cutoffIdx; i < a.length; i++) {
if (a[i] !== i) {
throw new Error("Invalid value at " + i + ": " + a[i]);
}
}`)
if err != nil {
t.Fatal(err)
}
if _, ok := a.self.(*arrayObject); !ok {
t.Fatal("3: array is not normal")
}
_, err = vm.RunString(`
// Delete enough elements for it to become sparse again.
var cutoffIdx1 = Math.round(20472 - 20472/10);
for (var i = cutoffIdx; i < cutoffIdx1; i++) {
delete a[i];
}
// This should switch it back to sparse.
a[25590] = 25590;
if (a.length != 25591) {
throw new Error("Invalid length: " + a.length);
}
for (var i = 0; i < cutoffIdx1; i++) {
if (a[i] !== undefined) {
throw new Error("Invalid value at " + i + ": " + a[i]);
}
}
for (var i = cutoffIdx1; i < 20472; i++) {
if (a[i] !== i) {
throw new Error("Invalid value at " + i + ": " + a[i]);
}
}
for (var i = 20472; i < 25590; i++) {
if (a[i] !== undefined) {
throw new Error("Invalid value at " + i + ": " + a[i]);
}
}
if (a[25590] !== 25590) {
throw new Error("Invalid value at 25590: " + a[25590]);
}
`)
if err != nil {
t.Fatal(err)
}
if _, ok := a.self.(*sparseArrayObject); !ok {
t.Fatal("4: array is not sparse")
}
}
func TestSparseArrayOwnKeys(t *testing.T) {
const SCRIPT = `
var a1 = [];
a1[500000] = 1;
var seen = false;
var count = 0;
var keys = Object.keys(a1);
keys.length === 1 && keys[0] === "500000";
`
testScript(SCRIPT, valueTrue, t)
}
func TestSparseArrayEnumerate(t *testing.T) {
const SCRIPT = `
var a1 = [];
a1[500000] = 1;
var seen = false;
var count = 0;
for (var i in a1) {
if (i === "500000") {
if (seen) {
throw new Error("seen twice");
}
seen = true;
}
count++;
}
seen && count === 1;
`
testScript(SCRIPT, valueTrue, t)
}
func TestArraySparseMaxLength(t *testing.T) {
const SCRIPT = `
var a = [];
a[4294967294]=1;
a.length === 4294967295 && a[4294967294] === 1;
`
testScript(SCRIPT, valueTrue, t)
}
func TestArraySparseExportProps(t *testing.T) {
vm := New()
proto := vm.NewArray()
for _, idx := range []string{"0", "500", "9999", "10001", "20471"} {
err := proto.Set(idx, true)
if err != nil {
t.Fatal(err)
}
}
arr := vm.NewArray()
err := arr.SetPrototype(proto)
if err != nil {
t.Fatal(err)
}
err = arr.DefineDataProperty("20470", vm.ToValue(true), FLAG_TRUE, FLAG_FALSE, FLAG_TRUE)
if err != nil {
t.Fatal(err)
}
err = arr.DefineDataProperty("10000", vm.ToValue(true), FLAG_TRUE, FLAG_FALSE, FLAG_TRUE)
if err != nil {
t.Fatal(err)
}
err = arr.Set("length", 20472)
if err != nil {
t.Fatal(err)
}
actual := arr.Export()
if actualArr, ok := actual.([]interface{}); ok {
if len(actualArr) == 20472 {
expectedIdx := map[int]struct{}{
0: {},
500: {},
9999: {},
10000: {},
10001: {},
20470: {},
20471: {},
}
for i, v := range actualArr {
if _, exists := expectedIdx[i]; exists {
if v != true {
t.Fatalf("Expected true at %d, got %v", i, v)
}
} else {
if v != nil {
t.Fatalf("Expected nil at %d, got %v", i, v)
}
}
}
} else {
t.Fatalf("Expected len 20471, actual: %d", len(actualArr))
}
} else {
t.Fatalf("Invalid export type: %T", actual)
}
}
func TestSparseArrayExportToSlice(t *testing.T) {
vm := New()
arr := vm.NewArray()
err := arr.Set("20470", 120470)
if err != nil {
t.Fatal(err)
}
err = arr.DefineDataProperty("20471", vm.ToValue(220471), FLAG_TRUE, FLAG_FALSE, FLAG_TRUE)
if err != nil {
t.Fatal(err)
}
var exp []int
err = vm.ExportTo(arr, &exp)
if err != nil {
t.Fatal(err)
}
if len(exp) != 20472 {
t.Fatalf("len: %d", len(exp))
}
if e := exp[20470]; e != 120470 {
t.Fatalf("20470: %d", e)
}
if e := exp[20471]; e != 220471 {
t.Fatalf("20471: %d", e)
}
for i := 0; i < 20470; i++ {
if exp[i] != 0 {
t.Fatalf("at %d: %d", i, exp[i])
}
}
}

133
goja/array_test.go Normal file
View File

@ -0,0 +1,133 @@
package goja
import (
"reflect"
"testing"
)
func TestArray1(t *testing.T) {
r := &Runtime{}
a := r.newArray(nil)
a.setOwnIdx(valueInt(0), asciiString("test"), true)
if l := a.getStr("length", nil).ToInteger(); l != 1 {
t.Fatalf("Unexpected length: %d", l)
}
}
func TestArrayExportProps(t *testing.T) {
vm := New()
arr := vm.NewArray()
err := arr.DefineDataProperty("0", vm.ToValue(true), FLAG_TRUE, FLAG_FALSE, FLAG_TRUE)
if err != nil {
t.Fatal(err)
}
actual := arr.Export()
expected := []interface{}{true}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("Expected: %#v, actual: %#v", expected, actual)
}
}
func TestArrayCanonicalIndex(t *testing.T) {
const SCRIPT = `
var a = [];
a["00"] = 1;
a["01"] = 2;
if (a[0] !== undefined) {
throw new Error("a[0]");
}
`
testScript(SCRIPT, _undefined, t)
}
func BenchmarkArrayGetStr(b *testing.B) {
b.StopTimer()
r := New()
v := &Object{runtime: r}
a := &arrayObject{
baseObject: baseObject{
val: v,
extensible: true,
},
}
v.self = a
a.init()
v.setOwn(valueInt(0), asciiString("test"), false)
b.StartTimer()
for i := 0; i < b.N; i++ {
a.getStr("0", nil)
}
}
func BenchmarkArrayGet(b *testing.B) {
b.StopTimer()
r := New()
v := &Object{runtime: r}
a := &arrayObject{
baseObject: baseObject{
val: v,
extensible: true,
},
}
v.self = a
a.init()
var idx Value = valueInt(0)
v.setOwn(idx, asciiString("test"), false)
b.StartTimer()
for i := 0; i < b.N; i++ {
v.get(idx, nil)
}
}
func BenchmarkArrayPut(b *testing.B) {
b.StopTimer()
r := New()
v := &Object{runtime: r}
a := &arrayObject{
baseObject: baseObject{
val: v,
extensible: true,
},
}
v.self = a
a.init()
var idx Value = valueInt(0)
var val Value = asciiString("test")
b.StartTimer()
for i := 0; i < b.N; i++ {
v.setOwn(idx, val, false)
}
}
func BenchmarkArraySetEmpty(b *testing.B) {
r := New()
_ = r.Get("Array").(*Object).Get("prototype").String() // materialise Array.prototype
a := r.NewArray(0, 0)
values := a.self.(*arrayObject).values
b.ResetTimer()
for i := 0; i < b.N; i++ {
values[0] = nil
a.self.setOwnIdx(0, valueTrue, true)
}
}

1068
goja/ast/README.markdown Normal file

File diff suppressed because it is too large Load Diff

367
goja/builtin_arrray_test.go Normal file
View File

@ -0,0 +1,367 @@
package goja
import (
"testing"
)
func TestArrayProtoProp(t *testing.T) {
const SCRIPT = `
Object.defineProperty(Array.prototype, '0', {value: 42, configurable: true, writable: false})
var a = []
a[0] = 1
a[0]
`
testScript(SCRIPT, valueInt(42), t)
}
func TestArrayDelete(t *testing.T) {
const SCRIPT = `
var a = [1, 2];
var deleted = delete a[0];
var undef = a[0] === undefined;
var len = a.length;
deleted && undef && len === 2;
`
testScript(SCRIPT, valueTrue, t)
}
func TestArrayDeleteNonexisting(t *testing.T) {
const SCRIPT = `
Array.prototype[0] = 42;
var a = [];
delete a[0] && a[0] === 42;
`
testScript(SCRIPT, valueTrue, t)
}
func TestArraySetLength(t *testing.T) {
const SCRIPT = `
var a = [1, 2];
var assert0 = a.length == 2;
a.length = "1";
a.length = 1.0;
a.length = 1;
var assert1 = a.length == 1;
a.length = 2;
var assert2 = a.length == 2;
assert0 && assert1 && assert2 && a[1] === undefined;
`
testScript(SCRIPT, valueTrue, t)
}
func TestArrayReverseNonOptimisable(t *testing.T) {
const SCRIPT = `
var a = [];
Object.defineProperty(a, "0", {get: function() {return 42}, set: function(v) {Object.defineProperty(a, "0", {value: v + 1, writable: true, configurable: true})}, configurable: true})
a[1] = 43;
a.reverse();
a.length === 2 && a[0] === 44 && a[1] === 42;
`
testScript(SCRIPT, valueTrue, t)
}
func TestArrayPushNonOptimisable(t *testing.T) {
const SCRIPT = `
Object.defineProperty(Object.prototype, "0", {value: 42});
var a = [];
var thrown = false;
try {
a.push(1);
} catch (e) {
thrown = e instanceof TypeError;
}
thrown;
`
testScript(SCRIPT, valueTrue, t)
}
func TestArraySetLengthWithPropItems(t *testing.T) {
const SCRIPT = `
var a = [1,2,3,4];
var thrown = false;
Object.defineProperty(a, "2", {value: 42, configurable: false, writable: false});
try {
Object.defineProperty(a, "length", {value: 0, writable: false});
} catch (e) {
thrown = e instanceof TypeError;
}
thrown && a.length === 3;
`
testScript(SCRIPT, valueTrue, t)
}
func TestArrayFrom(t *testing.T) {
const SCRIPT = `
function checkDestHoles(dest, prefix) {
assert(dest !== source, prefix + ": dest !== source");
assert.sameValue(dest.length, 3, prefix + ": dest.length");
assert.sameValue(dest[0], 1, prefix + ": [0]");
assert.sameValue(dest[1], undefined, prefix + ": [1]");
assert(dest.hasOwnProperty("1"), prefix + ': hasOwnProperty("1")');
assert.sameValue(dest[2], 3, prefix + ": [2]");
}
function checkDest(dest, prefix) {
assert(dest !== source, prefix + ": dest !== source");
assert.sameValue(dest.length, 3, prefix + ": dest.length");
assert.sameValue(dest[0], 1, prefix + ": [0]");
assert.sameValue(dest[1], 2, prefix + ": [1]");
assert.sameValue(dest[2], 3, prefix + ": [2]");
}
var source = [1,2,3];
var srcHoles = [1,,3];
checkDest(Array.from(source), "std source/std dest");
checkDestHoles(Array.from(srcHoles), "std source (holes)/std dest");
function Iter() {
this.idx = 0;
}
Iter.prototype.next = function() {
if (this.idx < source.length) {
return {value: source[this.idx++]};
} else {
return {done: true};
}
}
var src = {};
src[Symbol.iterator] = function() {
return new Iter();
}
checkDest(Array.from(src), "iter src/std dest");
src = {0: 1, 2: 3, length: 3};
checkDestHoles(Array.from(src), "arrayLike src/std dest");
function A() {}
A.from = Array.from;
checkDest(A.from(source), "std src/cust dest");
checkDestHoles(A.from(srcHoles), "std src (holes)/cust dest");
checkDestHoles(A.from(src), "arrayLike src/cust dest");
function T2() {
Object.defineProperty(this, 0, {
configurable: false,
writable: true,
enumerable: true
});
}
assert.throws(TypeError, function() {
Array.from.call(T2, source);
});
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestArrayOf(t *testing.T) {
const SCRIPT = `
function T1() {
Object.preventExtensions(this);
}
assert.throws(TypeError, function() {
Array.of.call(T1, 'Bob');
});
function T2() {
Object.defineProperty(this, 0, {
configurable: false,
writable: true,
enumerable: true
});
}
assert.throws(TypeError, function() {
Array.of.call(T2, 'Bob');
})
result = Array.of.call(undefined);
assert(
result instanceof Array,
'this is not a constructor'
);
result = Array.of.call(Math.cos);
assert(
result instanceof Array,
'this is a builtin function with no [[Construct]] slot'
);
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestUnscopables(t *testing.T) {
const SCRIPT = `
var keys = [];
var _length;
with (Array.prototype) {
_length = length;
keys.push('something');
}
_length === 0 && keys.length === 1 && keys[0] === "something";
`
testScript(SCRIPT, valueTrue, t)
}
func TestArraySort(t *testing.T) {
const SCRIPT = `
assert.throws(TypeError, function() {
[1,2].sort(null);
}, "null compare function");
assert.throws(TypeError, function() {
[1,2].sort({});
}, "non-callable compare function");
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestArraySortNonStdArray(t *testing.T) {
const SCRIPT = `
const array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
Object.defineProperty(array, '2', {
get() {
array.pop();
array.pop();
return this.foo;
},
set(v) {
this.foo = v;
}
});
array.sort();
assert.sameValue(array[0], 'b');
assert.sameValue(array[1], 'c');
assert.sameValue(array[3], undefined);
assert.sameValue(array[4], undefined);
assert.sameValue('5' in array, false);
assert.sameValue(array.hasOwnProperty('5'), false);
assert.sameValue(array.length, 6);
assert.sameValue(array.foo, undefined);
assert.sameValue(array[2], undefined);
assert.sameValue(array.length, 4);
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestArrayConcat(t *testing.T) {
const SCRIPT = `
var concat = Array.prototype.concat;
var array = [1, 2];
var sparseArray = [1, , 2];
var nonSpreadableArray = [1, 2];
nonSpreadableArray[Symbol.isConcatSpreadable] = false;
var arrayLike = { 0: 1, 1: 2, length: 2 };
var spreadableArrayLike = { 0: 1, 1: 2, length: 2 };
spreadableArrayLike[Symbol.isConcatSpreadable] = true;
assert(looksNative(concat));
assert(deepEqual(array.concat(), [1, 2]), '#1');
assert(deepEqual(sparseArray.concat(), [1, , 2]), '#2');
assert(deepEqual(nonSpreadableArray.concat(), [[1, 2]]), '#3');
assert(deepEqual(concat.call(arrayLike), [{ 0: 1, 1: 2, length: 2 }]), '#4');
assert(deepEqual(concat.call(spreadableArrayLike), [1, 2]), '#5');
assert(deepEqual([].concat(array), [1, 2]), '#6');
assert(deepEqual([].concat(sparseArray), [1, , 2]), '#7');
assert(deepEqual([].concat(nonSpreadableArray), [[1, 2]]), '#8');
assert(deepEqual([].concat(arrayLike), [{ 0: 1, 1: 2, length: 2 }]), '#9');
assert(deepEqual([].concat(spreadableArrayLike), [1, 2]), '#10');
assert(deepEqual(array.concat(sparseArray, nonSpreadableArray, arrayLike, spreadableArrayLike), [
1, 2, 1, , 2, [1, 2], { 0: 1, 1: 2, length: 2 }, 1, 2,
]), '#11');
array = [];
array.constructor = {};
array.constructor[Symbol.species] = function () {
return { foo: 1 };
}
assert.sameValue(array.concat().foo, 1, '@@species');
`
testScriptWithTestLibX(SCRIPT, _undefined, t)
}
func TestArrayFlat(t *testing.T) {
const SCRIPT = `
var array = [1, [2,3,[4,5,6]], [[[[7,8,9]]]]];
assert(deepEqual(array.flat(), [1,2,3,[4,5,6],[[[7,8,9]]]]), '#1');
assert(deepEqual(array.flat(1), [1,2,3,[4,5,6],[[[7,8,9]]]]), '#2');
assert(deepEqual(array.flat(3), [1,2,3,4,5,6,[7,8,9]]), '#3');
assert(deepEqual(array.flat(4), [1,2,3,4,5,6,7,8,9]), '#4');
assert(deepEqual(array.flat(10), [1,2,3,4,5,6,7,8,9]), '#5');
`
testScriptWithTestLibX(SCRIPT, _undefined, t)
}
func TestArrayFlatMap(t *testing.T) {
const SCRIPT = `
var double = function(x) {
if (isNaN(x)) {
return x
}
return x * 2
}
var array = [1, [2,3,[4,5,6]], [[[[7,8,9]]]]];
assert(deepEqual(array.flatMap(double), [2,2,3,[4,5,6],[[[7,8,9]]]]), '#1');
`
testScriptWithTestLibX(SCRIPT, _undefined, t)
}
func TestArrayProto(t *testing.T) {
const SCRIPT = `
const a = Array.prototype;
a.push(1, 2, 3, 4, 5);
assert.sameValue(a.length, 5);
assert.sameValue(a[0], 1);
a.length = 3;
assert.sameValue(a.length, 3);
assert(compareArray(a, [1, 2, 3]));
a.shift();
assert.sameValue(a.length, 2);
assert(compareArray(a, [2, 3]));
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestArrayToSpliced(t *testing.T) {
const SCRIPT = `
const a = [1, 2, 3];
a.push(4)
assert(compareArray(a, [1, 2, 3, 4]));
const b = a.toSpliced(2)
assert(compareArray(a, [1, 2, 3, 4]));
assert(compareArray(b, [1, 2]));
a.push(5)
const c = a.toSpliced(1, 2);
assert(compareArray(a, [1, 2, 3, 4, 5]));
assert(compareArray(c, [1, 4, 5]));
assert(compareArray(a.toSpliced(4, 2, 'a', 'b'), [1, 2, 3, 4, 'a', 'b']));
assert(compareArray(a, [1, 2, 3, 4, 5]));
assert(compareArray(a.toSpliced(-2, 2), [1, 2, 3]));
assert(compareArray(a, [1, 2, 3, 4, 5]));
assert(compareArray(a.toSpliced(2, 10), [1, 2]));
assert(compareArray(a, [1, 2, 3, 4, 5]));
assert(compareArray(a.toSpliced(1, 0, 'a'), [1, 'a', 2, 3, 4, 5]));
assert(compareArray(a, [1, 2, 3, 4, 5]));
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}

117
goja/builtin_bigint_test.go Normal file
View File

@ -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)
}

View File

@ -90,8 +90,10 @@ func (r *Runtime) createListFromArrayLike(a Value) []Value {
func (r *Runtime) functionproto_apply(call FunctionCall) Value {
var args []Value
if len(call.Arguments) >= 2 {
args = r.createListFromArrayLike(call.Arguments[1])
argArray := call.Argument(1)
if !IsUndefined(argArray) && !IsNull(argArray) {
args = r.createListFromArrayLike(argArray)
}
f := r.toCallable(call.This)

View File

@ -0,0 +1,21 @@
package goja
import (
"testing"
)
func TestHashbangInFunctionConstructor(t *testing.T) {
const SCRIPT = `
assert.throws(SyntaxError, function() {
new Function("#!")
});
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestFunctionApplyNullArgArray(t *testing.T) {
const SCRIPT = `
assert.sameValue(0, (function() {return arguments.length}).apply(undefined, null))
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}

View File

@ -0,0 +1,21 @@
package goja
import (
"testing"
)
func TestEncodeURI(t *testing.T) {
const SCRIPT = `
encodeURI('тест')
`
testScript(SCRIPT, asciiString("%D1%82%D0%B5%D1%81%D1%82"), t)
}
func TestDecodeURI(t *testing.T) {
const SCRIPT = `
decodeURI("http://ru.wikipedia.org/wiki/%d0%ae%D0%bd%D0%B8%D0%BA%D0%BE%D0%B4")
`
testScript(SCRIPT, newStringValue("http://ru.wikipedia.org/wiki/Юникод"), t)
}

140
goja/builtin_json_test.go Normal file
View File

@ -0,0 +1,140 @@
package goja
import (
"encoding/json"
"errors"
"strings"
"testing"
"time"
)
func TestJSONMarshalObject(t *testing.T) {
vm := New()
o := vm.NewObject()
o.Set("test", 42)
o.Set("testfunc", vm.Get("Error"))
b, err := json.Marshal(o)
if err != nil {
t.Fatal(err)
}
if string(b) != `{"test":42}` {
t.Fatalf("Unexpected value: %s", b)
}
}
func TestJSONMarshalGoDate(t *testing.T) {
vm := New()
o := vm.NewObject()
o.Set("test", time.Unix(86400, 0).UTC())
b, err := json.Marshal(o)
if err != nil {
t.Fatal(err)
}
if string(b) != `{"test":"1970-01-02T00:00:00Z"}` {
t.Fatalf("Unexpected value: %s", b)
}
}
func TestJSONMarshalObjectCircular(t *testing.T) {
vm := New()
o := vm.NewObject()
o.Set("o", o)
_, err := json.Marshal(o)
if err == nil {
t.Fatal("Expected error")
}
if !strings.HasSuffix(err.Error(), "Converting circular structure to JSON") {
t.Fatalf("Unexpected error: %v", err)
}
}
func TestJSONStringifyCircularWrappedGo(t *testing.T) {
type CircularType struct {
Self *CircularType
}
vm := New()
v := CircularType{}
v.Self = &v
vm.Set("v", &v)
_, err := vm.RunString("JSON.stringify(v)")
if err == nil {
t.Fatal("Expected error")
}
if !strings.HasPrefix(err.Error(), "TypeError: Converting circular structure to JSON") {
t.Fatalf("Unexpected error: %v", err)
}
}
func TestJSONParseReviver(t *testing.T) {
// example from
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
const SCRIPT = `
JSON.parse('{"p": 5}', function(key, value) {
return typeof value === 'number'
? value * 2 // return value * 2 for numbers
: value // return everything else unchanged
})["p"]
`
testScript(SCRIPT, intToValue(10), t)
}
func TestQuoteMalformedSurrogatePair(t *testing.T) {
testScript(`JSON.stringify("\uD800")`, asciiString(`"\ud800"`), t)
}
func TestEOFWrapping(t *testing.T) {
vm := New()
_, err := vm.RunString("JSON.parse('{')")
if err == nil {
t.Fatal("Expected error")
}
if !strings.Contains(err.Error(), "Unexpected end of JSON input") {
t.Fatalf("Error doesn't contain human-friendly wrapper: %v", err)
}
}
type testMarshalJSONErrorStruct struct {
e error
}
func (s *testMarshalJSONErrorStruct) MarshalJSON() ([]byte, error) {
return nil, s.e
}
func TestMarshalJSONError(t *testing.T) {
vm := New()
v := testMarshalJSONErrorStruct{e: errors.New("test error")}
vm.Set("v", &v)
_, err := vm.RunString("JSON.stringify(v)")
if !errors.Is(err, v.e) {
t.Fatalf("Unexpected error: %v", err)
}
}
func BenchmarkJSONStringify(b *testing.B) {
b.StopTimer()
vm := New()
var createObj func(level int) *Object
createObj = func(level int) *Object {
o := vm.NewObject()
o.Set("field1", "test")
o.Set("field2", 42)
if level > 0 {
level--
o.Set("obj1", createObj(level))
o.Set("obj2", createObj(level))
}
return o
}
o := createObj(3)
json := vm.Get("JSON").(*Object)
stringify, _ := AssertFunction(json.Get("stringify"))
b.StartTimer()
for i := 0; i < b.N; i++ {
stringify(nil, o)
}
}

244
goja/builtin_map_test.go Normal file
View File

@ -0,0 +1,244 @@
package goja
import (
"fmt"
"hash/maphash"
"testing"
)
func TestMapEvilIterator(t *testing.T) {
const SCRIPT = `
'use strict';
var o = {};
function Iter(value) {
this.value = value;
this.idx = 0;
}
Iter.prototype.next = function() {
var idx = this.idx;
if (idx === 0) {
this.idx++;
return this.value;
}
return {done: true};
}
o[Symbol.iterator] = function() {
return new Iter({});
}
assert.throws(TypeError, function() {
new Map(o);
});
o[Symbol.iterator] = function() {
return new Iter({value: []});
}
function t(prefix) {
var m = new Map(o);
assert.sameValue(1, m.size, prefix+": m.size");
assert.sameValue(true, m.has(undefined), prefix+": m.has(undefined)");
assert.sameValue(undefined, m.get(undefined), prefix+": m.get(undefined)");
}
t("standard adder");
var count = 0;
var origSet = Map.prototype.set;
Map.prototype.set = function() {
count++;
origSet.apply(this, arguments);
}
t("custom adder");
assert.sameValue(1, count, "count");
undefined;
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestMapExportToNilMap(t *testing.T) {
vm := New()
var m map[int]interface{}
res, err := vm.RunString("new Map([[1, true]])")
if err != nil {
t.Fatal(err)
}
err = vm.ExportTo(res, &m)
if err != nil {
t.Fatal(err)
}
if len(m) != 1 {
t.Fatal(m)
}
if _, exists := m[1]; !exists {
t.Fatal(m)
}
}
func TestMapExportToNonNilMap(t *testing.T) {
vm := New()
m := map[int]interface{}{
2: true,
}
res, err := vm.RunString("new Map([[1, true]])")
if err != nil {
t.Fatal(err)
}
err = vm.ExportTo(res, &m)
if err != nil {
t.Fatal(err)
}
if len(m) != 1 {
t.Fatal(m)
}
if _, exists := m[1]; !exists {
t.Fatal(m)
}
}
func TestMapGetAdderGetIteratorOrder(t *testing.T) {
const SCRIPT = `
let getterCalled = 0;
class M extends Map {
get set() {
getterCalled++;
return null;
}
}
let getIteratorCalled = 0;
let iterable = {};
iterable[Symbol.iterator] = () => {
getIteratorCalled++
return {
next: 1
};
}
let thrown = false;
try {
new M(iterable);
} catch (e) {
if (e instanceof TypeError) {
thrown = true;
} else {
throw e;
}
}
thrown && getterCalled === 1 && getIteratorCalled === 0;
`
testScript(SCRIPT, valueTrue, t)
}
func ExampleObject_Export_map() {
vm := New()
m, err := vm.RunString(`
new Map([[1, true], [2, false]]);
`)
if err != nil {
panic(err)
}
exp := m.Export()
fmt.Printf("%T, %v\n", exp, exp)
// Output: [][2]interface {}, [[1 true] [2 false]]
}
func ExampleRuntime_ExportTo_mapToMap() {
vm := New()
m, err := vm.RunString(`
new Map([[1, true], [2, false]]);
`)
if err != nil {
panic(err)
}
exp := make(map[int]bool)
err = vm.ExportTo(m, &exp)
if err != nil {
panic(err)
}
fmt.Println(exp)
// Output: map[1:true 2:false]
}
func ExampleRuntime_ExportTo_mapToSlice() {
vm := New()
m, err := vm.RunString(`
new Map([[1, true], [2, false]]);
`)
if err != nil {
panic(err)
}
exp := make([][]interface{}, 0)
err = vm.ExportTo(m, &exp)
if err != nil {
panic(err)
}
fmt.Println(exp)
// Output: [[1 true] [2 false]]
}
func ExampleRuntime_ExportTo_mapToTypedSlice() {
vm := New()
m, err := vm.RunString(`
new Map([[1, true], [2, false]]);
`)
if err != nil {
panic(err)
}
exp := make([][2]interface{}, 0)
err = vm.ExportTo(m, &exp)
if err != nil {
panic(err)
}
fmt.Println(exp)
// Output: [[1 true] [2 false]]
}
func BenchmarkMapDelete(b *testing.B) {
var key1 Value = asciiString("a")
var key2 Value = asciiString("b")
one := intToValue(1)
two := intToValue(2)
for i := 0; i < b.N; i++ {
m := newOrderedMap(&maphash.Hash{})
m.set(key1, one)
m.set(key2, two)
if !m.remove(key1) {
b.Fatal("remove() returned false")
}
}
}
func BenchmarkMapDeleteJS(b *testing.B) {
prg, err := Compile("test.js", `
var m = new Map([['a',1], ['b', 2]]);
var result = m.delete('a');
if (!result || m.size !== 1) {
throw new Error("Fail!");
}
`,
false)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
vm := New()
_, err := vm.RunProgram(prg)
if err != nil {
b.Fatal(err)
}
}
}

View File

@ -596,14 +596,17 @@ func (r *Runtime) getPromise() *Object {
return ret
}
func (r *Runtime) wrapPromiseReaction(fObj *Object) func(interface{}) {
func (r *Runtime) wrapPromiseReaction(fObj *Object) func(interface{}) error {
f, _ := AssertFunction(fObj)
return func(x interface{}) {
_, _ = f(nil, r.ToValue(x))
return func(x interface{}) error {
_, err := f(nil, r.ToValue(x))
return err
}
}
// NewPromise creates and returns a Promise and resolving functions for it.
// The returned errors will be uncatchable errors, such as InterruptedError or StackOverflowError, which should be propagated upwards.
// Exceptions are handled through [PromiseRejectionTracker].
//
// WARNING: The returned values are not goroutine-safe and must not be called in parallel with VM running.
// In order to make use of this method you need an event loop such as the one in goja_nodejs (https://apigo.cc/gojs/goja_nodejs)
@ -618,11 +621,12 @@ func (r *Runtime) wrapPromiseReaction(fObj *Object) func(interface{}) {
// go func() {
// time.Sleep(500 * time.Millisecond) // or perform any other blocking operation
// loop.RunOnLoop(func(*goja.Runtime) { // resolve() must be called on the loop, cannot call it here
// resolve(result)
// err := resolve(result)
// // Handle uncatchable errors (e.g. by stopping the loop, panicking or setting a flag)
// })
// }()
// }
func (r *Runtime) NewPromise() (promise *Promise, resolve func(result interface{}), reject func(reason interface{})) {
func (r *Runtime) NewPromise() (promise *Promise, resolve, reject func(reason interface{}) error) {
p := r.newPromise(r.getPromisePrototype())
resolveF, rejectF := p.createResolvingFunctions()
return p, r.wrapPromiseReaction(resolveF), r.wrapPromiseReaction(rejectF)

1275
goja/builtin_proxy_test.go Normal file

File diff suppressed because it is too large Load Diff

180
goja/builtin_set_test.go Normal file
View File

@ -0,0 +1,180 @@
package goja
import (
"fmt"
"strings"
"testing"
)
func TestSetEvilIterator(t *testing.T) {
const SCRIPT = `
var o = {};
o[Symbol.iterator] = function() {
return {
next: function() {
if (!this.flag) {
this.flag = true;
return {};
}
return {done: true};
}
}
}
new Set(o);
undefined;
`
testScript(SCRIPT, _undefined, t)
}
func ExampleRuntime_ExportTo_setToMap() {
vm := New()
s, err := vm.RunString(`
new Set([1, 2, 3])
`)
if err != nil {
panic(err)
}
m := make(map[int]struct{})
err = vm.ExportTo(s, &m)
if err != nil {
panic(err)
}
fmt.Println(m)
// Output: map[1:{} 2:{} 3:{}]
}
func ExampleRuntime_ExportTo_setToSlice() {
vm := New()
s, err := vm.RunString(`
new Set([1, 2, 3])
`)
if err != nil {
panic(err)
}
var a []int
err = vm.ExportTo(s, &a)
if err != nil {
panic(err)
}
fmt.Println(a)
// Output: [1 2 3]
}
func TestSetExportToSliceCircular(t *testing.T) {
vm := New()
s, err := vm.RunString(`
let s = new Set();
s.add(s);
s;
`)
if err != nil {
t.Fatal(err)
}
var a []Value
err = vm.ExportTo(s, &a)
if err != nil {
t.Fatal(err)
}
if len(a) != 1 {
t.Fatalf("len: %d", len(a))
}
if a[0] != s {
t.Fatalf("a: %v", a)
}
}
func TestSetExportToArrayMismatchedLengths(t *testing.T) {
vm := New()
s, err := vm.RunString(`
new Set([1, 2])
`)
if err != nil {
panic(err)
}
var s1 [3]int
err = vm.ExportTo(s, &s1)
if err == nil {
t.Fatal("expected error")
}
if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestSetExportToNilMap(t *testing.T) {
vm := New()
var m map[int]interface{}
res, err := vm.RunString("new Set([1])")
if err != nil {
t.Fatal(err)
}
err = vm.ExportTo(res, &m)
if err != nil {
t.Fatal(err)
}
if len(m) != 1 {
t.Fatal(m)
}
if _, exists := m[1]; !exists {
t.Fatal(m)
}
}
func TestSetExportToNonNilMap(t *testing.T) {
vm := New()
m := map[int]interface{}{
2: true,
}
res, err := vm.RunString("new Set([1])")
if err != nil {
t.Fatal(err)
}
err = vm.ExportTo(res, &m)
if err != nil {
t.Fatal(err)
}
if len(m) != 1 {
t.Fatal(m)
}
if _, exists := m[1]; !exists {
t.Fatal(m)
}
}
func TestSetGetAdderGetIteratorOrder(t *testing.T) {
const SCRIPT = `
let getterCalled = 0;
class S extends Set {
get add() {
getterCalled++;
return null;
}
}
let getIteratorCalled = 0;
let iterable = {};
iterable[Symbol.iterator] = () => {
getIteratorCalled++
return {
next: 1
};
}
let thrown = false;
try {
new S(iterable);
} catch (e) {
if (e instanceof TypeError) {
thrown = true;
} else {
throw e;
}
}
thrown && getterCalled === 1 && getIteratorCalled === 0;
`
testScript(SCRIPT, valueTrue, t)
}

288
goja/builtin_string_test.go Normal file
View File

@ -0,0 +1,288 @@
package goja
import "testing"
func TestSubstr(t *testing.T) {
const SCRIPT = `
assert.sameValue('abc'.substr(0, false), '', 'start: 0, length: false');
assert.sameValue('abc'.substr(1, false), '', 'start: 1, length: false');
assert.sameValue('abc'.substr(2, false), '', 'start: 2, length: false');
assert.sameValue('abc'.substr(3, false), '', 'start: 3, length: false');
assert.sameValue('abc'.substr(0, NaN), '', 'start: 0, length: NaN');
assert.sameValue('abc'.substr(1, NaN), '', 'start: 1, length: NaN');
assert.sameValue('abc'.substr(2, NaN), '', 'start: 2, length: NaN');
assert.sameValue('abc'.substr(3, NaN), '', 'start: 3, length: NaN');
assert.sameValue('abc'.substr(0, ''), '', 'start: 0, length: ""');
assert.sameValue('abc'.substr(1, ''), '', 'start: 1, length: ""');
assert.sameValue('abc'.substr(2, ''), '', 'start: 2, length: ""');
assert.sameValue('abc'.substr(3, ''), '', 'start: 3, length: ""');
assert.sameValue('abc'.substr(0, null), '', 'start: 0, length: null');
assert.sameValue('abc'.substr(1, null), '', 'start: 1, length: null');
assert.sameValue('abc'.substr(2, null), '', 'start: 2, length: null');
assert.sameValue('abc'.substr(3, null), '', 'start: 3, length: null');
assert.sameValue('abc'.substr(0, -1), '', '0, -1');
assert.sameValue('abc'.substr(0, -2), '', '0, -2');
assert.sameValue('abc'.substr(0, -3), '', '0, -3');
assert.sameValue('abc'.substr(0, -4), '', '0, -4');
assert.sameValue('abc'.substr(1, -1), '', '1, -1');
assert.sameValue('abc'.substr(1, -2), '', '1, -2');
assert.sameValue('abc'.substr(1, -3), '', '1, -3');
assert.sameValue('abc'.substr(1, -4), '', '1, -4');
assert.sameValue('abc'.substr(2, -1), '', '2, -1');
assert.sameValue('abc'.substr(2, -2), '', '2, -2');
assert.sameValue('abc'.substr(2, -3), '', '2, -3');
assert.sameValue('abc'.substr(2, -4), '', '2, -4');
assert.sameValue('abc'.substr(3, -1), '', '3, -1');
assert.sameValue('abc'.substr(3, -2), '', '3, -2');
assert.sameValue('abc'.substr(3, -3), '', '3, -3');
assert.sameValue('abc'.substr(3, -4), '', '3, -4');
assert.sameValue('abc'.substr(0, 1), 'a', '0, 1');
assert.sameValue('abc'.substr(0, 2), 'ab', '0, 1');
assert.sameValue('abc'.substr(0, 3), 'abc', '0, 1');
assert.sameValue('abc'.substr(0, 4), 'abc', '0, 1');
assert.sameValue('abc'.substr(1, 1), 'b', '1, 1');
assert.sameValue('abc'.substr(1, 2), 'bc', '1, 1');
assert.sameValue('abc'.substr(1, 3), 'bc', '1, 1');
assert.sameValue('abc'.substr(1, 4), 'bc', '1, 1');
assert.sameValue('abc'.substr(2, 1), 'c', '2, 1');
assert.sameValue('abc'.substr(2, 2), 'c', '2, 1');
assert.sameValue('abc'.substr(2, 3), 'c', '2, 1');
assert.sameValue('abc'.substr(2, 4), 'c', '2, 1');
assert.sameValue('abc'.substr(3, 1), '', '3, 1');
assert.sameValue('abc'.substr(3, 2), '', '3, 1');
assert.sameValue('abc'.substr(3, 3), '', '3, 1');
assert.sameValue('abc'.substr(3, 4), '', '3, 1');
assert.sameValue('abc'.substr(0), 'abc', 'start: 0, length: unspecified');
assert.sameValue('abc'.substr(1), 'bc', 'start: 1, length: unspecified');
assert.sameValue('abc'.substr(2), 'c', 'start: 2, length: unspecified');
assert.sameValue('abc'.substr(3), '', 'start: 3, length: unspecified');
assert.sameValue(
'abc'.substr(0, undefined), 'abc', 'start: 0, length: undefined'
);
assert.sameValue(
'abc'.substr(1, undefined), 'bc', 'start: 1, length: undefined'
);
assert.sameValue(
'abc'.substr(2, undefined), 'c', 'start: 2, length: undefined'
);
assert.sameValue(
'abc'.substr(3, undefined), '', 'start: 3, length: undefined'
);
assert.sameValue('A', String.fromCharCode(65, 0x2014));
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestStringMatchSym(t *testing.T) {
const SCRIPT = `
function Prefix(p) {
this.p = p;
}
Prefix.prototype[Symbol.match] = function(s) {
return s.substring(0, this.p.length) === this.p;
}
var prefix1 = new Prefix("abc");
var prefix2 = new Prefix("def");
"abc123".match(prefix1) === true && "abc123".match(prefix2) === false &&
"def123".match(prefix1) === false && "def123".match(prefix2) === true;
`
testScript(SCRIPT, valueTrue, t)
}
func TestStringMatchAllSym(t *testing.T) {
const SCRIPT = `
function Prefix(p) {
this.p = p;
}
Prefix.prototype[Symbol.matchAll] = function(s) {
return s.substring(0, this.p.length) === this.p;
}
var prefix1 = new Prefix("abc");
var prefix2 = new Prefix("def");
"abc123".matchAll(prefix1) === true && "abc123".matchAll(prefix2) === false &&
"def123".matchAll(prefix1) === false && "def123".matchAll(prefix2) === true;
`
testScript(SCRIPT, valueTrue, t)
}
func TestGenericSplitter(t *testing.T) {
const SCRIPT = `
function MyRegexp(pattern, flags) {
if (pattern instanceof MyRegexp) {
pattern = pattern.wrapped;
}
this.wrapped = new RegExp(pattern, flags);
}
MyRegexp.prototype.exec = function() {
return this.wrapped.exec.apply(this.wrapped, arguments);
}
Object.defineProperty(MyRegexp.prototype, "lastIndex", {
get: function() {
return this.wrapped.lastIndex;
},
set: function(v) {
this.wrapped.lastIndex = v;
}
});
Object.defineProperty(MyRegexp.prototype, "flags", {
get: function() {
return this.wrapped.flags;
}
});
MyRegexp[Symbol.species] = MyRegexp;
MyRegexp.prototype[Symbol.split] = RegExp.prototype[Symbol.split];
var r = new MyRegexp(/ /);
var res = "a b c".split(r);
res.length === 3 && res[0] === "a" && res[1] === "b" && res[2] === "c";
`
testScript(SCRIPT, valueTrue, t)
}
func TestStringIterSurrPair(t *testing.T) {
const SCRIPT = `
var lo = '\uD834';
var hi = '\uDF06';
var pair = lo + hi;
var string = 'a' + pair + 'b' + lo + pair + hi + lo;
var iterator = string[Symbol.iterator]();
var result;
result = iterator.next();
if (result.value !== 'a') {
throw new Error("at 0: " + result.value);
}
result = iterator.next();
if (result.value !== pair) {
throw new Error("at 1: " + result.value);
}
`
testScript(SCRIPT, _undefined, t)
}
func TestValueStringBuilder(t *testing.T) {
t.Run("substringASCII", func(t *testing.T) {
t.Parallel()
var sb StringBuilder
str := newStringValue("a\U00010000b")
sb.WriteSubstring(str, 0, 1)
res := sb.String()
if res != asciiString("a") {
t.Fatal(res)
}
})
t.Run("substringASCIIPure", func(t *testing.T) {
t.Parallel()
var sb StringBuilder
str := newStringValue("ab")
sb.WriteSubstring(str, 0, 1)
res := sb.String()
if res != asciiString("a") {
t.Fatal(res)
}
})
t.Run("substringUnicode", func(t *testing.T) {
t.Parallel()
var sb StringBuilder
str := newStringValue("a\U00010000b")
sb.WriteSubstring(str, 1, 3)
res := sb.String()
if !res.SameAs(unicodeStringFromRunes([]rune{0x10000})) {
t.Fatal(res)
}
})
t.Run("substringASCIIUnicode", func(t *testing.T) {
t.Parallel()
var sb StringBuilder
str := newStringValue("a\U00010000b")
sb.WriteSubstring(str, 0, 2)
res := sb.String()
if !res.SameAs(unicodeStringFromRunes([]rune{'a', 0xD800})) {
t.Fatal(res)
}
})
t.Run("substringUnicodeASCII", func(t *testing.T) {
t.Parallel()
var sb StringBuilder
str := newStringValue("a\U00010000b")
sb.WriteSubstring(str, 2, 4)
res := sb.String()
if !res.SameAs(unicodeStringFromRunes([]rune{0xDC00, 'b'})) {
t.Fatal(res)
}
})
t.Run("concatSubstringUnicodeASCII", func(t *testing.T) {
t.Parallel()
var sb StringBuilder
sb.WriteString(newStringValue("юникод"))
sb.WriteSubstring(asciiString(" ascii"), 0, 6)
if res := sb.String(); !res.SameAs(newStringValue("юникод ascii")) {
t.Fatal(res)
}
})
t.Run("concat_ASCII_importedASCII", func(t *testing.T) {
t.Parallel()
var sb StringBuilder
sb.WriteString(asciiString("ascii"))
sb.WriteString(&importedString{s: " imported_ascii1234567890"})
s := sb.String()
if res, ok := s.(asciiString); !ok || res != "ascii imported_ascii1234567890" {
t.Fatal(s)
}
})
t.Run("concat_ASCII_importedUnicode", func(t *testing.T) {
t.Parallel()
var sb StringBuilder
sb.WriteString(asciiString("ascii"))
sb.WriteString(&importedString{s: " imported_юникод"})
s := sb.String()
if res, ok := s.(unicodeString); !ok || !res.SameAs(newStringValue("ascii imported_юникод")) {
t.Fatal(s)
}
})
}
func TestStringSplit(t *testing.T) {
const SCRIPT = `
assert(compareArray("".split("#",2), [""]));
assert(compareArray("".split("#"), [""]));
assert(compareArray("".split("",2), []));
assert(compareArray("".split(""), []));
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}

View File

@ -0,0 +1,326 @@
package goja
import (
"testing"
)
/*
func TestArrayBufferNew(t *testing.T) {
const SCRIPT = `
var b = new ArrayBuffer(16);
b.byteLength;
`
testScript(SCRIPT, intToValue(16), t)
}
*/
func TestArrayBufferSetUint32(t *testing.T) {
vm := New()
b := vm._newArrayBuffer(vm.global.ArrayBufferPrototype, nil)
b.data = make([]byte, 4)
b.setUint32(0, 0xCAFEBABE, bigEndian)
i := b.getUint32(0, bigEndian)
if i != 0xCAFEBABE {
t.Fatal(i)
}
i = b.getUint32(0, littleEndian)
if i != 0xBEBAFECA {
t.Fatal(i)
}
b.setUint32(0, 0xBEBAFECA, littleEndian)
i = b.getUint32(0, bigEndian)
if i != 0xCAFEBABE {
t.Fatal(i)
}
}
func TestArrayBufferSetInt32(t *testing.T) {
vm := New()
b := vm._newArrayBuffer(vm.global.ArrayBufferPrototype, nil)
b.data = make([]byte, 4)
b.setInt32(0, -42, littleEndian)
if v := b.getInt32(0, littleEndian); v != -42 {
t.Fatal(v)
}
b.setInt32(0, -42, bigEndian)
if v := b.getInt32(0, bigEndian); v != -42 {
t.Fatal(v)
}
}
func TestNewUint8Array(t *testing.T) {
const SCRIPT = `
var a = new Uint8Array(1);
a[0] = 42;
a.byteLength === 1 && a.length === 1 && a[0] === 42;
`
testScript(SCRIPT, valueTrue, t)
}
func TestNewUint16Array(t *testing.T) {
const SCRIPT = `
var a = new Uint16Array(1);
a[0] = 42;
a.byteLength === 2 && a.length === 1 && a[0] === 42;
`
testScript(SCRIPT, valueTrue, t)
}
func TestTypedArraysSpeciesConstructor(t *testing.T) {
const SCRIPT = `
'use strict';
function MyArray() {
var NewTarget = this.__proto__.constructor;
return Reflect.construct(Uint16Array, arguments, NewTarget);
}
MyArray.prototype = Object.create(Uint16Array.prototype, {
constructor: {
value: MyArray,
writable: true,
configurable: true
}
});
var a = new MyArray(1);
Object.defineProperty(MyArray, Symbol.species, {value: Uint8Array, configurable: true});
a[0] = 32767;
var b = a.filter(function() {
return true;
});
if (a[0] !== 32767) {
throw new Error("a[0]=" + a[0]);
}
if (!(b instanceof Uint8Array)) {
throw new Error("b instanceof Uint8Array");
}
if (b[0] != 255) {
throw new Error("b[0]=" + b[0]);
}
`
testScript(SCRIPT, _undefined, t)
}
func TestTypedArrayFromArrayBuffer(t *testing.T) {
const SCRIPT = `
var buf = new ArrayBuffer(2);
var a16 = new Uint16Array(buf);
if (!(a16 instanceof Uint16Array)) {
throw new Error("a16 is not an instance");
}
if (a16.buffer !== buf) {
throw new Error("a16.buffer !== buf");
}
if (a16.length !== 1) {
throw new Error("a16.length=" + a16.length);
}
var a8 = new Uint8Array(buf);
a8.fill(0xAA);
if (a16[0] !== 0xAAAA) {
throw new Error("a16[0]=" + a16[0]);
}
`
testScript(SCRIPT, _undefined, t)
}
func TestTypedArraySetOverlapDifSize(t *testing.T) {
const SCRIPT = `
var buf = new ArrayBuffer(4);
var src = new Uint8Array(buf, 1, 2);
src[0] = 1;
src[1] = 2;
var dst = new Uint16Array(buf);
dst.set(src);
if (dst[0] !== 1 || dst[1] !== 2) {
throw new Error("dst: " + dst.join(","));
}
`
testScript(SCRIPT, _undefined, t)
}
func TestTypedArraySetOverlapDifSize2(t *testing.T) {
const SCRIPT = `
var buf = new ArrayBuffer(4);
var src = new Uint8Array(buf, 0, 2);
src[0] = 1;
src[1] = 2;
var dst = new Uint16Array(buf);
dst.set(src);
if (dst[0] !== 1 || dst[1] !== 2) {
throw new Error("dst: " + dst.join(","));
}
`
testScript(SCRIPT, _undefined, t)
}
func TestTypedArraySetOverlapDifSize3(t *testing.T) {
const SCRIPT = `
var buf = new ArrayBuffer(8);
var src = new Uint8Array(buf, 2, 4);
src[0] = 1;
src[1] = 2;
src[2] = 3;
src[3] = 4;
var dst = new Uint16Array(buf);
dst.set(src);
if (dst[0] !== 1 || dst[1] !== 2 || dst[2] !== 3 || dst[3] !== 4) {
throw new Error("dst: " + dst.join(","));
}
`
testScript(SCRIPT, _undefined, t)
}
func TestTypedArraySetOverlapDifSize4(t *testing.T) {
const SCRIPT = `
var buf = new ArrayBuffer(10);
var dst = new Uint8Array(buf, 2, 5);
var src = new Uint16Array(buf);
src[0] = 1;
src[1] = 2;
src[2] = 3;
src[3] = 4;
src[4] = 5;
dst.set(src);
if (dst[0] !== 1 || dst[1] !== 2 || dst[2] !== 3 || dst[3] !== 4 || dst[4] !== 5) {
throw new Error("dst: " + dst.join(","));
}
`
testScript(SCRIPT, _undefined, t)
}
func TestTypedArraySetNoOverlapDifSizeForward(t *testing.T) {
const SCRIPT = `
var buf = new ArrayBuffer(10);
var dst = new Uint8Array(buf, 7, 2);
var src = new Uint16Array(buf, 0, 2);
src[0] = 1;
src[1] = 2;
dst.set(src);
if (dst[0] !== 1 || dst[1] !== 2 || src[0] !== 1 || src[1] !== 2) {
throw new Error("dst: " + dst.join(","));
}
`
testScript(SCRIPT, _undefined, t)
}
func TestTypedArraySetNoOverlapDifSizeBackward(t *testing.T) {
const SCRIPT = `
var buf = new ArrayBuffer(10);
var dst = new Uint8Array(buf, 0, 2);
var src = new Uint16Array(buf, 6, 2);
src[0] = 1;
src[1] = 2;
dst.set(src);
if (dst[0] !== 1 || dst[1] !== 2 || src[0] !== 1 || src[1] !== 2) {
throw new Error("dst: " + dst.join(","));
}
`
testScript(SCRIPT, _undefined, t)
}
func TestTypedArraySetNoOverlapDifSizeDifBuffers(t *testing.T) {
const SCRIPT = `
var dstBuf = new ArrayBuffer(1024);
var dst = new Uint8Array(dstBuf, 0, 2);
var src = new Uint16Array(2);
src[0] = 1;
src[1] = 2;
dst.set(src);
if (dst[0] !== 1 || dst[1] !== 2 || src[0] !== 1 || src[1] !== 2) {
throw new Error("dst: " + dst.join(","));
}
`
testScript(SCRIPT, _undefined, t)
}
func TestTypedArraySliceSameType(t *testing.T) {
const SCRIPT = `
var src = Uint8Array.of(1,2,3,4);
var dst = src.slice(1, 3);
if (dst.length !== 2 || dst[0] !== 2 || dst[1] !== 3) {
throw new Error("dst: " + dst.join(","));
}
`
testScript(SCRIPT, _undefined, t)
}
func TestTypedArraySliceDifType(t *testing.T) {
const SCRIPT = `
var src = Uint8Array.of(1,2,3,4);
Object.defineProperty(Uint8Array, Symbol.species, {value: Uint16Array, configurable: true});
var dst = src.slice(1, 3);
if (!(dst instanceof Uint16Array)) {
throw new Error("wrong dst type: " + dst);
}
if (dst.length !== 2 || dst[0] !== 2 || dst[1] !== 3) {
throw new Error("dst: " + dst.join(","));
}
`
testScript(SCRIPT, _undefined, t)
}
func TestTypedArraySortComparatorReturnValueFloats(t *testing.T) {
const SCRIPT = `
var a = Float64Array.of(
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 TestTypedArraySortComparatorReturnValueNegZero(t *testing.T) {
const SCRIPT = `
var a = new Uint8Array([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 TestInt32ArrayNegativeIndex(t *testing.T) {
const SCRIPT = `
new Int32Array()[-1] === undefined;
`
testScript(SCRIPT, valueTrue, t)
}
func TestTypedArrayDeleteUnconfigurable(t *testing.T) {
const SCRIPT = `
try {
(function() {
'use strict';
delete Uint8Array.prototype.BYTES_PER_ELEMENT;
})();
} catch(e) {
if (!(e instanceof TypeError)) {
throw e;
}
if (!e.message.startsWith("Cannot delete property")) {
throw e;
}
}
`
testScript(SCRIPT, _undefined, t)
}

View File

@ -0,0 +1,76 @@
package goja
import (
"testing"
)
func TestWeakMap(t *testing.T) {
vm := New()
_, err := vm.RunString(`
var m = new WeakMap();
var m1 = new WeakMap();
var key = {};
m.set(key, true);
m1.set(key, false);
if (!m.has(key)) {
throw new Error("has");
}
if (m.get(key) !== true) {
throw new Error("value does not match");
}
if (!m1.has(key)) {
throw new Error("has (m1)");
}
if (m1.get(key) !== false) {
throw new Error("m1 value does not match");
}
m.delete(key);
if (m.has(key)) {
throw new Error("m still has after delete");
}
if (!m1.has(key)) {
throw new Error("m1 does not have after delete from m");
}
`)
if err != nil {
t.Fatal(err)
}
}
func TestWeakMapGetAdderGetIteratorOrder(t *testing.T) {
const SCRIPT = `
let getterCalled = 0;
class M extends WeakMap {
get set() {
getterCalled++;
return null;
}
}
let getIteratorCalled = 0;
let iterable = {};
iterable[Symbol.iterator] = () => {
getIteratorCalled++
return {
next: 1
};
}
let thrown = false;
try {
new M(iterable);
} catch (e) {
if (e instanceof TypeError) {
thrown = true;
} else {
throw e;
}
}
thrown && getterCalled === 1 && getIteratorCalled === 0;
`
testScript(SCRIPT, valueTrue, t)
}

View File

@ -0,0 +1,101 @@
package goja
import (
"testing"
)
func TestWeakSetBasic(t *testing.T) {
const SCRIPT = `
var s = new WeakSet();
var o = {};
s.add(o);
if (!s.has(o)) {
throw new Error("has");
}
s.delete(o);
if (s.has(o)) {
throw new Error("still has");
}
`
testScript(SCRIPT, _undefined, t)
}
func TestWeakSetArraySimple(t *testing.T) {
const SCRIPT = `
var o1 = {}, o2 = {}, o3 = {};
var s = new WeakSet([o1, o2, o3]);
s.has(o1) && s.has(o2) && s.has(o3);
`
testScript(SCRIPT, valueTrue, t)
}
func TestWeakSetArrayGeneric(t *testing.T) {
const SCRIPT = `
var o1 = {}, o2 = {}, o3 = {};
var a = new Array();
var s;
var thrown = false;
a[1] = o2;
try {
s = new WeakSet(a);
} catch (e) {
if (e instanceof TypeError) {
thrown = true;
}
}
if (!thrown) {
throw new Error("Case 1 does not throw");
}
Object.defineProperty(a.__proto__, "0", {value: o1, writable: true, enumerable: true, configurable: true});
s = new WeakSet(a);
if (!(s.has(o1) && s.has(o2) && !s.has(o3))) {
throw new Error("Case 2 failed");
}
Object.defineProperty(a, "2", {value: o3, configurable: true});
s = new WeakSet(a);
s.has(o1) && s.has(o2) && s.has(o3);
`
testScript(SCRIPT, valueTrue, t)
}
func TestWeakSetGetAdderGetIteratorOrder(t *testing.T) {
const SCRIPT = `
let getterCalled = 0;
class S extends WeakSet {
get add() {
getterCalled++;
return null;
}
}
let getIteratorCalled = 0;
let iterable = {};
iterable[Symbol.iterator] = () => {
getIteratorCalled++
return {
next: 1
};
}
let thrown = false;
try {
new S(iterable);
} catch (e) {
if (e instanceof TypeError) {
thrown = true;
} else {
throw e;
}
}
thrown && getterCalled === 1 && getIteratorCalled === 0;
`
testScript(SCRIPT, valueTrue, t)
}

View File

@ -650,6 +650,8 @@ func (s *scope) finaliseVarAlloc(stackOffset int) (stashSize, stackSize int) {
*ap = loadThisStash(idx)
case initStack:
*ap = initStash(idx)
case initStackP:
*ap = initStashP(idx)
case resolveThisStack:
*ap = resolveThisStash(idx)
case _ret:
@ -666,6 +668,8 @@ func (s *scope) finaliseVarAlloc(stackOffset int) (stashSize, stackSize int) {
*ap = loadStash(idx)
case initStack:
*ap = initStash(idx)
case initStackP:
*ap = initStashP(idx)
default:
s.c.assert(false, s.c.p.sourceOffset(pc), "Unsupported instruction for 'this'")
}
@ -735,6 +739,8 @@ func (s *scope) finaliseVarAlloc(stackOffset int) (stashSize, stackSize int) {
*ap = loadThisStack{}
case initStack:
// no-op
case initStackP:
// no-op
case resolveThisStack:
// no-op
case _ret:

View File

@ -1255,6 +1255,44 @@ func (e *compiledAssignExpr) emitGetter(putOnStack bool) {
e.right.emitGetter(true)
e.c.emit(shr)
}, false, putOnStack)
case token.LOGICAL_AND, token.LOGICAL_OR, token.COALESCE:
e.left.emitRef()
e.c.emit(getValue)
mark := len(e.c.p.code)
e.c.emit(nil)
if id, ok := e.left.(*compiledIdentifierExpr); ok {
e.c.emitNamedOrConst(e.right, id.name)
} else {
e.right.emitGetter(true)
}
if putOnStack {
e.c.emit(putValue)
} else {
e.c.emit(putValueP)
}
e.c.emit(jump(2))
offset := len(e.c.p.code) - mark
switch e.operator {
case token.LOGICAL_AND:
if putOnStack {
e.c.p.code[mark] = jne(offset)
} else {
e.c.p.code[mark] = jneP(offset)
}
case token.LOGICAL_OR:
if putOnStack {
e.c.p.code[mark] = jeq(offset)
} else {
e.c.p.code[mark] = jeqP(offset)
}
case token.COALESCE:
if putOnStack {
e.c.p.code[mark] = jcoalesc(offset)
} else {
e.c.p.code[mark] = jcoalescP(offset)
}
}
e.c.emit(popRef)
default:
e.c.assert(false, e.offset, "Unknown assign operator: %s", e.operator.String())
panic("unreachable")
@ -1647,6 +1685,7 @@ func (e *compiledFunctionLiteral) compile() (prg *Program, name unistring.String
}
}
needInitThis := false
if thisBinding != nil {
if !s.isDynamic() && thisBinding.useCount() == 0 {
s.deleteBinding(thisBinding)
@ -1655,13 +1694,15 @@ func (e *compiledFunctionLiteral) compile() (prg *Program, name unistring.String
if thisBinding.inStash || s.isDynamic() {
delta++
thisBinding.emitInitAtScope(s, preambleLen-delta)
needInitThis = true
}
}
}
stashSize, stackSize := s.finaliseVarAlloc(0)
if thisBinding != nil && thisBinding.inStash && (!s.argsInStash || stackSize > 0) {
if needInitThis && (!s.argsInStash || firstForwardRef != -1 || stackSize > 0) {
code[preambleLen-delta] = initStashP(code[preambleLen-delta].(initStash))
delta++
code[preambleLen-delta] = loadStack(0)
} // otherwise, 'this' will be at stack[sp-1], no need to load
@ -2598,7 +2639,7 @@ func (e *compiledConditionalExpr) emitGetter(putOnStack bool) {
e.consequent.emitGetter(putOnStack)
j1 := len(e.c.p.code)
e.c.emit(nil)
e.c.p.code[j] = jne(len(e.c.p.code) - j)
e.c.p.code[j] = jneP(len(e.c.p.code) - j)
e.alternate.emitGetter(putOnStack)
e.c.p.code[j1] = jump(len(e.c.p.code) - j1)
}
@ -2648,7 +2689,7 @@ func (e *compiledLogicalOr) emitGetter(putOnStack bool) {
e.addSrcMap()
e.c.emit(nil)
e.c.emitExpr(e.right, true)
e.c.p.code[j] = jeq1(len(e.c.p.code) - j)
e.c.p.code[j] = jeq(len(e.c.p.code) - j)
if !putOnStack {
e.c.emit(pop)
}
@ -2730,7 +2771,7 @@ func (e *compiledLogicalAnd) emitGetter(putOnStack bool) {
e.addSrcMap()
e.c.emit(nil)
e.c.emitExpr(e.right, true)
e.c.p.code[j] = jneq1(len(e.c.p.code) - j)
e.c.p.code[j] = jne(len(e.c.p.code) - j)
if !putOnStack {
e.c.emit(pop)
}

View File

@ -226,7 +226,7 @@ func (c *compiler) compileLabeledDoWhileStatement(v *ast.DoWhileStatement, needR
c.compileStatement(v.Body, needResult)
c.block.cont = len(c.p.code)
c.emitExpr(c.compileExpression(v.Test), true)
c.emit(jeq(start - len(c.p.code)))
c.emit(jeqP(start - len(c.p.code)))
c.leaveBlock()
}
@ -339,7 +339,7 @@ func (c *compiler) compileLabeledForStatement(v *ast.ForStatement, needResult bo
c.emit(jump(start - len(c.p.code)))
if v.Test != nil {
if !testConst {
c.p.code[j] = jne(len(c.p.code) - j)
c.p.code[j] = jneP(len(c.p.code) - j)
}
}
end:
@ -535,7 +535,7 @@ func (c *compiler) compileLabeledWhileStatement(v *ast.WhileStatement, needResul
c.compileStatement(v.Body, needResult)
c.emit(jump(start - len(c.p.code)))
if !testTrue {
c.p.code[j] = jne(len(c.p.code) - j)
c.p.code[j] = jneP(len(c.p.code) - j)
}
end:
c.leaveBlock()
@ -713,16 +713,16 @@ func (c *compiler) compileIfStatement(v *ast.IfStatement, needResult bool) {
if v.Alternate != nil {
jmp1 := len(c.p.code)
c.emit(nil)
c.p.code[jmp] = jne(len(c.p.code) - jmp)
c.p.code[jmp] = jneP(len(c.p.code) - jmp)
c.compileIfBody(v.Alternate, needResult)
c.p.code[jmp1] = jump(len(c.p.code) - jmp1)
} else {
if needResult {
c.emit(jump(2))
c.p.code[jmp] = jne(len(c.p.code) - jmp)
c.p.code[jmp] = jneP(len(c.p.code) - jmp)
c.emit(clearResult)
} else {
c.p.code[jmp] = jne(len(c.p.code) - jmp)
c.p.code[jmp] = jneP(len(c.p.code) - jmp)
}
}
}
@ -1081,9 +1081,9 @@ func (c *compiler) compileSwitchStatement(v *ast.SwitchStatement, needResult boo
c.compileExpression(s.Test).emitGetter(true)
c.emit(op_strict_eq)
if db != nil {
c.emit(jne(2))
c.emit(jneP(2))
} else {
c.emit(jne(3), pop)
c.emit(jneP(3), pop)
}
jumps[i] = len(c.p.code)
c.emit(nil)

6055
goja/compiler_test.go Normal file

File diff suppressed because it is too large Load Diff

623
goja/date_test.go Normal file
View File

@ -0,0 +1,623 @@
package goja
import (
"testing"
"time"
)
func TestDateUTC(t *testing.T) {
const SCRIPT = `
assert.sameValue(Date.UTC(1970, 0), 0, '1970, 0');
assert.sameValue(Date.UTC(2016, 0), 1451606400000, '2016, 0');
assert.sameValue(Date.UTC(2016, 6), 1467331200000, '2016, 6');
assert.sameValue(Date.UTC(2016, 6, 1), 1467331200000, '2016, 6, 1');
assert.sameValue(Date.UTC(2016, 6, 5), 1467676800000, '2016, 6, 5');
assert.sameValue(Date.UTC(2016, 6, 5, 0), 1467676800000, '2016, 6, 5, 0');
assert.sameValue(Date.UTC(2016, 6, 5, 15), 1467730800000, '2016, 6, 5, 15');
assert.sameValue(
Date.UTC(2016, 6, 5, 15, 0), 1467730800000, '2016, 6, 5, 15, 0'
);
assert.sameValue(
Date.UTC(2016, 6, 5, 15, 34), 1467732840000, '2016, 6, 5, 15, 34'
);
assert.sameValue(
Date.UTC(2016, 6, 5, 15, 34, 0), 1467732840000, '2016, 6, 5, 15, 34, 0'
);
assert.sameValue(
Date.UTC(2016, 6, 5, 15, 34, 45), 1467732885000, '2016, 6, 5, 15, 34, 45'
);
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestNewDate(t *testing.T) {
const SCRIPT = `
var d1 = new Date("2016-09-01T12:34:56Z");
d1.getUTCHours() === 12;
`
testScript(SCRIPT, valueTrue, t)
}
func TestNewDate0(t *testing.T) {
const SCRIPT = `
(new Date(0)).toUTCString();
`
testScript(SCRIPT, asciiString("Thu, 01 Jan 1970 00:00:00 GMT"), t)
}
func TestSetHour(t *testing.T) {
l := time.Local
defer func() {
time.Local = l
}()
var err error
time.Local, err = time.LoadLocation("America/New_York")
if err != nil {
t.Fatal(err)
}
const SCRIPT = `
var d = new Date(2016, 8, 1, 12, 23, 45)
assert.sameValue(d.getHours(), 12);
assert.sameValue(d.getUTCHours(), 16);
d.setHours(13);
assert.sameValue(d.getHours(), 13);
assert.sameValue(d.getMinutes(), 23);
assert.sameValue(d.getSeconds(), 45);
d.setUTCHours(13);
assert.sameValue(d.getHours(), 9);
assert.sameValue(d.getMinutes(), 23);
assert.sameValue(d.getSeconds(), 45);
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestSetMinute(t *testing.T) {
l := time.Local
defer func() {
time.Local = l
}()
time.Local = time.FixedZone("Asia/Delhi", 5*60*60+30*60)
const SCRIPT = `
var d = new Date(2016, 8, 1, 12, 23, 45)
assert.sameValue(d.getHours(), 12);
assert.sameValue(d.getUTCHours(), 6);
assert.sameValue(d.getMinutes(), 23);
assert.sameValue(d.getUTCMinutes(), 53);
d.setMinutes(55);
assert.sameValue(d.getMinutes(), 55);
assert.sameValue(d.getSeconds(), 45);
d.setUTCMinutes(52);
assert.sameValue(d.getMinutes(), 22);
assert.sameValue(d.getHours(), 13);
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestTimezoneOffset(t *testing.T) {
const SCRIPT = `
var d = new Date(0);
d.getTimezoneOffset();
`
l := time.Local
defer func() {
time.Local = l
}()
var err error
time.Local, err = time.LoadLocation("Europe/London")
if err != nil {
t.Fatal(err)
}
testScript(SCRIPT, intToValue(-60), t)
}
func TestDateValueOf(t *testing.T) {
const SCRIPT = `
var d9 = new Date(1.23e15);
d9.valueOf();
`
testScript(SCRIPT, intToValue(1.23e15), t)
}
func TestDateSetters(t *testing.T) {
const SCRIPT = `
assert.sameValue((new Date(0)).setMilliseconds(2345), 2345, "setMilliseconds(2345)");
assert.sameValue(new Date(1000).setMilliseconds(23450000000000), 23450000001000, "setMilliseconds(23450000000000)");
assert.sameValue((new Date(0)).setUTCMilliseconds(2345), 2345, "setUTCMilliseconds()");
assert.sameValue((new Date(0)).setSeconds(12), 12000, "setSeconds()");
assert.sameValue((new Date(0)).setUTCSeconds(12), 12000, "setUTCSeconds()");
assert.sameValue((new Date(0)).setMinutes(12), 12 * 60 * 1000, "setMinutes()");
assert.sameValue((new Date(0)).setUTCMinutes(12), 12 * 60 * 1000, "setUTCMinutes()");
assert.sameValue((new Date("2016-06-01")).setHours(1), 1464739200000, "setHours()");
assert.sameValue((new Date("2016-06-01")).setUTCHours(1), 1464742800000, "setUTCHours()");
assert.sameValue((new Date(0)).setDate(2), 86400000, "setDate()");
assert.sameValue((new Date(0)).setUTCDate(2), 86400000, "setUTCDate()");
assert.sameValue((new Date(0)).setMonth(2), 5097600000, "setMonth()");
assert.sameValue((new Date(0)).setUTCMonth(2), 5097600000, "setUTCMonth()");
assert.sameValue((new Date(0)).setFullYear(1971), 31536000000, "setFullYear()");
assert.sameValue((new Date(0)).setFullYear(1971, 2, 3), 36806400000, "setFullYear(Y,M,D)");
assert.sameValue((new Date(0)).setUTCFullYear(1971), 31536000000, "setUTCFullYear()");
assert.sameValue((new Date(0)).setUTCFullYear(1971, 2, 3), 36806400000, "setUTCFullYear(Y,M,D)");
var d = new Date();
d.setTime(1151877845000);
assert.sameValue(d.getHours(), 23, "d.getHours()");
`
l := time.Local
defer func() {
time.Local = l
}()
var err error
time.Local, err = time.LoadLocation("Europe/London")
if err != nil {
t.Fatal(err)
}
testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestDateParse(t *testing.T) {
const SCRIPT = `
var zero = new Date(0);
assert.sameValue(zero.valueOf(), Date.parse(zero.toString()),
"Date.parse(zeroDate.toString())");
assert.sameValue(zero.valueOf(), Date.parse(zero.toUTCString()),
"Date.parse(zeroDate.toUTCString())");
assert.sameValue(zero.valueOf(), Date.parse(zero.toISOString()),
"Date.parse(zeroDate.toISOString())");
function testParse(str, expected) {
assert.sameValue(Date.parse(str), expected, str);
}
testParse("Mon, 02 Jan 2006 15:04:05 MST", 1136239445000);
testParse("Tue, 22 Jun 2021 13:54:40 GMT", 1624370080000);
testParse("Tuesday, 22 Jun 2021 13:54:40 GMT", 1624370080000);
testParse("Mon, 02 Jan 2006 15:04:05 GMT-07:00 (MST)", 1136239445000);
testParse("Mon, 02 Jan 2006 15:04:05 -07:00 (MST)", 1136239445000);
testParse("Monday, 02 Jan 2006 15:04:05 -0700 (MST)", 1136239445000);
testParse("Mon Jan 02 2006 15:04:05 GMT-0700 (GMT Standard Time)", 1136239445000);
testParse("Mon Jan 2 15:04:05 MST 2006", 1136239445000);
testParse("Mon Jan 02 15:04:05 MST 2006", 1136239445000);
testParse("Mon Jan 2 15:04:05 2006", 1136232245000);
testParse("Mon Jan 02 15:04:05 -0700 2006", 1136239445000);
testParse("Mon Jan 02 3:4 PM -0700 2006", 1136239440000);
testParse("December 04, 1986", 534056400000);
testParse("Dec 04, 1986", 534056400000);
testParse("Dec 4, 1986", 534056400000);
testParse("2006-01-02T15:04:05.000Z", 1136214245000);
testParse("2006-06-02T15:04:05.000", 1149275045000);
testParse("2006-01-02T15:04:05", 1136232245000);
testParse("2006-01-02 15:04:05.123", 1136232245123);
testParse("2006-01-02", 1136160000000);
testParse("2006T15:04-0700", 1136153040000);
testParse("2006T15:04+07:00", 1136102640000);
testParse("2006T15:04Z", 1136127840000);
testParse("2019-01-01T12:00:00.52Z", 1546344000520);
testParse("2019-01T12:00:00.52Z", 1546344000520);
testParse("+002019-01-01T12:00:00.52Z", 1546344000520);
var d = new Date("Mon, 02 Jan 2006 15:04:05 MST");
assert.sameValue(d.getUTCHours(), 22,
"new Date(\"Mon, 02 Jan 2006 15:04:05 MST\").getUTCHours()");
assert.sameValue(d.getHours(), 17,
"new Date(\"Mon, 02 Jan 2006 15:04:05 MST\").getHours()");
assert.sameValue(Date.parse("Mon, 02 Jan 2006 15:04:05 zzz"), NaN,
"Date.parse(\"Mon, 02 Jan 2006 15:04:05 zzz\")");
assert.sameValue(Date.parse("Mon, 02 Jan 2006 15:04:05 ZZZ"), NaN,
"Date.parse(\"Mon, 02 Jan 2006 15:04:05 ZZZ\")");
var minDateStr = "-271821-04-20T00:00:00.000Z";
var minDate = new Date(-8640000000000000);
assert.sameValue(minDate.toISOString(), minDateStr, "minDateStr");
assert.sameValue(Date.parse(minDateStr), minDate.valueOf(), "parse minDateStr");
var maxDateStr = "+275760-09-13T00:00:00.000Z";
var maxDate = new Date(8640000000000000);
assert.sameValue(maxDate.toISOString(), maxDateStr, "maxDateStr");
assert.sameValue(Date.parse(maxDateStr), maxDate.valueOf(), "parse maxDateStr");
var belowRange = "-271821-04-19T23:59:59.999Z";
var aboveRange = "+275760-09-13T00:00:00.001Z";
assert.sameValue(Date.parse(belowRange), NaN, "parse below minimum time value");
assert.sameValue(Date.parse(aboveRange), NaN, "parse above maximum time value");
`
l := time.Local
defer func() {
time.Local = l
}()
var err error
time.Local, err = time.LoadLocation("America/New_York")
if err != nil {
t.Fatal(err)
}
testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestDateMaxValues(t *testing.T) {
const SCRIPT = `
assert.sameValue((new Date(0)).setUTCMilliseconds(8.64e15), 8.64e15);
assert.sameValue((new Date(0)).setUTCSeconds(8640000000000), 8.64e15);
assert.sameValue((new Date(0)).setUTCMilliseconds(-8.64e15), -8.64e15);
assert.sameValue((new Date(0)).setUTCSeconds(-8640000000000), -8.64e15);
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestDateExport(t *testing.T) {
vm := New()
res, err := vm.RunString(`new Date(1000)`)
if err != nil {
t.Fatal(err)
}
exp := res.Export()
if d, ok := exp.(time.Time); ok {
if d.UnixNano()/1e6 != 1000 {
t.Fatalf("Invalid exported date: %v", d)
}
if loc := d.Location(); loc != time.Local {
t.Fatalf("Invalid timezone: %v", loc)
}
} else {
t.Fatalf("Invalid export type: %T", exp)
}
}
func TestDateToJSON(t *testing.T) {
const SCRIPT = `
Date.prototype.toJSON.call({ toISOString: function () { return 1; } })
`
testScript(SCRIPT, intToValue(1), t)
}
func TestDateExportType(t *testing.T) {
vm := New()
v, err := vm.RunString(`new Date()`)
if err != nil {
t.Fatal(err)
}
if typ := v.ExportType(); typ != typeTime {
t.Fatal(typ)
}
}
func TestDateParseV8(t *testing.T) {
// Taken from https://chromium.googlesource.com/v8/v8/+/refs/heads/main/test/mjsunit/date-parse.js
const SCRIPT = `
const assertEquals = assert.sameValue,
assertTrue = assert;
// Copyright 2008 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Test that we can parse dates in all the different formats that we
// have to support.
//
// These formats are all supported by KJS but a lot of them are not
// supported by Spidermonkey.
function testDateParse(string) {
var d = Date.parse(string);
assertEquals(946713600000, d, "parse: " + string);
};
// For local time we just test that parsing returns non-NaN positive
// number of milliseconds to make it timezone independent.
function testDateParseLocalTime(string) {
var d = Date.parse("parse-local-time:" + string);
assertTrue(!isNaN(d), "parse-local-time: " + string + " is NaN.");
assertTrue(d > 0, "parse-local-time: " + string + " <= 0.");
};
function testDateParseMisc(array) {
assertEquals(2, array.length, "array [" + array + "] length != 2.");
var string = array[0];
var expected = array[1];
var d = Date.parse(string);
assertEquals(expected, d, "parse-misc: " + string);
}
//
// Test all the formats in UT timezone.
//
var testCasesUT = [
'Sat, 01-Jan-2000 08:00:00 UT',
'Sat, 01 Jan 2000 08:00:00 UT',
'Jan 01 2000 08:00:00 UT',
'Jan 01 08:00:00 UT 2000',
'Saturday, 01-Jan-00 08:00:00 UT',
'01 Jan 00 08:00 +0000',
// Ignore weekdays.
'Mon, 01 Jan 2000 08:00:00 UT',
'Tue, 01 Jan 2000 08:00:00 UT',
// Ignore prefix that is not part of a date.
'[Saturday] Jan 01 08:00:00 UT 2000',
'Ignore all of this stuff because it is annoying 01 Jan 2000 08:00:00 UT',
'[Saturday] Jan 01 2000 08:00:00 UT',
'All of this stuff is really annoying, so it will be ignored Jan 01 2000 08:00:00 UT',
// If the three first letters of the month is a
// month name we are happy - ignore the rest.
'Sat, 01-Janisamonth-2000 08:00:00 UT',
'Sat, 01 Janisamonth 2000 08:00:00 UT',
'Janisamonth 01 2000 08:00:00 UT',
'Janisamonth 01 08:00:00 UT 2000',
'Saturday, 01-Janisamonth-00 08:00:00 UT',
'01 Janisamonth 00 08:00 +0000',
// Allow missing space between month and day.
'Janisamonthandtherestisignored01 2000 08:00:00 UT',
'Jan01 2000 08:00:00 UT',
// Allow year/month/day format.
'Sat, 2000/01/01 08:00:00 UT',
// Allow month/day/year format.
'Sat, 01/01/2000 08:00:00 UT',
// Allow month/day year format.
'Sat, 01/01 2000 08:00:00 UT',
// Allow comma instead of space after day, month and year.
'Sat, 01,Jan,2000,08:00:00 UT',
// Seconds are optional.
'Sat, 01-Jan-2000 08:00 UT',
'Sat, 01 Jan 2000 08:00 UT',
'Jan 01 2000 08:00 UT',
'Jan 01 08:00 UT 2000',
'Saturday, 01-Jan-00 08:00 UT',
'01 Jan 00 08:00 +0000',
// Allow AM/PM after the time.
'Sat, 01-Jan-2000 08:00 AM UT',
'Sat, 01 Jan 2000 08:00 AM UT',
'Jan 01 2000 08:00 AM UT',
'Jan 01 08:00 AM UT 2000',
'Saturday, 01-Jan-00 08:00 AM UT',
'01 Jan 00 08:00 AM +0000',
// White space and stuff in parenthesis is
// apparently allowed in most places where white
// space is allowed.
' Sat, 01-Jan-2000 08:00:00 UT ',
' Sat, 01 Jan 2000 08:00:00 UT ',
' Saturday, 01-Jan-00 08:00:00 UT ',
' 01 Jan 00 08:00 +0000 ',
' ()(Sat, 01-Jan-2000) Sat, 01-Jan-2000 08:00:00 UT ',
' Sat()(Sat, 01-Jan-2000)01 Jan 2000 08:00:00 UT ',
' Sat,(02)01 Jan 2000 08:00:00 UT ',
' Sat, 01(02)Jan 2000 08:00:00 UT ',
' Sat, 01 Jan 2000 (2001)08:00:00 UT ',
' Sat, 01 Jan 2000 (01)08:00:00 UT ',
' Sat, 01 Jan 2000 (01:00:00)08:00:00 UT ',
' Sat, 01 Jan 2000 08:00:00 (CDT)UT ',
' Sat, 01 Jan 2000 08:00:00 UT((((CDT))))',
' Saturday, 01-Jan-00 ()(((asfd)))(Sat, 01-Jan-2000)08:00:00 UT ',
' 01 Jan 00 08:00 ()(((asdf)))(Sat, 01-Jan-2000)+0000 ',
' 01 Jan 00 08:00 +0000()((asfd)(Sat, 01-Jan-2000)) '];
//
// Test that we do the right correction for different time zones.
// I'll assume that we can handle the same formats as for UT and only
// test a few formats for each of the timezones.
//
// GMT = UT
var testCasesGMT = [
'Sat, 01-Jan-2000 08:00:00 GMT',
'Sat, 01-Jan-2000 08:00:00 GMT+0',
'Sat, 01-Jan-2000 08:00:00 GMT+00',
'Sat, 01-Jan-2000 08:00:00 GMT+000',
'Sat, 01-Jan-2000 08:00:00 GMT+0000',
'Sat, 01-Jan-2000 08:00:00 GMT+00:00', // Interestingly, KJS cannot handle this.
'Sat, 01 Jan 2000 08:00:00 GMT',
'Saturday, 01-Jan-00 08:00:00 GMT',
'01 Jan 00 08:00 -0000',
'01 Jan 00 08:00 +0000'];
// EST = UT minus 5 hours.
var testCasesEST = [
'Sat, 01-Jan-2000 03:00:00 UTC-0500',
'Sat, 01-Jan-2000 03:00:00 UTC-05:00', // Interestingly, KJS cannot handle this.
'Sat, 01-Jan-2000 03:00:00 EST',
'Sat, 01 Jan 2000 03:00:00 EST',
'Saturday, 01-Jan-00 03:00:00 EST',
'01 Jan 00 03:00 -0500'];
// EDT = UT minus 4 hours.
var testCasesEDT = [
'Sat, 01-Jan-2000 04:00:00 EDT',
'Sat, 01 Jan 2000 04:00:00 EDT',
'Saturday, 01-Jan-00 04:00:00 EDT',
'01 Jan 00 04:00 -0400'];
// CST = UT minus 6 hours.
var testCasesCST = [
'Sat, 01-Jan-2000 02:00:00 CST',
'Sat, 01 Jan 2000 02:00:00 CST',
'Saturday, 01-Jan-00 02:00:00 CST',
'01 Jan 00 02:00 -0600'];
// CDT = UT minus 5 hours.
var testCasesCDT = [
'Sat, 01-Jan-2000 03:00:00 CDT',
'Sat, 01 Jan 2000 03:00:00 CDT',
'Saturday, 01-Jan-00 03:00:00 CDT',
'01 Jan 00 03:00 -0500'];
// MST = UT minus 7 hours.
var testCasesMST = [
'Sat, 01-Jan-2000 01:00:00 MST',
'Sat, 01 Jan 2000 01:00:00 MST',
'Saturday, 01-Jan-00 01:00:00 MST',
'01 Jan 00 01:00 -0700'];
// MDT = UT minus 6 hours.
var testCasesMDT = [
'Sat, 01-Jan-2000 02:00:00 MDT',
'Sat, 01 Jan 2000 02:00:00 MDT',
'Saturday, 01-Jan-00 02:00:00 MDT',
'01 Jan 00 02:00 -0600'];
// PST = UT minus 8 hours.
var testCasesPST = [
'Sat, 01-Jan-2000 00:00:00 PST',
'Sat, 01 Jan 2000 00:00:00 PST',
'Saturday, 01-Jan-00 00:00:00 PST',
'01 Jan 00 00:00 -0800',
// Allow missing time.
'Sat, 01-Jan-2000 PST'];
// PDT = UT minus 7 hours.
var testCasesPDT = [
'Sat, 01-Jan-2000 01:00:00 PDT',
'Sat, 01 Jan 2000 01:00:00 PDT',
'Saturday, 01-Jan-00 01:00:00 PDT',
'01 Jan 00 01:00 -0700'];
// Local time cases.
var testCasesLocalTime = [
// Allow timezone omission.
'Sat, 01-Jan-2000 08:00:00',
'Sat, 01 Jan 2000 08:00:00',
'Jan 01 2000 08:00:00',
'Jan 01 08:00:00 2000',
'Saturday, 01-Jan-00 08:00:00',
'01 Jan 00 08:00'];
// Misc. test cases that result in a different time value.
var testCasesMisc = [
// Special handling for years in the [0, 100) range.
['Sat, 01 Jan 0 08:00:00 UT', 946713600000], // year 2000
['Sat, 01 Jan 49 08:00:00 UT', 2493100800000], // year 2049
['Sat, 01 Jan 50 08:00:00 UT', -631123200000], // year 1950
['Sat, 01 Jan 99 08:00:00 UT', 915177600000], // year 1999
['Sat, 01 Jan 100 08:00:00 UT', -59011430400000], // year 100
// Test PM after time.
['Sat, 01-Jan-2000 08:00 PM UT', 946756800000],
['Sat, 01 Jan 2000 08:00 PM UT', 946756800000],
['Jan 01 2000 08:00 PM UT', 946756800000],
['Jan 01 08:00 PM UT 2000', 946756800000],
['Saturday, 01-Jan-00 08:00 PM UT', 946756800000],
['01 Jan 00 08:00 PM +0000', 946756800000]];
// Test different version of the ES5 date time string format.
var testCasesES5Misc = [
['2000-01-01T08:00:00.000Z', 946713600000],
['2000-01-01T08:00:00Z', 946713600000],
['2000-01-01T08:00Z', 946713600000],
['2000-01T08:00:00.000Z', 946713600000],
['2000T08:00:00.000Z', 946713600000],
['2000T08:00Z', 946713600000],
['2000-01T00:00:00.000-08:00', 946713600000],
['2000-01T08:00:00.001Z', 946713600001],
['2000-01T08:00:00.099Z', 946713600099],
['2000-01T08:00:00.999Z', 946713600999],
['2000-01T00:00:00.001-08:00', 946713600001],
['2000-01-01T24:00Z', 946771200000],
['2000-01-01T24:00:00Z', 946771200000],
['2000-01-01T24:00:00.000Z', 946771200000],
['2000-01-01T24:00:00.000Z', 946771200000]];
var testCasesES5MiscNegative = [
'2000-01-01TZ',
'2000-01-01T60Z',
'2000-01-01T60:60Z',
'2000-01-0108:00Z',
'2000-01-01T08Z',
'2000-01-01T24:01',
'2000-01-01T24:00:01',
'2000-01-01T24:00:00.001',
'2000-01-01T24:00:00.999Z'];
// TODO(littledan): This is an hack that could break in historically
// changing timezones that happened on this day, but allows us to
// check the date value for local times.
var localOffset = new Date('2000-01-01').getTimezoneOffset()*1000*60;
// Sanity check which is even more of a hack: in the timezones where
// these tests are likely to be run, the offset is nonzero because
// dates which don't include Z are in the local timezone.
if (this.Intl &&
["America/Los_Angeles", "Europe/Berlin", "Europe/Madrid"].indexOf(
Intl.DateTimeFormat().resolvedOptions().timeZone) != -1) {
assertTrue(localOffset != 0);
}
var testCasesES2016TZ = [
// If the timezone is absent and time is present, use local time
['2000-01-02T00:00', 946771200000 + localOffset],
['2000-01-02T00:00:00', 946771200000 + localOffset],
['2000-01-02T00:00:00.000', 946771200000 + localOffset],
// If timezone is absent and time is absent, use UTC
['2000-01-02', 946771200000],
['2000-01-02', 946771200000],
['2000-01-02', 946771200000],
];
// Run all the tests.
testCasesUT.forEach(testDateParse);
testCasesGMT.forEach(testDateParse);
testCasesEST.forEach(testDateParse);
testCasesEDT.forEach(testDateParse);
testCasesCST.forEach(testDateParse);
testCasesCDT.forEach(testDateParse);
testCasesMST.forEach(testDateParse);
testCasesMDT.forEach(testDateParse);
testCasesPST.forEach(testDateParse);
testCasesPDT.forEach(testDateParse);
testCasesLocalTime.forEach(testDateParseLocalTime);
testCasesMisc.forEach(testDateParseMisc);
// ES5 date time string format compliance.
testCasesES5Misc.forEach(testDateParseMisc);
testCasesES5MiscNegative.forEach(function (s) {
assertTrue(isNaN(Date.parse(s)), s + " is not NaN.");
});
testCasesES2016TZ.forEach(testDateParseMisc);
// Test that we can parse our own date format.
// (Dates from 1970 to ~2070 with 150h steps.)
for (var i = 0; i < 24 * 365 * 100; i += 150) {
var ms = i * (3600 * 1000);
var s = (new Date(ms)).toString();
assertEquals(ms, Date.parse(s), "parse own: " + s);
}
// Negative tests.
var testCasesNegative = [
'May 25 2008 1:30 (PM)) UTC', // Bad unmatched ')' after number.
'May 25 2008 1:30( )AM (PM)', //
'a1', // Issue 126448, 53209.
'nasfdjklsfjoaifg1',
'x_2',
'May 25 2008 AAA (GMT)']; // Unknown word after number.
testCasesNegative.forEach(function (s) {
assertTrue(isNaN(Date.parse(s)), s + " is not NaN.");
});
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}

3
goja/extract_failed_tests.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
sed -En 's/^.*FAIL: TestTC39\/tc39\/(test\/.*.js).*$/"\1": true,/p'

110
goja/file/README.markdown Normal file
View File

@ -0,0 +1,110 @@
# file
--
import "apigo.cc/gojs/goja/file"
Package file encapsulates the file abstractions used by the ast & parser.
## Usage
#### type File
```go
type File struct {
}
```
#### func NewFile
```go
func NewFile(filename, src string, base int) *File
```
#### func (*File) Base
```go
func (fl *File) Base() int
```
#### func (*File) Name
```go
func (fl *File) Name() string
```
#### func (*File) Source
```go
func (fl *File) Source() string
```
#### type FileSet
```go
type FileSet struct {
}
```
A FileSet represents a set of source files.
#### func (*FileSet) AddFile
```go
func (self *FileSet) AddFile(filename, src string) int
```
AddFile adds a new file with the given filename and src.
This an internal method, but exported for cross-package use.
#### func (*FileSet) File
```go
func (self *FileSet) File(idx Idx) *File
```
#### func (*FileSet) Position
```go
func (self *FileSet) Position(idx Idx) *Position
```
Position converts an Idx in the FileSet into a Position.
#### type Idx
```go
type Idx int
```
Idx is a compact encoding of a source position within a file set. It can be
converted into a Position for a more convenient, but much larger,
representation.
#### type Position
```go
type Position struct {
Filename string // The filename where the error occurred, if any
Offset int // The src offset
Line int // The line number, starting at 1
Column int // The column number, starting at 1 (The character count)
}
```
Position describes an arbitrary source position including the filename, line,
and column location.
#### func (*Position) String
```go
func (self *Position) String() string
```
String returns a string in one of several forms:
file:line:column A valid position with filename
line:column A valid position without filename
file An invalid position with filename
- An invalid position without filename
--
**godocdown** http://github.com/robertkrimen/godocdown

View File

@ -5,6 +5,7 @@ import (
"fmt"
"net/url"
"path"
"path/filepath"
"sort"
"strings"
"sync"
@ -182,9 +183,10 @@ func (fl *File) Position(offset int) Position {
func ResolveSourcemapURL(basename, source string) *url.URL {
// if the url is absolute(has scheme) there is nothing to do
smURL, err := url.Parse(strings.TrimSpace(source))
smURL, err := url.Parse(filepath.ToSlash(strings.TrimSpace(source)))
if err == nil && !smURL.IsAbs() {
baseURL, err1 := url.Parse(strings.TrimSpace(basename))
basename = filepath.ToSlash(strings.TrimSpace(basename))
baseURL, err1 := url.Parse(basename)
if err1 == nil && path.IsAbs(baseURL.Path) {
smURL = baseURL.ResolveReference(smURL)
} else {

76
goja/file/file_test.go Normal file
View File

@ -0,0 +1,76 @@
package file
import (
"testing"
)
func TestPosition(t *testing.T) {
const SRC = `line1
line2
line3`
f := NewFile("", SRC, 0)
tests := []struct {
offset int
line int
col int
}{
{0, 1, 1},
{2, 1, 3},
{2, 1, 3},
{6, 2, 1},
{7, 2, 2},
{12, 3, 1},
{12, 3, 1},
{13, 3, 2},
{13, 3, 2},
{16, 3, 5},
{17, 3, 6},
}
for i, test := range tests {
if p := f.Position(test.offset); p.Line != test.line || p.Column != test.col {
t.Fatalf("%d. Line: %d, col: %d", i, p.Line, p.Column)
}
}
}
func TestFileConcurrency(t *testing.T) {
const SRC = `line1
line2
line3`
f := NewFile("", SRC, 0)
go func() {
f.Position(12)
}()
f.Position(2)
}
func TestGetSourceFilename(t *testing.T) {
tests := []struct {
source, basename, result string
}{
{"test.js", "base.js", "test.js"},
{"test.js", "../base.js", "../test.js"},
{"test.js", "/somewhere/base.js", "/somewhere/test.js"},
{"/test.js", "/somewhere/base.js", "/test.js"},
{"/test.js", "file:///somewhere/base.js", "file:///test.js"},
{"file:///test.js", "base.js", "file:///test.js"},
{"file:///test.js", "/somwehere/base.js", "file:///test.js"},
{"file:///test.js", "file:///somewhere/base.js", "file:///test.js"},
{"../test.js", "/somewhere/else/base.js", "/somewhere/test.js"},
{"../test.js", "file:///somewhere/else/base.js", "file:///somewhere/test.js"},
{"../test.js", "https://example.com/somewhere/else/base.js", "https://example.com/somewhere/test.js"},
{"\ntest.js", "base123.js", "test.js"},
{"\rtest2.js\t\n ", "base123.js", "test2.js"},
{"z:/file.map", "a.js", "z:/file.map"},
// TODO find something that won't parse
}
for _, test := range tests {
resultURL := ResolveSourcemapURL(test.basename, test.source)
result := resultURL.String()
if result != test.result {
t.Fatalf("source: %q, basename %q produced %q instead of %q", test.source, test.basename, result, test.result)
}
}
}

21
goja/ftoa/LICENSE_LUCENE Normal file
View File

@ -0,0 +1,21 @@
Copyright (C) 1998, 1999 by Lucent Technologies
All Rights Reserved
Permission to use, copy, modify, and distribute this software and
its documentation for any purpose and without fee is hereby
granted, provided that the above copyright notice appear in all
copies and that both that the copyright notice and this
permission notice and warranty disclaimer appear in supporting
documentation, and that the name of Lucent or any of its entities
not be used in advertising or publicity pertaining to
distribution of the software without specific, written prior
permission.
LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.

View File

@ -0,0 +1,9 @@
package ftoa
import "testing"
func TestFToBaseStr(t *testing.T) {
if s := FToBaseStr(0.8466400793967279, 36); s != "0.uh8u81s3fz" {
t.Fatal(s)
}
}

92
goja/ftoa/ftostr_test.go Normal file
View File

@ -0,0 +1,92 @@
package ftoa
import (
"math"
"strconv"
"testing"
)
func _testFToStr(num float64, mode FToStrMode, precision int, expected string, t *testing.T) {
buf := FToStr(num, mode, precision, nil)
if s := string(buf); s != expected {
t.Fatalf("expected: '%s', actual: '%s", expected, s)
}
if !math.IsNaN(num) && num != 0 && !math.Signbit(num) {
_testFToStr(-num, mode, precision, "-"+expected, t)
}
}
func testFToStr(num float64, mode FToStrMode, precision int, expected string, t *testing.T) {
t.Run("", func(t *testing.T) {
t.Parallel()
_testFToStr(num, mode, precision, expected, t)
})
}
func TestDtostr(t *testing.T) {
testFToStr(0, ModeStandard, 0, "0", t)
testFToStr(1, ModeStandard, 0, "1", t)
testFToStr(9007199254740991, ModeStandard, 0, "9007199254740991", t)
testFToStr(math.MaxInt64, ModeStandardExponential, 0, "9.223372036854776e+18", t)
testFToStr(1e-5, ModeFixed, 1, "0.0", t)
testFToStr(8.85, ModeExponential, 2, "8.8e+0", t)
testFToStr(885, ModeExponential, 2, "8.9e+2", t)
testFToStr(25, ModeExponential, 1, "3e+1", t)
testFToStr(1e-6, ModeFixed, 7, "0.0000010", t)
testFToStr(math.Pi, ModeStandardExponential, 0, "3.141592653589793e+0", t)
testFToStr(math.Inf(1), ModeStandard, 0, "Infinity", t)
testFToStr(math.NaN(), ModeStandard, 0, "NaN", t)
testFToStr(math.SmallestNonzeroFloat64, ModeExponential, 40, "4.940656458412465441765687928682213723651e-324", t)
testFToStr(3.5844466002796428e+298, ModeStandard, 0, "3.5844466002796428e+298", t)
testFToStr(math.Float64frombits(0x0010000000000000), ModeStandard, 0, "2.2250738585072014e-308", t) // smallest normal
testFToStr(math.Float64frombits(0x000FFFFFFFFFFFFF), ModeStandard, 0, "2.225073858507201e-308", t) // largest denormal
testFToStr(4294967272.0, ModePrecision, 14, "4294967272.0000", t)
}
func BenchmarkDtostrSmall(b *testing.B) {
var buf [128]byte
b.ReportAllocs()
for i := 0; i < b.N; i++ {
FToStr(math.Pi, ModeStandardExponential, 0, buf[:0])
}
}
func BenchmarkDtostrShort(b *testing.B) {
var buf [128]byte
b.ReportAllocs()
for i := 0; i < b.N; i++ {
FToStr(3.1415, ModeStandard, 0, buf[:0])
}
}
func BenchmarkDtostrFixed(b *testing.B) {
var buf [128]byte
b.ReportAllocs()
for i := 0; i < b.N; i++ {
FToStr(math.Pi, ModeFixed, 4, buf[:0])
}
}
func BenchmarkDtostrBig(b *testing.B) {
var buf [128]byte
b.ReportAllocs()
for i := 0; i < b.N; i++ {
FToStr(math.SmallestNonzeroFloat64, ModeExponential, 40, buf[:0])
}
}
func BenchmarkAppendFloatBig(b *testing.B) {
var buf [128]byte
b.ReportAllocs()
for i := 0; i < b.N; i++ {
strconv.AppendFloat(buf[:0], math.SmallestNonzeroFloat64, 'e', 40, 64)
}
}
func BenchmarkAppendFloatSmall(b *testing.B) {
var buf [128]byte
b.ReportAllocs()
for i := 0; i < b.N; i++ {
strconv.AppendFloat(buf[:0], math.Pi, 'e', -1, 64)
}
}

View File

@ -0,0 +1,26 @@
Copyright 2014, the V8 project authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -293,8 +293,16 @@ func (f *classFuncObject) vmCall(vm *vm, n int) {
f.Call(FunctionCall{})
}
func (f *classFuncObject) exportType() reflect.Type {
return reflectTypeCtor
}
func (f *classFuncObject) Construct(ccall ConstructorCall) *Object {
return f.construct(ccall.Arguments, ccall.NewTarget)
}
func (f *classFuncObject) export(*objectExportCtx) interface{} {
return f.Call
return f.Construct
}
func (f *classFuncObject) createInstance(args []Value, newTarget *Object) (instance *Object) {

305
goja/func_test.go Normal file
View File

@ -0,0 +1,305 @@
package goja
import (
"errors"
"fmt"
"reflect"
"testing"
)
func TestFuncProto(t *testing.T) {
const SCRIPT = `
"use strict";
function A() {}
A.__proto__ = Object;
A.prototype = {};
function B() {}
B.__proto__ = Object.create(null);
var thrown = false;
try {
delete B.prototype;
} catch (e) {
thrown = e instanceof TypeError;
}
thrown;
`
testScript(SCRIPT, valueTrue, t)
}
func TestFuncPrototypeRedefine(t *testing.T) {
const SCRIPT = `
let thrown = false;
try {
Object.defineProperty(function() {}, "prototype", {
set: function(_value) {},
});
} catch (e) {
if (e instanceof TypeError) {
thrown = true;
} else {
throw e;
}
}
thrown;
`
testScript(SCRIPT, valueTrue, t)
}
func TestFuncExport(t *testing.T) {
vm := New()
typ := reflect.TypeOf((func(FunctionCall) Value)(nil))
f := func(expr string, t *testing.T) {
v, err := vm.RunString(expr)
if err != nil {
t.Fatal(err)
}
if actualTyp := v.ExportType(); actualTyp != typ {
t.Fatalf("Invalid export type: %v", actualTyp)
}
ev := v.Export()
if actualTyp := reflect.TypeOf(ev); actualTyp != typ {
t.Fatalf("Invalid export value: %v", ev)
}
}
t.Run("regular function", func(t *testing.T) {
f("(function() {})", t)
})
t.Run("arrow function", func(t *testing.T) {
f("(()=>{})", t)
})
t.Run("method", func(t *testing.T) {
f("({m() {}}).m", t)
})
}
func TestFuncWrapUnwrap(t *testing.T) {
vm := New()
f := func(a int, b string) bool {
return a > 0 && b != ""
}
var f1 func(int, string) bool
v := vm.ToValue(f)
if et := v.ExportType(); et != reflect.TypeOf(f1) {
t.Fatal(et)
}
err := vm.ExportTo(v, &f1)
if err != nil {
t.Fatal(err)
}
if !f1(1, "a") {
t.Fatal("not true")
}
}
func TestWrappedFunc(t *testing.T) {
vm := New()
f := func(a int, b string) bool {
return a > 0 && b != ""
}
vm.Set("f", f)
const SCRIPT = `
assert.sameValue(typeof f, "function");
const s = f.toString()
assert(s.endsWith("TestWrappedFunc.func1() { [native code] }"), s);
assert(f(1, "a"));
assert(!f(0, ""));
`
vm.testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestWrappedFuncErrorPassthrough(t *testing.T) {
vm := New()
e := errors.New("test")
f := func(a int) error {
if a > 0 {
return e
}
return nil
}
var f1 func(a int64) error
err := vm.ExportTo(vm.ToValue(f), &f1)
if err != nil {
t.Fatal(err)
}
if err := f1(1); err != e {
t.Fatal(err)
}
}
func ExampleAssertConstructor() {
vm := New()
res, err := vm.RunString(`
(class C {
constructor(x) {
this.x = x;
}
})
`)
if err != nil {
panic(err)
}
if ctor, ok := AssertConstructor(res); ok {
obj, err := ctor(nil, vm.ToValue("Test"))
if err != nil {
panic(err)
}
fmt.Print(obj.Get("x"))
} else {
panic("Not a constructor")
}
// Output: Test
}
type testAsyncCtx struct {
group string
refCount int
}
type testAsyncContextTracker struct {
ctx *testAsyncCtx
logFunc func(...interface{})
resumed bool
}
func (s *testAsyncContextTracker) Grab() interface{} {
ctx := s.ctx
if ctx != nil {
s.logFunc("Grab", ctx.group)
ctx.refCount++
}
return ctx
}
func (s *testAsyncContextTracker) Resumed(trackingObj interface{}) {
s.logFunc("Resumed", trackingObj)
if s.resumed {
panic("Nested Resumed() calls")
}
s.ctx = trackingObj.(*testAsyncCtx)
s.resumed = true
}
func (s *testAsyncContextTracker) releaseCtx() {
s.ctx.refCount--
if s.ctx.refCount < 0 {
panic("refCount < 0")
}
if s.ctx.refCount == 0 {
s.logFunc(s.ctx.group, "is finished")
}
}
func (s *testAsyncContextTracker) Exited() {
s.logFunc("Exited")
if s.ctx != nil {
s.releaseCtx()
s.ctx = nil
}
s.resumed = false
}
func TestAsyncContextTracker(t *testing.T) {
r := New()
var tracker testAsyncContextTracker
tracker.logFunc = t.Log
group := func(name string, asyncFunc func(FunctionCall) Value) Value {
prevCtx := tracker.ctx
defer func() {
t.Log("Returned", name)
tracker.releaseCtx()
tracker.ctx = prevCtx
}()
tracker.ctx = &testAsyncCtx{
group: name,
refCount: 1,
}
t.Log("Set", name)
return asyncFunc(FunctionCall{})
}
r.SetAsyncContextTracker(&tracker)
r.Set("group", group)
r.Set("check", func(expectedGroup, msg string) {
var groupName string
if tracker.ctx != nil {
groupName = tracker.ctx.group
}
if groupName != expectedGroup {
t.Fatalf("Unexpected group (%q), expected %q in %s", groupName, expectedGroup, msg)
}
t.Log("In", msg)
})
t.Run("", func(t *testing.T) {
_, err := r.RunString(`
group("1", async () => {
check("1", "line A");
await 3;
check("1", "line B");
group("2", async () => {
check("2", "line C");
await 4;
check("2", "line D");
})
}).then(() => {
check("", "line E");
})
`)
if err != nil {
t.Fatal(err)
}
})
t.Run("", func(t *testing.T) {
_, err := r.RunString(`
group("some", async () => {
check("some", "line A");
(async () => {
check("some", "line B");
await 1;
check("some", "line C");
await 2;
check("some", "line D");
})();
check("some", "line E");
});
`)
if err != nil {
t.Fatal(err)
}
})
t.Run("", func(t *testing.T) {
_, err := r.RunString(`
group("Main", async () => {
check("Main", "0.1");
await Promise.all([
group("A", async () => {
check("A", "1.1");
await 1;
check("A", "1.2");
}),
(async () => {
check("Main", "3.1");
})(),
group("B", async () => {
check("B", "2.1");
await 2;
check("B", "2.2");
})
]);
check("Main", "0.2");
});
`)
if err != nil {
t.Fatal(err)
}
})
}

201
goja/map_test.go Normal file
View File

@ -0,0 +1,201 @@
package goja
import (
"hash/maphash"
"math"
"math/big"
"strconv"
"testing"
)
func testMapHashVal(v1, v2 Value, expected bool, t *testing.T) {
var h maphash.Hash
actual := v1.hash(&h) == v2.hash(&h)
if actual != expected {
t.Fatalf("testMapHashVal failed for %v, %v", v1, v2)
}
}
func TestMapHash(t *testing.T) {
testMapHashVal(_NaN, _NaN, true, t)
testMapHashVal(valueTrue, valueFalse, false, t)
testMapHashVal(valueTrue, valueTrue, true, t)
testMapHashVal(intToValue(0), _negativeZero, true, t)
testMapHashVal(asciiString("Test"), asciiString("Test"), true, t)
testMapHashVal(newStringValue("Тест"), newStringValue("Тест"), true, 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)
//testMapHashVal(newStringValue("Тест"), asciiString("Test"), false, t)
//testMapHashVal(newStringValue("Тест"), newStringValue("Тест1"), false, t)
}
func TestOrderedMap(t *testing.T) {
m := newOrderedMap(&maphash.Hash{})
for i := int64(0); i < 50; i++ {
m.set(intToValue(i), asciiString(strconv.FormatInt(i, 10)))
}
if m.size != 50 {
t.Fatalf("Unexpected size: %d", m.size)
}
for i := int64(0); i < 50; i++ {
expected := asciiString(strconv.FormatInt(i, 10))
actual := m.get(intToValue(i))
if !expected.SameAs(actual) {
t.Fatalf("Wrong value for %d", i)
}
}
for i := int64(0); i < 50; i += 2 {
if !m.remove(intToValue(i)) {
t.Fatalf("remove(%d) return false", i)
}
}
if m.size != 25 {
t.Fatalf("Unexpected size: %d", m.size)
}
iter := m.newIter()
count := 0
for {
entry := iter.next()
if entry == nil {
break
}
m.remove(entry.key)
count++
}
if count != 25 {
t.Fatalf("Unexpected iter count: %d", count)
}
if m.size != 0 {
t.Fatalf("Unexpected size: %d", m.size)
}
}
func TestOrderedMapCollision(t *testing.T) {
m := newOrderedMap(&maphash.Hash{})
n1 := uint64(123456789)
n2 := math.Float64frombits(n1)
n1Key := intToValue(int64(n1))
n2Key := floatToValue(n2)
m.set(n1Key, asciiString("n1"))
m.set(n2Key, asciiString("n2"))
if m.size == len(m.hashTable) {
t.Fatal("Expected a collision but there wasn't one")
}
if n2Val := m.get(n2Key); !asciiString("n2").SameAs(n2Val) {
t.Fatalf("unexpected n2Val: %v", n2Val)
}
if n1Val := m.get(n1Key); !asciiString("n1").SameAs(n1Val) {
t.Fatalf("unexpected nVal: %v", n1Val)
}
if !m.remove(n1Key) {
t.Fatal("removing n1Key returned false")
}
if n2Val := m.get(n2Key); !asciiString("n2").SameAs(n2Val) {
t.Fatalf("2: unexpected n2Val: %v", n2Val)
}
}
func TestOrderedMapIter(t *testing.T) {
m := newOrderedMap(&maphash.Hash{})
iter := m.newIter()
ent := iter.next()
if ent != nil {
t.Fatal("entry should be nil")
}
iter1 := m.newIter()
m.set(intToValue(1), valueTrue)
ent = iter.next()
if ent != nil {
t.Fatal("2: entry should be nil")
}
ent = iter1.next()
if ent == nil {
t.Fatal("entry is nil")
}
if !intToValue(1).SameAs(ent.key) {
t.Fatal("unexpected key")
}
if !valueTrue.SameAs(ent.value) {
t.Fatal("unexpected value")
}
}
func TestOrderedMapIterVisitAfterReAdd(t *testing.T) {
m := newOrderedMap(&maphash.Hash{})
one := intToValue(1)
two := intToValue(2)
m.set(one, valueTrue)
m.set(two, valueTrue)
iter := m.newIter()
entry := iter.next()
if !one.SameAs(entry.key) {
t.Fatalf("1: unexpected key: %v", entry.key)
}
if !m.remove(one) {
t.Fatal("remove returned false")
}
entry = iter.next()
if !two.SameAs(entry.key) {
t.Fatalf("2: unexpected key: %v", entry.key)
}
m.set(one, valueTrue)
entry = iter.next()
if entry == nil {
t.Fatal("entry is nil")
}
if !one.SameAs(entry.key) {
t.Fatalf("3: unexpected key: %v", entry.key)
}
}
func TestOrderedMapIterAddAfterClear(t *testing.T) {
m := newOrderedMap(&maphash.Hash{})
one := intToValue(1)
m.set(one, valueTrue)
iter := m.newIter()
iter.next()
m.clear()
m.set(one, valueTrue)
entry := iter.next()
if entry == nil {
t.Fatal("entry is nil")
}
if entry.key != one {
t.Fatalf("unexpected key: %v", entry.key)
}
entry = iter.next()
if entry != nil {
t.Fatalf("entry is not nil: %v", entry)
}
}
func TestOrderedMapIterDeleteCurrent(t *testing.T) {
m := newOrderedMap(&maphash.Hash{})
one := intToValue(1)
two := intToValue(2)
iter := m.newIter()
m.set(one, valueTrue)
m.set(two, valueTrue)
entry := iter.next()
if entry.key != one {
t.Fatalf("unexpected key: %v", entry.key)
}
m.remove(one)
entry = iter.next()
if entry.key != two {
t.Fatalf("2: unexpected key: %v", entry.key)
}
}

420
goja/object_dynamic_test.go Normal file
View File

@ -0,0 +1,420 @@
package goja
import (
"sync"
"testing"
)
type testDynObject struct {
r *Runtime
m map[string]Value
}
func (t *testDynObject) Get(key string) Value {
return t.m[key]
}
func (t *testDynObject) Set(key string, val Value) bool {
t.m[key] = val
return true
}
func (t *testDynObject) Has(key string) bool {
_, exists := t.m[key]
return exists
}
func (t *testDynObject) Delete(key string) bool {
delete(t.m, key)
return true
}
func (t *testDynObject) Keys() []string {
keys := make([]string, 0, len(t.m))
for k := range t.m {
keys = append(keys, k)
}
return keys
}
type testDynArray struct {
r *Runtime
a []Value
}
func (t *testDynArray) Len() int {
return len(t.a)
}
func (t *testDynArray) Get(idx int) Value {
if idx < 0 {
idx += len(t.a)
}
if idx >= 0 && idx < len(t.a) {
return t.a[idx]
}
return nil
}
func (t *testDynArray) expand(newLen int) {
if newLen > cap(t.a) {
a := make([]Value, newLen)
copy(a, t.a)
t.a = a
} else {
t.a = t.a[:newLen]
}
}
func (t *testDynArray) Set(idx int, val Value) bool {
if idx < 0 {
idx += len(t.a)
}
if idx < 0 {
return false
}
if idx >= len(t.a) {
t.expand(idx + 1)
}
t.a[idx] = val
return true
}
func (t *testDynArray) SetLen(i int) bool {
if i > len(t.a) {
t.expand(i)
return true
}
if i < 0 {
return false
}
if i < len(t.a) {
tail := t.a[i:len(t.a)]
for j := range tail {
tail[j] = nil
}
t.a = t.a[:i]
}
return true
}
func TestDynamicObject(t *testing.T) {
vm := New()
dynObj := &testDynObject{
r: vm,
m: make(map[string]Value),
}
o := vm.NewDynamicObject(dynObj)
vm.Set("o", o)
vm.testScriptWithTestLibX(`
assert(o instanceof Object, "instanceof Object");
assert(o === o, "self equality");
assert(o !== {}, "non-equality");
o.test = 42;
assert("test" in o, "'test' in o");
assert(deepEqual(Object.getOwnPropertyDescriptor(o, "test"), {value: 42, writable: true, enumerable: true, configurable: true}), "prop desc");
assert.throws(TypeError, function() {
"use strict";
Object.defineProperty(o, "test1", {value: 0, writable: false, enumerable: false, configurable: true});
}, "define prop");
var keys = [];
for (var key in o) {
keys.push(key);
}
assert(compareArray(keys, ["test"]), "for-in");
assert(delete o.test, "delete");
assert(!("test" in o), "'test' in o after delete");
assert("__proto__" in o, "__proto__ in o");
assert.sameValue(o.__proto__, Object.prototype, "__proto__");
o.__proto__ = null;
assert(!("__proto__" in o), "__proto__ in o after setting to null");
`, _undefined, t)
}
func TestDynamicObjectCustomProto(t *testing.T) {
vm := New()
m := make(map[string]Value)
dynObj := &testDynObject{
r: vm,
m: m,
}
o := vm.NewDynamicObject(dynObj)
vm.Set("o", o)
vm.testScriptWithTestLib(`
var proto = {
valueOf: function() {
return this.num;
}
};
proto[Symbol.toStringTag] = "GoObject";
Object.setPrototypeOf(o, proto);
o.num = 41;
assert(o instanceof Object, "instanceof");
assert.sameValue(o+1, 42);
assert.sameValue(o.toString(), "[object GoObject]");
`, _undefined, t)
if v := m["num"]; v.Export() != int64(41) {
t.Fatal(v)
}
}
func TestDynamicArray(t *testing.T) {
vm := New()
dynObj := &testDynArray{
r: vm,
}
a := vm.NewDynamicArray(dynObj)
vm.Set("a", a)
vm.testScriptWithTestLibX(`
assert(a instanceof Array, "instanceof Array");
assert(a instanceof Object, "instanceof Object");
assert(a === a, "self equality");
assert(a !== [], "non-equality");
assert(Array.isArray(a), "isArray()");
assert("length" in a, "length in a");
assert.sameValue(a.length, 0, "len == 0");
assert.sameValue(a[0], undefined, "a[0] (1)");
a[0] = 0;
assert.sameValue(a[0], 0, "a[0] (2)");
assert.sameValue(a.length, 1, "length");
assert(deepEqual(Object.getOwnPropertyDescriptor(a, 0), {value: 0, writable: true, enumerable: true, configurable: true}), "prop desc");
assert(deepEqual(Object.getOwnPropertyDescriptor(a, "length"), {value: 1, writable: true, enumerable: false, configurable: false}), "length prop desc");
assert("__proto__" in a, "__proto__ in a");
assert.sameValue(a.__proto__, Array.prototype, "__proto__");
assert(compareArray(Object.keys(a), ["0"]), "Object.keys()");
assert(compareArray(Reflect.ownKeys(a), ["0", "length"]), "Reflect.ownKeys()");
a.length = 2;
assert.sameValue(a.length, 2, "length after grow");
assert.sameValue(a[1], undefined, "a[1]");
a[1] = 1;
assert.sameValue(a[1], 1, "a[1] after set");
a.length = 1;
assert.sameValue(a.length, 1, "length after shrink");
assert.sameValue(a[1], undefined, "a[1] after shrink");
a.length = 2;
assert.sameValue(a.length, 2, "length after shrink and grow");
assert.sameValue(a[1], undefined, "a[1] after grow");
a[0] = 3; a[1] = 1; a[2] = 2;
assert.sameValue(a.length, 3);
var keys = [];
for (var key in a) {
keys.push(key);
}
assert(compareArray(keys, ["0","1","2"]), "for-in");
var vals = [];
for (var val of a) {
vals.push(val);
}
assert(compareArray(vals, [3,1,2]), "for-of");
a.sort();
assert(compareArray(a, [1,2,3]), "sort: "+a);
assert.sameValue(a[-1], 3);
assert.sameValue(a[-4], undefined);
assert.throws(TypeError, function() {
"use strict";
delete a.length;
}, "delete length");
assert.throws(TypeError, function() {
"use strict";
a.test = true;
}, "set string prop");
assert.throws(TypeError, function() {
"use strict";
Object.defineProperty(a, 0, {value: 0, writable: false, enumerable: false, configurable: true});
}, "define prop");
`, _undefined, t)
}
type testSharedDynObject struct {
sync.RWMutex
m map[string]Value
}
func (t *testSharedDynObject) Get(key string) Value {
t.RLock()
val := t.m[key]
t.RUnlock()
return val
}
func (t *testSharedDynObject) Set(key string, val Value) bool {
t.Lock()
t.m[key] = val
t.Unlock()
return true
}
func (t *testSharedDynObject) Has(key string) bool {
t.RLock()
_, exists := t.m[key]
t.RUnlock()
return exists
}
func (t *testSharedDynObject) Delete(key string) bool {
t.Lock()
delete(t.m, key)
t.Unlock()
return true
}
func (t *testSharedDynObject) Keys() []string {
t.RLock()
keys := make([]string, 0, len(t.m))
for k := range t.m {
keys = append(keys, k)
}
t.RUnlock()
return keys
}
func TestSharedDynamicObject(t *testing.T) {
dynObj := &testSharedDynObject{m: make(map[string]Value, 10000)}
o := NewSharedDynamicObject(dynObj)
ch := make(chan error, 1)
go func() {
vm := New()
vm.Set("o", o)
_, err := vm.RunString(`
for (let i = 0; i < 10000; i++) {
o[i] = i;
}
`)
ch <- err
}()
vm := New()
vm.Set("o", o)
_, err := vm.RunString(`
for (let i = 0; i < 10000; i++) {
o[i] = i+1;
}
`)
if err != nil {
t.Fatal(err)
}
err = <-ch
if err != nil {
t.Fatal(err)
}
}
type testSharedDynArray struct {
sync.RWMutex
a []Value
}
func (t *testSharedDynArray) Len() int {
t.RLock()
l := len(t.a)
t.RUnlock()
return l
}
func (t *testSharedDynArray) Get(idx int) Value {
t.RLock()
defer t.RUnlock()
if idx < 0 {
idx += len(t.a)
}
if idx >= 0 && idx < len(t.a) {
return t.a[idx]
}
return nil
}
func (t *testSharedDynArray) expand(newLen int) {
if newLen > cap(t.a) {
a := make([]Value, newLen)
copy(a, t.a)
t.a = a
} else {
t.a = t.a[:newLen]
}
}
func (t *testSharedDynArray) Set(idx int, val Value) bool {
t.Lock()
defer t.Unlock()
if idx < 0 {
idx += len(t.a)
}
if idx < 0 {
return false
}
if idx >= len(t.a) {
t.expand(idx + 1)
}
t.a[idx] = val
return true
}
func (t *testSharedDynArray) SetLen(i int) bool {
t.Lock()
defer t.Unlock()
if i > len(t.a) {
t.expand(i)
return true
}
if i < 0 {
return false
}
if i < len(t.a) {
tail := t.a[i:len(t.a)]
for j := range tail {
tail[j] = nil
}
t.a = t.a[:i]
}
return true
}
func TestSharedDynamicArray(t *testing.T) {
dynObj := &testSharedDynArray{a: make([]Value, 10000)}
o := NewSharedDynamicArray(dynObj)
ch := make(chan error, 1)
go func() {
vm := New()
vm.Set("o", o)
_, err := vm.RunString(`
for (let i = 0; i < 10000; i++) {
o[i] = i;
}
`)
ch <- err
}()
vm := New()
vm.Set("o", o)
_, err := vm.RunString(`
for (let i = 0; i < 10000; i++) {
o[i] = i+1;
}
`)
if err != nil {
t.Fatal(err)
}
err = <-ch
if err != nil {
t.Fatal(err)
}
}

View File

@ -0,0 +1,304 @@
package goja
import (
"testing"
)
func TestGoReflectArray(t *testing.T) {
vm := New()
vm.Set("a", [...]int{1, 2, 3})
_, err := vm.RunString(`
if (!Array.isArray(a)) {
throw new Error("isArray() returned false");
}
if (a[0] !== 1 || a[1] !== 2 || a[2] !== 3) {
throw new Error("Array contents is incorrect");
}
if (!a.hasOwnProperty("length")) {
throw new Error("hasOwnProperty() returned false");
}
let desc = Object.getOwnPropertyDescriptor(a, "length");
if (desc.value !== 3 || desc.writable || desc.enumerable || desc.configurable) {
throw new Error("incorrect property descriptor: " + JSON.stringify(desc));
}
`)
if err != nil {
t.Fatal(err)
}
}
func TestGoReflectArraySort(t *testing.T) {
vm := New()
vm.Set("a", [...]int{3, 1, 2})
v, err := vm.RunString(`
a.sort();
if (a[0] !== 1 || a[1] !== 2 || a[2] !== 3) {
throw new Error(a.toString());
}
a;
`)
if err != nil {
t.Fatal(err)
}
res := v.Export()
if a, ok := res.([3]int); ok {
if a[0] != 1 || a[1] != 2 || a[2] != 3 {
t.Fatal(a)
}
} else {
t.Fatalf("Wrong type: %T", res)
}
}
func TestGoReflectArrayCopyOnChange(t *testing.T) {
vm := New()
v, err := vm.RunString(`
a => {
let tmp = a[0];
if (tmp !== a[0]) {
throw new Error("tmp !== a[0]");
}
a[0] = a[1];
if (tmp === a[0]) {
throw new Error("tmp === a[0]");
}
if (tmp.Test !== "1") {
throw new Error("tmp.Test: " + tmp.Test + " (" + typeof tmp.Test + ")");
}
if (a[0].Test !== "2") {
throw new Error("a[0].Test: " + a[0].Test);
}
a[0].Test = "3";
if (a[0].Test !== "3") {
throw new Error("a[0].Test (1): " + a[0].Test);
}
tmp = a[0];
tmp.Test = "4";
if (a[0].Test !== "4") {
throw new Error("a[0].Test (2): " + a[0].Test);
}
delete a[0];
if (a[0] && a[0].Test !== "") {
throw new Error("a[0].Test (3): " + a[0].Test);
}
if (tmp.Test !== "4") {
throw new Error("tmp.Test (1): " + tmp.Test);
}
a[1] = tmp;
if (a[1].Test !== "4") {
throw new Error("a[1].Test: " + a[1].Test);
}
// grow
tmp = a[1];
a.push(null);
if (a.length !== 3) {
throw new Error("a.length after push: " + a.length);
}
tmp.Test = "5";
if (a[1].Test !== "5") {
throw new Error("a[1].Test (1): " + a[1].Test);
}
// shrink
a.length = 1;
if (a.length !== 1) {
throw new Error("a.length after shrink: " + a.length);
}
if (tmp.Test !== "5") {
throw new Error("tmp.Test (shrink): " + tmp.Test);
}
}
`)
if err != nil {
t.Fatal(err)
}
fn, ok := AssertFunction(v)
if !ok {
t.Fatal("Not a function")
}
t.Run("[]struct", func(t *testing.T) {
a := []struct {
Test string
}{{"1"}, {"2"}}
_, err := fn(nil, vm.ToValue(a))
if err != nil {
t.Fatal(err)
}
if a[0].Test != "" {
t.Fatalf("a[0]: %#v", a[0])
}
if a[1].Test != "4" {
t.Fatalf("a0[1]: %#v", a[1])
}
})
// The copy-on-change mechanism doesn't apply to the types below because the contained values are references.
// These tests are here for completeness and to prove that the behaviour is consistent.
t.Run("[]I", func(t *testing.T) {
type I interface {
Get() string
}
a := []I{&testGoReflectMethod_O{Test: "1"}, &testGoReflectMethod_O{Test: "2"}}
_, err = fn(nil, vm.ToValue(a))
if err != nil {
t.Fatal(err)
}
})
t.Run("[]interface{}", func(t *testing.T) {
a := []interface{}{&testGoReflectMethod_O{Test: "1"}, &testGoReflectMethod_O{Test: "2"}}
_, err = fn(nil, vm.ToValue(a))
if err != nil {
t.Fatal(err)
}
})
}
func TestCopyOnChangeReflectSlice(t *testing.T) {
vm := New()
v, err := vm.RunString(`
s => {
s.A.push(1);
if (s.A.length !== 1) {
throw new Error("s.A.length: " + s.A.length);
}
if (s.A[0] !== 1) {
throw new Error("s.A[0]: " + s.A[0]);
}
let tmp = s.A;
if (tmp !== s.A) {
throw new Error("tmp !== s.A");
}
s.A = [2];
if (tmp === s.A) {
throw new Error("tmp === s.A");
}
if (tmp[0] !== 1) {
throw new Error("tmp[0]: " + tmp[0]);
}
if (s.A[0] !== 2) {
throw new Error("s.A[0] (1): " + s.A[0]);
}
}
`)
if err != nil {
t.Fatal(err)
}
fn, ok := AssertFunction(v)
if !ok {
t.Fatal("Not a function")
}
t.Run("[]int", func(t *testing.T) {
type S struct {
A []int
}
var s S
_, err := fn(nil, vm.ToValue(&s))
if err != nil {
t.Fatal(err)
}
if len(s.A) != 1 {
t.Fatal(s)
}
if s.A[0] != 2 {
t.Fatal(s.A)
}
})
t.Run("[]interface{}", func(t *testing.T) {
type S struct {
A []interface{}
}
var s S
_, err := fn(nil, vm.ToValue(&s))
if err != nil {
t.Fatal(err)
}
if len(s.A) != 1 {
t.Fatal(s)
}
if s.A[0] != int64(2) {
t.Fatal(s.A)
}
})
}
func TestCopyOnChangeSort(t *testing.T) {
a := []struct {
Test string
}{{"2"}, {"1"}}
vm := New()
vm.Set("a", &a)
_, err := vm.RunString(`
let a0 = a[0];
let a1 = a[1];
a.sort((a, b) => a.Test.localeCompare(b.Test));
if (a[0].Test !== "1") {
throw new Error("a[0]: " + a[0]);
}
if (a[1].Test !== "2") {
throw new Error("a[1]: " + a[1]);
}
if (a0 !== a[1]) {
throw new Error("a0 !== a[1]");
}
if (a1 !== a[0]) {
throw new Error("a1 !== a[0]");
}
`)
if err != nil {
t.Fatal(err)
}
if a[0].Test != "1" || a[1].Test != "2" {
t.Fatal(a)
}
}
type testStringerArray [8]byte
func (a testStringerArray) String() string {
return "X"
}
func TestReflectArrayToString(t *testing.T) {
vm := New()
var a testStringerArray
vm.Set("a", &a)
res, err := vm.RunString("`${a}`")
if err != nil {
t.Fatal(err)
}
if exp := res.Export(); exp != "X" {
t.Fatal(exp)
}
var a1 [2]byte
vm.Set("a", &a1)
res, err = vm.RunString("`${a}`")
if err != nil {
t.Fatal(err)
}
if exp := res.Export(); exp != "0,0" {
t.Fatal(exp)
}
}

View File

@ -0,0 +1,350 @@
package goja
import (
"sort"
"strings"
"testing"
)
func TestGoMapReflectGetSet(t *testing.T) {
const SCRIPT = `
m.c = m.a + m.b;
`
vm := New()
m := map[string]string{
"a": "4",
"b": "2",
}
vm.Set("m", m)
_, err := vm.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
if c := m["c"]; c != "42" {
t.Fatalf("Unexpected value: '%s'", c)
}
}
func TestGoMapReflectIntKey(t *testing.T) {
const SCRIPT = `
m[2] = m[0] + m[1];
`
vm := New()
m := map[int]int{
0: 40,
1: 2,
}
vm.Set("m", m)
_, err := vm.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
if c := m[2]; c != 42 {
t.Fatalf("Unexpected value: '%d'", c)
}
}
func TestGoMapReflectDelete(t *testing.T) {
const SCRIPT = `
delete m.a;
`
vm := New()
m := map[string]string{
"a": "4",
"b": "2",
}
vm.Set("m", m)
_, err := vm.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
if _, exists := m["a"]; exists {
t.Fatal("a still exists")
}
if b := m["b"]; b != "2" {
t.Fatalf("Unexpected b: '%s'", b)
}
}
func TestGoMapReflectJSON(t *testing.T) {
const SCRIPT = `
function f(m) {
return JSON.stringify(m);
}
`
vm := New()
m := map[string]string{
"t": "42",
}
_, err := vm.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
f := vm.Get("f")
if call, ok := AssertFunction(f); ok {
v, err := call(nil, ([]Value{vm.ToValue(m)})...)
if err != nil {
t.Fatal(err)
}
if !v.StrictEquals(asciiString(`{"t":"42"}`)) {
t.Fatalf("Unexpected value: %v", v)
}
} else {
t.Fatalf("Not a function: %v", f)
}
}
func TestGoMapReflectProto(t *testing.T) {
const SCRIPT = `
m.hasOwnProperty("t");
`
vm := New()
m := map[string]string{
"t": "42",
}
vm.Set("m", m)
v, err := vm.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
if !v.StrictEquals(valueTrue) {
t.Fatalf("Expected true, got %v", v)
}
}
type gomapReflect_noMethods map[string]interface{}
type gomapReflect_withMethods map[string]interface{}
func (m gomapReflect_withMethods) Method() bool {
return true
}
func TestGoMapReflectNoMethods(t *testing.T) {
const SCRIPT = `
typeof m === "object" && m.hasOwnProperty("t") && m.t === 42;
`
vm := New()
m := make(gomapReflect_noMethods)
m["t"] = 42
vm.Set("m", m)
v, err := vm.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
if !v.StrictEquals(valueTrue) {
t.Fatalf("Expected true, got %v", v)
}
}
func TestGoMapReflectWithMethods(t *testing.T) {
const SCRIPT = `
typeof m === "object" && !m.hasOwnProperty("t") && m.hasOwnProperty("Method") && m.Method();
`
vm := New()
m := make(gomapReflect_withMethods)
m["t"] = 42
vm.Set("m", m)
v, err := vm.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
if !v.StrictEquals(valueTrue) {
t.Fatalf("Expected true, got %v", v)
}
}
func TestGoMapReflectWithProto(t *testing.T) {
vm := New()
m := map[string]string{
"t": "42",
}
vm.Set("m", m)
vm.testScriptWithTestLib(`
(function() {
'use strict';
var proto = {};
var getterAllowed = false;
var setterAllowed = false;
var tHolder = "proto t";
Object.defineProperty(proto, "t", {
get: function() {
if (!getterAllowed) throw new Error("getter is called");
return tHolder;
},
set: function(v) {
if (!setterAllowed) throw new Error("setter is called");
tHolder = v;
}
});
var t1Holder;
Object.defineProperty(proto, "t1", {
get: function() {
return t1Holder;
},
set: function(v) {
t1Holder = v;
}
});
Object.setPrototypeOf(m, proto);
assert.sameValue(m.t, "42");
m.t = 43;
assert.sameValue(m.t, "43");
t1Holder = "test";
assert.sameValue(m.t1, "test");
m.t1 = "test1";
assert.sameValue(m.t1, "test1");
delete m.t;
getterAllowed = true;
assert.sameValue(m.t, "proto t", "after delete");
setterAllowed = true;
m.t = true;
assert.sameValue(m.t, true, "m.t === true");
assert.sameValue(tHolder, true, "tHolder === true");
Object.preventExtensions(m);
assert.throws(TypeError, function() {
m.t2 = 1;
});
m.t1 = "test2";
assert.sameValue(m.t1, "test2");
})();
`, _undefined, t)
}
func TestGoMapReflectProtoProp(t *testing.T) {
const SCRIPT = `
(function() {
"use strict";
var proto = {};
Object.defineProperty(proto, "ro", {value: 42});
Object.setPrototypeOf(m, proto);
assert.throws(TypeError, function() {
m.ro = 43;
});
Object.defineProperty(m, "ro", {value: 43});
assert.sameValue(m.ro, "43");
})();
`
r := New()
r.Set("m", map[string]string{})
r.testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestGoMapReflectUnicode(t *testing.T) {
const SCRIPT = `
Object.setPrototypeOf(m, s);
if (m.Тест !== "passed") {
throw new Error("m.Тест: " + m.Тест);
}
m["é"];
`
type S struct {
Тест string
}
vm := New()
m := map[string]int{
"é": 42,
}
s := S{
Тест: "passed",
}
vm.Set("m", m)
vm.Set("s", &s)
res, err := vm.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
if res == nil || !res.StrictEquals(valueInt(42)) {
t.Fatalf("Unexpected value: %v", res)
}
}
func TestGoMapReflectStruct(t *testing.T) {
type S struct {
Test int
}
m := map[string]S{
"1": {Test: 1},
}
vm := New()
vm.Set("m", m)
res, err := vm.RunString("m[1].Test = 2; m[1].Test")
if err != nil {
t.Fatal(err)
}
if res.Export() != int64(1) {
t.Fatal(res)
}
}
func TestGoMapReflectElt(t *testing.T) {
type mapping map[string]interface{}
const SCRIPT = `a.s() && a.t === null && a.t1 === undefined;`
r := New()
r.Set("a", mapping{
"s": func() bool { return true },
"t": nil,
})
r.testScript(SCRIPT, valueTrue, t)
}
func TestGoMapReflectKeyToString(t *testing.T) {
vm := New()
test := func(v any, t *testing.T) {
o1 := vm.ToValue(v).ToObject(vm)
keys := o1.Keys()
sort.Strings(keys)
if len(keys) != 2 || keys[0] != "1" || keys[1] != "2" {
t.Fatal(keys)
}
keys1 := o1.self.stringKeys(true, nil)
sort.Slice(keys1, func(a, b int) bool {
return strings.Compare(keys1[a].String(), keys1[b].String()) < 0
})
if len(keys1) != 2 || keys1[0] != asciiString("1") || keys1[1] != asciiString("2") {
t.Fatal(keys1)
}
}
t.Run("int", func(t *testing.T) {
m1 := map[int]any{
1: 2,
2: 3,
}
test(m1, t)
})
t.Run("CustomString", func(t *testing.T) {
type CustomString string
m2 := map[CustomString]any{
"1": 2,
"2": 3,
}
test(m2, t)
})
}

328
goja/object_gomap_test.go Normal file
View File

@ -0,0 +1,328 @@
package goja
import "testing"
func TestGomapProp(t *testing.T) {
const SCRIPT = `
o.a + o.b;
`
r := New()
r.Set("o", map[string]interface{}{
"a": 40,
"b": 2,
})
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 TestGomapEnumerate(t *testing.T) {
const SCRIPT = `
var hasX = false;
var hasY = false;
for (var key in o) {
switch (key) {
case "x":
if (hasX) {
throw "Already have x";
}
hasX = true;
break;
case "y":
if (hasY) {
throw "Already have y";
}
hasY = true;
break;
default:
throw "Unexpected property: " + key;
}
}
hasX && hasY;
`
r := New()
r.Set("o", map[string]interface{}{
"x": 40,
"y": 2,
})
v, err := r.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
if !v.StrictEquals(valueTrue) {
t.Fatalf("Expected true, got %v", v)
}
}
func TestGomapDeleteWhileEnumerate(t *testing.T) {
const SCRIPT = `
var hasX = false;
var hasY = false;
for (var key in o) {
switch (key) {
case "x":
if (hasX) {
throw "Already have x";
}
hasX = true;
delete o.y;
break;
case "y":
if (hasY) {
throw "Already have y";
}
hasY = true;
delete o.x;
break;
default:
throw "Unexpected property: " + key;
}
}
hasX && !hasY || hasY && !hasX;
`
r := New()
r.Set("o", map[string]interface{}{
"x": 40,
"y": 2,
})
v, err := r.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
if !v.StrictEquals(valueTrue) {
t.Fatalf("Expected true, got %v", v)
}
}
func TestGomapInstanceOf(t *testing.T) {
const SCRIPT = `
(o instanceof Object) && !(o instanceof Error);
`
r := New()
r.Set("o", map[string]interface{}{})
v, err := r.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
if !v.StrictEquals(valueTrue) {
t.Fatalf("Expected true, got %v", v)
}
}
func TestGomapTypeOf(t *testing.T) {
const SCRIPT = `
typeof o;
`
r := New()
r.Set("o", map[string]interface{}{})
v, err := r.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
if !v.StrictEquals(asciiString("object")) {
t.Fatalf("Expected object, got %v", v)
}
}
func TestGomapProto(t *testing.T) {
const SCRIPT = `
o.hasOwnProperty("test");
`
r := New()
r.Set("o", map[string]interface{}{
"test": 42,
})
v, err := r.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
if !v.StrictEquals(valueTrue) {
t.Fatalf("Expected true, got %v", v)
}
}
func TestGoMapExtensibility(t *testing.T) {
const SCRIPT = `
"use strict";
o.test = 42;
Object.preventExtensions(o);
o.test = 43;
try {
o.test1 = 42;
} catch (e) {
if (!(e instanceof TypeError)) {
throw e;
}
}
o.test === 43 && o.test1 === undefined;
`
r := New()
r.Set("o", map[string]interface{}{})
v, err := r.RunString(SCRIPT)
if err != nil {
if ex, ok := err.(*Exception); ok {
t.Fatal(ex.String())
} else {
t.Fatal(err)
}
}
if !v.StrictEquals(valueTrue) {
t.Fatalf("Expected true, got %v", v)
}
}
func TestGoMapWithProto(t *testing.T) {
vm := New()
m := map[string]interface{}{
"t": "42",
}
vm.Set("m", m)
vm.testScriptWithTestLib(`
(function() {
'use strict';
var proto = {};
var getterAllowed = false;
var setterAllowed = false;
var tHolder = "proto t";
Object.defineProperty(proto, "t", {
get: function() {
if (!getterAllowed) throw new Error("getter is called");
return tHolder;
},
set: function(v) {
if (!setterAllowed) throw new Error("setter is called");
tHolder = v;
}
});
var t1Holder;
Object.defineProperty(proto, "t1", {
get: function() {
return t1Holder;
},
set: function(v) {
t1Holder = v;
}
});
Object.setPrototypeOf(m, proto);
assert.sameValue(m.t, "42");
m.t = 43;
assert.sameValue(m.t, 43);
t1Holder = "test";
assert.sameValue(m.t1, "test");
m.t1 = "test1";
assert.sameValue(m.t1, "test1");
delete m.t;
getterAllowed = true;
assert.sameValue(m.t, "proto t", "after delete");
setterAllowed = true;
m.t = true;
assert.sameValue(m.t, true);
assert.sameValue(tHolder, true);
Object.preventExtensions(m);
assert.throws(TypeError, function() {
m.t2 = 1;
});
m.t1 = "test2";
assert.sameValue(m.t1, "test2");
})();
`, _undefined, t)
}
func TestGoMapProtoProp(t *testing.T) {
const SCRIPT = `
(function() {
"use strict";
var proto = {};
Object.defineProperty(proto, "ro", {value: 42});
Object.setPrototypeOf(m, proto);
assert.throws(TypeError, function() {
m.ro = 43;
});
Object.defineProperty(m, "ro", {value: 43});
assert.sameValue(m.ro, 43);
})();
`
r := New()
r.Set("m", map[string]interface{}{})
r.testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestGoMapProtoPropChain(t *testing.T) {
const SCRIPT = `
(function() {
"use strict";
var p1 = Object.create(null);
m.__proto__ = p1;
Object.defineProperty(p1, "test", {
value: 42
});
Object.defineProperty(m, "test", {
value: 43,
writable: true,
});
var o = Object.create(m);
o.test = 44;
assert.sameValue(o.test, 44);
var sym = Symbol(true);
Object.defineProperty(p1, sym, {
value: 42
});
Object.defineProperty(m, sym, {
value: 43,
writable: true,
});
o[sym] = 44;
assert.sameValue(o[sym], 44);
})();
`
r := New()
r.Set("m", map[string]interface{}{})
r.testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestGoMapUnicode(t *testing.T) {
const SCRIPT = `
Object.setPrototypeOf(m, s);
if (m.Тест !== "passed") {
throw new Error("m.Тест: " + m.Тест);
}
m["é"];
`
type S struct {
Тест string
}
vm := New()
m := map[string]interface{}{
"é": 42,
}
s := S{
Тест: "passed",
}
vm.Set("m", m)
vm.Set("s", &s)
res, err := vm.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
if res == nil || !res.StrictEquals(valueInt(42)) {
t.Fatalf("Unexpected value: %v", res)
}
}

View File

@ -221,6 +221,9 @@ func (o *objectGoReflect) _getMethod(jsName string) reflect.Value {
func (o *objectGoReflect) elemToValue(ev reflect.Value) (Value, reflectValueWrapper) {
if isContainer(ev.Kind()) {
if ev.CanAddr() {
ev = ev.Addr()
}
ret := o.val.runtime.toValue(ev.Interface(), ev)
if obj, ok := ret.(*Object); ok {
if w, ok := obj.self.(reflectValueWrapper); ok {

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,520 @@
package goja
import (
"reflect"
"testing"
)
func TestGoSliceReflectBasic(t *testing.T) {
const SCRIPT = `
var sum = 0;
for (var i = 0; i < a.length; i++) {
sum += a[i];
}
sum;
`
r := New()
r.Set("a", []int{1, 2, 3, 4})
v, err := r.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
if i := v.ToInteger(); i != 10 {
t.Fatalf("Expected 10, got: %d", i)
}
}
func TestGoSliceReflectIn(t *testing.T) {
const SCRIPT = `
var idx = "";
for (var i in a) {
idx += i;
}
idx;
`
r := New()
r.Set("a", []int{1, 2, 3, 4})
v, err := r.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
if i := v.String(); i != "0123" {
t.Fatalf("Expected '0123', got: '%s'", i)
}
}
func TestGoSliceReflectSet(t *testing.T) {
const SCRIPT = `
a[0] = 33;
a[1] = 333;
a[2] = "42";
a[3] = {};
a[4] = 0;
`
r := New()
a := []int8{1, 2, 3, 4}
r.Set("a", a)
_, err := r.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
if a[0] != 33 {
t.Fatalf("a[0] = %d, expected 33", a[0])
}
if a[1] != 77 {
t.Fatalf("a[1] = %d, expected 77", a[1])
}
if a[2] != 42 {
t.Fatalf("a[2] = %d, expected 42", a[2])
}
if a[3] != 0 {
t.Fatalf("a[3] = %d, expected 0", a[3])
}
}
func TestGoSliceReflectPush(t *testing.T) {
r := New()
t.Run("Can push to array by array ptr", func(t *testing.T) {
a := []int8{1}
r.Set("a", &a)
_, err := r.RunString(`a.push (10)`)
if err != nil {
t.Fatal(err)
}
if a[1] != 10 {
t.Fatalf("a[1] = %d, expected 10", a[1])
}
})
t.Run("Can push to array by struct ptr", func(t *testing.T) {
type testStr struct {
A []int
}
a := testStr{
A: []int{2},
}
r.Set("a", &a)
_, err := r.RunString(`a.A.push (10)`)
if err != nil {
t.Fatal(err)
}
if a.A[1] != 10 {
t.Fatalf("a[1] = %v, expected 10", a)
}
})
}
func TestGoSliceReflectStructField(t *testing.T) {
vm := New()
var s struct {
A []int
B *[]int
}
vm.Set("s", &s)
_, err := vm.RunString(`
'use strict';
s.A.push(1);
if (s.B !== null) {
throw new Error("s.B is not null: " + s.B);
}
s.B = [2];
`)
if err != nil {
t.Fatal(err)
}
if len(s.A) != 1 || s.A[0] != 1 {
t.Fatalf("s.A: %v", s.A)
}
if len(*s.B) != 1 || (*s.B)[0] != 2 {
t.Fatalf("s.B: %v", *s.B)
}
}
func TestGoSliceReflectExportToStructField(t *testing.T) {
vm := New()
v, err := vm.RunString(`({A: [1], B: [2]})`)
if err != nil {
t.Fatal(err)
}
var s struct {
A []int
B *[]int
}
err = vm.ExportTo(v, &s)
if err != nil {
t.Fatal(err)
}
if len(s.A) != 1 || s.A[0] != 1 {
t.Fatalf("s.A: %v", s.A)
}
if len(*s.B) != 1 || (*s.B)[0] != 2 {
t.Fatalf("s.B: %v", *s.B)
}
}
func TestGoSliceReflectProtoMethod(t *testing.T) {
const SCRIPT = `
a.join(",")
`
r := New()
a := []int8{1, 2, 3, 4}
r.Set("a", a)
ret, err := r.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
if s := ret.String(); s != "1,2,3,4" {
t.Fatalf("Unexpected result: '%s'", s)
}
}
type gosliceReflect_withMethods []interface{}
func (s gosliceReflect_withMethods) Method() bool {
return true
}
func TestGoSliceReflectMethod(t *testing.T) {
const SCRIPT = `
typeof a === "object" && a[0] === 42 && a.Method() === true;
`
vm := New()
a := make(gosliceReflect_withMethods, 1)
a[0] = 42
vm.Set("a", a)
v, err := vm.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
if !v.StrictEquals(valueTrue) {
t.Fatalf("Expected true, got %v", v)
}
}
func TestGoSliceReflectGetStr(t *testing.T) {
r := New()
v := r.ToValue([]string{"test"})
if o, ok := v.(*Object); ok {
if e := o.Get("0").Export(); e != "test" {
t.Fatalf("Unexpected o.Get(\"0\"): %v", e)
}
}
}
func TestGoSliceReflectNilObjectIfaceVal(t *testing.T) {
r := New()
a := []Value{(*Object)(nil)}
r.Set("a", a)
ret, err := r.RunString(`
""+a[0];
`)
if err != nil {
t.Fatal(err)
}
if !asciiString("null").SameAs(ret) {
t.Fatalf("ret: %v", ret)
}
}
func TestGoSliceReflectSetLength(t *testing.T) {
r := New()
a := []int{1, 2, 3, 4}
b := []testing.TB{&testing.T{}, &testing.T{}, (*testing.T)(nil)}
r.Set("a", &a)
r.Set("b", &b)
_, err := r.RunString(`
'use strict';
a.length = 3;
if (a.length !== 3) {
throw new Error("length="+a.length);
}
if (a[3] !== undefined) {
throw new Error("a[3]="+a[3]);
}
a.length = 5;
if (a.length !== 5) {
throw new Error("a.length="+a.length);
}
if (a[3] !== 0) {
throw new Error("a[3]="+a[3]);
}
if (a[4] !== 0) {
throw new Error("a[4]="+a[4]);
}
b.length = 3;
if (b.length !== 3) {
throw new Error("b.length="+b.length);
}
if (b[3] !== undefined) {
throw new Error("b[3]="+b[3]);
}
b.length = 5;
if (b.length !== 5) {
throw new Error("length="+b.length);
}
if (b[3] !== null) {
throw new Error("b[3]="+b[3]);
}
if (b[4] !== null) {
throw new Error("b[4]="+b[4]);
}
if (b[2] !== null) {
throw new Error("b[2]="+b[2]);
}
`)
if err != nil {
t.Fatal(err)
}
}
func TestGoSliceReflectProto(t *testing.T) {
r := New()
a := []*Object{{}, nil, {}}
r.Set("a", &a)
r.testScriptWithTestLib(`
var proto = [,2,,4];
Object.setPrototypeOf(a, proto);
assert.sameValue(a[1], null, "a[1]");
assert.sameValue(a[3], 4, "a[3]");
var desc = Object.getOwnPropertyDescriptor(a, "1");
assert.sameValue(desc.value, null, "desc.value");
assert(desc.writable, "writable");
assert(desc.enumerable, "enumerable");
assert(!desc.configurable, "configurable");
var v5;
Object.defineProperty(proto, "5", {
set: function(v) {
v5 = v;
}
});
a[5] = "test";
assert.sameValue(v5, "test", "v5");
`, _undefined, t)
}
func TestGoSliceReflectProtoProto(t *testing.T) {
r := New()
a := []*Object{{}, nil, {}}
proto := []*Object{{}, {}, {}, {}}
r.Set("a", &a)
r.Set("proto", proto)
_, err := r.RunString(`
"use strict";
var protoproto = {};
Object.defineProperty(protoproto, "3", {
value: 42
});
Object.setPrototypeOf(proto, protoproto);
Object.setPrototypeOf(a, proto);
if (a.hasOwnProperty("3")) {
throw new Error("a.hasOwnProperty(\"3\")");
}
if (a[3] !== null) {
throw new Error("a[3]="+a[3]);
}
a[3] = null;
if (a[3] !== null) {
throw new Error("a[3]=" + a[3]);
}
`)
if err != nil {
t.Fatal(err)
}
}
func TestGoSliceReflectDelete(t *testing.T) {
r := New()
a := []*Object{{}, nil, {}}
r.Set("a", a)
v, err := r.RunString(`
delete a[0] && delete a[1] && delete a[3];
`)
if err != nil {
t.Fatal(err)
}
if v != valueTrue {
t.Fatalf("not true: %v", v)
}
}
func TestGoSliceReflectPop(t *testing.T) {
r := New()
a := []string{"1", "", "3"}
r.Set("a", &a)
v, err := r.RunString(`
a.pop()
`)
if err != nil {
t.Fatal(err)
}
if !v.SameAs(asciiString("3")) {
t.Fatal(v)
}
}
func TestGoSliceReflectPopNoPtr(t *testing.T) {
r := New()
a := []string{"1", "", "3"}
r.Set("a", a)
v, err := r.RunString(`
a.pop()
`)
if err != nil {
t.Fatal(err)
}
if !v.SameAs(asciiString("3")) {
t.Fatal(v)
}
}
func TestGoSliceReflectLengthProperty(t *testing.T) {
vm := New()
vm.Set("s", []int{2, 3, 4})
_, err := vm.RunString(`
if (!s.hasOwnProperty("length")) {
throw new Error("hasOwnProperty() returned false");
}
let desc = Object.getOwnPropertyDescriptor(s, "length");
if (desc.value !== 3 || !desc.writable || desc.enumerable || desc.configurable) {
throw new Error("incorrect property descriptor: " + JSON.stringify(desc));
}
`)
if err != nil {
t.Fatal(err)
}
}
type testCustomSliceWithMethods []int
func (a testCustomSliceWithMethods) Method() bool {
return true
}
func TestGoSliceReflectMethods(t *testing.T) {
vm := New()
vm.Set("s", testCustomSliceWithMethods{1, 2, 3})
_, err := vm.RunString(`
if (!s.hasOwnProperty("Method")) {
throw new Error("hasOwnProperty() returned false");
}
let desc = Object.getOwnPropertyDescriptor(s, "Method");
if (desc.value() !== true || desc.writable || !desc.enumerable || desc.configurable) {
throw new Error("incorrect property descriptor: " + JSON.stringify(desc));
}
`)
if err != nil {
t.Fatal(err)
}
}
func TestGoSliceReflectExportAfterGrow(t *testing.T) {
vm := New()
vm.Set("a", []int{1})
v, err := vm.RunString(`
a.push(2);
a;
`)
if err != nil {
t.Fatal(err)
}
exp := v.Export()
if a, ok := exp.([]int); ok {
if len(a) != 2 || a[0] != 1 || a[1] != 2 {
t.Fatal(a)
}
} else {
t.Fatalf("Wrong type: %T", exp)
}
}
func TestGoSliceReflectSort(t *testing.T) {
vm := New()
type Thing struct{ Name string }
vm.Set("v", []*Thing{
{Name: "log"},
{Name: "etc"},
{Name: "test"},
{Name: "bin"},
})
ret, err := vm.RunString(`
//v.sort((a, b) => a.Name.localeCompare(b.Name)).map((x) => x.Name);
const tmp = v[0];
v[0] = v[1];
v[1] = tmp;
v[0].Name + v[1].Name;
`)
if err != nil {
panic(err)
}
t.Log(ret.Export())
}
func TestGoSliceReflect111(t *testing.T) {
vm := New()
vm.Set("v", []int32{
1, 2,
})
ret, err := vm.RunString(`
//v.sort((a, b) => a.Name.localeCompare(b.Name)).map((x) => x.Name);
const tmp = v[0];
v[0] = v[1];
v[1] = tmp;
"" + v[0] + v[1];
`)
if err != nil {
panic(err)
}
t.Log(ret.Export())
a := []int{1, 2}
a0 := reflect.ValueOf(a).Index(0)
a0.Set(reflect.ValueOf(0))
t.Log(a[0])
}
func TestGoSliceReflectExternalLenUpdate(t *testing.T) {
data := &[]int{1}
vm := New()
vm.Set("data", data)
vm.Set("append", func(a *[]int, v int) {
if a != data {
panic(vm.NewTypeError("a != data"))
}
*a = append(*a, v)
})
vm.testScriptWithTestLib(`
assert.sameValue(data.length, 1);
// modify with js
data.push(1);
assert.sameValue(data.length, 2);
// modify with go
append(data, 2);
assert.sameValue(data.length, 3);
`, _undefined, t)
}
func BenchmarkGoSliceReflectSet(b *testing.B) {
vm := New()
a := vm.ToValue([]int{1}).(*Object)
b.ResetTimer()
v := intToValue(0)
for i := 0; i < b.N; i++ {
a.Set("0", v)
}
}

298
goja/object_goslice_test.go Normal file
View File

@ -0,0 +1,298 @@
package goja
import (
"testing"
)
func TestGoSliceBasic(t *testing.T) {
const SCRIPT = `
var sum = 0;
for (var i = 0; i < a.length; i++) {
sum += a[i];
}
sum;
`
r := New()
r.Set("a", []interface{}{1, 2, 3, 4})
v, err := r.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
if i := v.ToInteger(); i != 10 {
t.Fatalf("Expected 10, got: %d", i)
}
}
func TestGoSliceIn(t *testing.T) {
const SCRIPT = `
var idx = "";
for (var i in a) {
idx += i;
}
idx;
`
r := New()
r.Set("a", []interface{}{1, 2, 3, 4})
v, err := r.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
if i := v.String(); i != "0123" {
t.Fatalf("Expected '0123', got: '%s'", i)
}
}
func TestGoSliceExpand(t *testing.T) {
const SCRIPT = `
var l = a.length;
for (var i = 0; i < l; i++) {
a[l + i] = a[i] * 2;
}
var sum = 0;
for (var i = 0; i < a.length; i++) {
sum += a[i];
}
sum;
`
r := New()
a := []interface{}{int64(1), int64(2), int64(3), int64(4)}
r.Set("a", &a)
v, err := r.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
sum := int64(0)
for _, v := range a {
sum += v.(int64)
}
if i := v.ToInteger(); i != sum {
t.Fatalf("Expected %d, got: %d", sum, i)
}
}
func TestGoSliceProtoMethod(t *testing.T) {
const SCRIPT = `
a.join(",")
`
r := New()
a := []interface{}{1, 2, 3, 4}
r.Set("a", a)
ret, err := r.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
if s := ret.String(); s != "1,2,3,4" {
t.Fatalf("Unexpected result: '%s'", s)
}
}
func TestGoSliceSetLength(t *testing.T) {
r := New()
a := []interface{}{1, 2, 3, 4}
r.Set("a", &a)
_, err := r.RunString(`
'use strict';
a.length = 3;
if (a.length !== 3) {
throw new Error("length="+a.length);
}
if (a[3] !== undefined) {
throw new Error("a[3](1)="+a[3]);
}
a.length = 5;
if (a.length !== 5) {
throw new Error("length="+a.length);
}
if (a[3] !== null) {
throw new Error("a[3](2)="+a[3]);
}
if (a[4] !== null) {
throw new Error("a[4]="+a[4]);
}
`)
if err != nil {
t.Fatal(err)
}
}
func TestGoSliceProto(t *testing.T) {
r := New()
a := []interface{}{1, nil, 3}
r.Set("a", &a)
r.testScriptWithTestLib(`
var proto = [,2,,4];
Object.setPrototypeOf(a, proto);
assert.sameValue(a[1], null, "a[1]");
assert.sameValue(a[3], 4, "a[3]");
var desc = Object.getOwnPropertyDescriptor(a, "1");
assert.sameValue(desc.value, null, "desc.value");
assert(desc.writable, "writable");
assert(desc.enumerable, "enumerable");
assert(!desc.configurable, "configurable");
var v5;
Object.defineProperty(proto, "5", {
set: function(v) {
v5 = v;
}
});
a[5] = "test";
assert.sameValue(v5, "test", "v5");
`, _undefined, t)
}
func TestGoSliceProtoProto(t *testing.T) {
r := New()
a := []interface{}{1, nil, 3}
proto := []interface{}{1, 2, 3, 4}
r.Set("a", &a)
r.Set("proto", proto)
_, err := r.RunString(`
"use strict";
var protoproto = Object.create(null);
Object.defineProperty(protoproto, "3", {
value: 42
});
Object.setPrototypeOf(proto, protoproto);
Object.setPrototypeOf(a, proto);
a[3] = 11;
if (a[3] !== 11) {
throw new Error("a[3]=" + a[3]);
}
`)
if err != nil {
t.Fatal(err)
}
}
func TestGoSliceDelete(t *testing.T) {
r := New()
a := []interface{}{1, nil, 3}
r.Set("a", a)
v, err := r.RunString(`
delete a[0] && delete a[1] && delete a[3];
`)
if err != nil {
t.Fatal(err)
}
if v != valueTrue {
t.Fatalf("not true: %v", v)
}
}
func TestGoSlicePop(t *testing.T) {
r := New()
a := []interface{}{1, nil, 3}
r.Set("a", &a)
v, err := r.RunString(`
a.pop()
`)
if err != nil {
t.Fatal(err)
}
if !v.SameAs(intToValue(3)) {
t.Fatal(v)
}
}
func TestGoSlicePopNoPtr(t *testing.T) {
r := New()
a := []interface{}{1, nil, 3}
r.Set("a", a)
v, err := r.RunString(`
a.pop()
`)
if err != nil {
t.Fatal(err)
}
if !v.SameAs(intToValue(3)) {
t.Fatal(v)
}
}
func TestGoSliceShift(t *testing.T) {
r := New()
a := []interface{}{1, nil, 3}
r.Set("a", &a)
v, err := r.RunString(`
a.shift()
`)
if err != nil {
t.Fatal(err)
}
if !v.SameAs(intToValue(1)) {
t.Fatal(v)
}
}
func TestGoSliceLengthProperty(t *testing.T) {
vm := New()
vm.Set("s", []interface{}{2, 3, 4})
_, err := vm.RunString(`
if (!s.hasOwnProperty("length")) {
throw new Error("hasOwnProperty() returned false");
}
let desc = Object.getOwnPropertyDescriptor(s, "length");
if (desc.value !== 3 || !desc.writable || desc.enumerable || desc.configurable) {
throw new Error("incorrect property descriptor: " + JSON.stringify(desc));
}
`)
if err != nil {
t.Fatal(err)
}
}
func TestGoSliceSort(t *testing.T) {
vm := New()
s := []interface{}{4, 2, 3}
vm.Set("s", &s)
_, err := vm.RunString(`s.sort()`)
if err != nil {
t.Fatal(err)
}
if len(s) != 3 {
t.Fatalf("len: %d", len(s))
}
if s[0] != 2 || s[1] != 3 || s[2] != 4 {
t.Fatalf("val: %v", s)
}
}
func TestGoSliceToString(t *testing.T) {
vm := New()
s := []interface{}{4, 2, 3}
vm.Set("s", &s)
res, err := vm.RunString("`${s}`")
if err != nil {
t.Fatal(err)
}
if exp := res.Export(); exp != "4,2,3" {
t.Fatal(exp)
}
}
func TestGoSliceExternalLenUpdate(t *testing.T) {
data := &[]interface{}{1}
vm := New()
vm.Set("data", data)
vm.Set("append", func(a *[]interface{}, v int) {
if a != data {
panic(vm.NewTypeError("a != data"))
}
*a = append(*a, v)
})
vm.testScriptWithTestLib(`
assert.sameValue(data.length, 1);
// modify with js
data.push(1);
assert.sameValue(data.length, 2);
// modify with go
append(data, 2);
assert.sameValue(data.length, 3);
`, _undefined, t)
}

668
goja/object_test.go Normal file
View File

@ -0,0 +1,668 @@
package goja
import (
"fmt"
"reflect"
"strings"
"testing"
)
func TestDefineProperty(t *testing.T) {
r := New()
o := r.NewObject()
err := o.DefineDataProperty("data", r.ToValue(42), FLAG_TRUE, FLAG_TRUE, FLAG_TRUE)
if err != nil {
t.Fatal(err)
}
err = o.DefineAccessorProperty("accessor_ro", r.ToValue(func() int {
return 1
}), nil, FLAG_TRUE, FLAG_TRUE)
if err != nil {
t.Fatal(err)
}
err = o.DefineAccessorProperty("accessor_rw",
r.ToValue(func(call FunctionCall) Value {
return o.Get("__hidden")
}),
r.ToValue(func(call FunctionCall) (ret Value) {
o.Set("__hidden", call.Argument(0))
return
}),
FLAG_TRUE, FLAG_TRUE)
if err != nil {
t.Fatal(err)
}
if v := o.Get("accessor_ro"); v.ToInteger() != 1 {
t.Fatalf("Unexpected accessor value: %v", v)
}
err = o.Set("accessor_ro", r.ToValue(2))
if err == nil {
t.Fatal("Expected an error")
}
if ex, ok := err.(*Exception); ok {
if msg := ex.Error(); msg != "TypeError: Cannot assign to read only property 'accessor_ro'" {
t.Fatalf("Unexpected error: '%s'", msg)
}
} else {
t.Fatalf("Unexected error type: %T", err)
}
err = o.Set("accessor_rw", 42)
if err != nil {
t.Fatal(err)
}
if v := o.Get("accessor_rw"); v.ToInteger() != 42 {
t.Fatalf("Unexpected value: %v", v)
}
}
func TestPropertyOrder(t *testing.T) {
const SCRIPT = `
var o = {};
var sym1 = Symbol(1);
var sym2 = Symbol(2);
o[sym2] = 1;
o[4294967294] = 1;
o[2] = 1;
o[1] = 1;
o[0] = 1;
o["02"] = 1;
o[4294967295] = 1;
o["01"] = 1;
o["00"] = 1;
o[sym1] = 1;
var expected = ["0", "1", "2", "4294967294", "02", "4294967295", "01", "00", sym2, sym1];
var actual = Reflect.ownKeys(o);
if (actual.length !== expected.length) {
throw new Error("Unexpected length: "+actual.length);
}
for (var i = 0; i < actual.length; i++) {
if (actual[i] !== expected[i]) {
throw new Error("Unexpected list: " + actual);
}
}
`
testScript(SCRIPT, _undefined, t)
}
func TestDefinePropertiesSymbol(t *testing.T) {
const SCRIPT = `
var desc = {};
desc[Symbol.toStringTag] = {value: "Test"};
var o = {};
Object.defineProperties(o, desc);
o[Symbol.toStringTag] === "Test";
`
testScript(SCRIPT, valueTrue, t)
}
func TestObjectShorthandProperties(t *testing.T) {
const SCRIPT = `
var b = 1;
var a = {b, get() {return "c"}};
assert.sameValue(a.b, b, "#1");
assert.sameValue(a.get(), "c", "#2");
var obj = {
w\u0069th() { return 42; }
};
assert.sameValue(obj['with'](), 42, 'property exists');
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestObjectAssign(t *testing.T) {
const SCRIPT = `
assert.sameValue(Object.assign({ b: 1 }, { get a() {
Object.defineProperty(this, "b", {
value: 3,
enumerable: false
});
}, b: 2 }).b, 1, "#1");
assert.sameValue(Object.assign({ b: 1 }, { get a() {
delete this.b;
}, b: 2 }).b, 1, "#2");
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestExportCircular(t *testing.T) {
vm := New()
o := vm.NewObject()
o.Set("o", o)
v := o.Export()
if m, ok := v.(map[string]interface{}); ok {
if reflect.ValueOf(m["o"]).Pointer() != reflect.ValueOf(v).Pointer() {
t.Fatal("Unexpected value")
}
} else {
t.Fatal("Unexpected type")
}
res, err := vm.RunString(`var a = []; a[0] = a;`)
if err != nil {
t.Fatal(err)
}
v = res.Export()
if a, ok := v.([]interface{}); ok {
if reflect.ValueOf(a[0]).Pointer() != reflect.ValueOf(v).Pointer() {
t.Fatal("Unexpected value")
}
} else {
t.Fatal("Unexpected type")
}
}
type test_s struct {
S *test_s1
}
type test_s1 struct {
S *test_s
}
func TestExportToCircular(t *testing.T) {
vm := New()
o := vm.NewObject()
o.Set("o", o)
var m map[string]interface{}
err := vm.ExportTo(o, &m)
if err != nil {
t.Fatal(err)
}
type K string
type T map[K]T
var m1 T
err = vm.ExportTo(o, &m1)
if err != nil {
t.Fatal(err)
}
type A []A
var a A
res, err := vm.RunString("var a = []; a[0] = a;")
if err != nil {
t.Fatal(err)
}
err = vm.ExportTo(res, &a)
if err != nil {
t.Fatal(err)
}
if &a[0] != &a[0][0] {
t.Fatal("values do not match")
}
o = vm.NewObject()
o.Set("S", o)
var s test_s
err = vm.ExportTo(o, &s)
if err != nil {
t.Fatal(err)
}
if s.S.S != &s {
t.Fatalf("values do not match: %v, %v", s.S.S, &s)
}
type test_s2 struct {
S interface{}
S1 *test_s2
}
var s2 test_s2
o.Set("S1", o)
err = vm.ExportTo(o, &s2)
if err != nil {
t.Fatal(err)
}
if m, ok := s2.S.(map[string]interface{}); ok {
if reflect.ValueOf(m["S"]).Pointer() != reflect.ValueOf(m).Pointer() {
t.Fatal("Unexpected m.S")
}
} else {
t.Fatalf("Unexpected s2.S type: %T", s2.S)
}
if s2.S1 != &s2 {
t.Fatal("Unexpected s2.S1")
}
o1 := vm.NewObject()
o1.Set("S", o)
o1.Set("S1", o)
err = vm.ExportTo(o1, &s2)
if err != nil {
t.Fatal(err)
}
if s2.S1.S1 != s2.S1 {
t.Fatal("Unexpected s2.S1.S1")
}
}
func TestExportWrappedMap(t *testing.T) {
vm := New()
m := map[string]interface{}{
"test": "failed",
}
exported := vm.ToValue(m).Export()
if exportedMap, ok := exported.(map[string]interface{}); ok {
exportedMap["test"] = "passed"
if v := m["test"]; v != "passed" {
t.Fatalf("Unexpected m[\"test\"]: %v", v)
}
} else {
t.Fatalf("Unexpected export type: %T", exported)
}
}
func TestExportToWrappedMap(t *testing.T) {
vm := New()
m := map[string]interface{}{
"test": "failed",
}
var exported map[string]interface{}
err := vm.ExportTo(vm.ToValue(m), &exported)
if err != nil {
t.Fatal(err)
}
exported["test"] = "passed"
if v := m["test"]; v != "passed" {
t.Fatalf("Unexpected m[\"test\"]: %v", v)
}
}
func TestExportToWrappedMapCustom(t *testing.T) {
type CustomMap map[string]bool
vm := New()
m := CustomMap{}
var exported CustomMap
err := vm.ExportTo(vm.ToValue(m), &exported)
if err != nil {
t.Fatal(err)
}
exported["test"] = true
if v := m["test"]; v != true {
t.Fatalf("Unexpected m[\"test\"]: %v", v)
}
}
func TestExportToSliceNonIterable(t *testing.T) {
vm := New()
o := vm.NewObject()
var a []interface{}
err := vm.ExportTo(o, &a)
if err == nil {
t.Fatal("Expected an error")
}
if len(a) != 0 {
t.Fatalf("a: %v", a)
}
if msg := err.Error(); msg != "cannot convert [object Object] to []interface {}: not an array or iterable" {
t.Fatalf("Unexpected error: %v", err)
}
}
func ExampleRuntime_ExportTo_iterableToSlice() {
vm := New()
v, err := vm.RunString(`
function reverseIterator() {
const arr = this;
let idx = arr.length;
return {
next: () => idx > 0 ? {value: arr[--idx]} : {done: true}
}
}
const arr = [1,2,3];
arr[Symbol.iterator] = reverseIterator;
arr;
`)
if err != nil {
panic(err)
}
var arr []int
err = vm.ExportTo(v, &arr)
if err != nil {
panic(err)
}
fmt.Println(arr)
// Output: [3 2 1]
}
func TestRuntime_ExportTo_proxiedIterableToSlice(t *testing.T) {
vm := New()
v, err := vm.RunString(`
function reverseIterator() {
const arr = this;
let idx = arr.length;
return {
next: () => idx > 0 ? {value: arr[--idx]} : {done: true}
}
}
const arr = [1,2,3];
arr[Symbol.iterator] = reverseIterator;
new Proxy(arr, {});
`)
if err != nil {
t.Fatal(err)
}
var arr []int
err = vm.ExportTo(v, &arr)
if err != nil {
t.Fatal(err)
}
if out := fmt.Sprint(arr); out != "[3 2 1]" {
t.Fatal(out)
}
}
func ExampleRuntime_ExportTo_arrayLikeToSlice() {
vm := New()
v, err := vm.RunString(`
({
length: 3,
0: 1,
1: 2,
2: 3
});
`)
if err != nil {
panic(err)
}
var arr []int
err = vm.ExportTo(v, &arr)
if err != nil {
panic(err)
}
fmt.Println(arr)
// Output: [1 2 3]
}
func TestExportArrayToArrayMismatchedLengths(t *testing.T) {
vm := New()
a := vm.NewArray(1, 2)
var a1 [3]int
err := vm.ExportTo(a, &a1)
if err == nil {
t.Fatal("expected error")
}
if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestExportIterableToArrayMismatchedLengths(t *testing.T) {
vm := New()
a, err := vm.RunString(`
new Map([[1, true], [2, true]]);
`)
if err != nil {
t.Fatal(err)
}
var a1 [3]interface{}
err = vm.ExportTo(a, &a1)
if err == nil {
t.Fatal("expected error")
}
if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestExportArrayLikeToArrayMismatchedLengths(t *testing.T) {
vm := New()
a, err := vm.RunString(`
({
length: 2,
0: true,
1: true
});
`)
if err != nil {
t.Fatal(err)
}
var a1 [3]interface{}
err = vm.ExportTo(a, &a1)
if err == nil {
t.Fatal("expected error")
}
if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestSetForeignReturnValue(t *testing.T) {
const SCRIPT = `
var array = [1, 2, 3];
var arrayTarget = new Proxy(array, {});
Object.preventExtensions(array);
!Reflect.set(arrayTarget, "foo", 2);
`
testScript(SCRIPT, valueTrue, t)
}
func TestDefinePropertiesUndefinedVal(t *testing.T) {
const SCRIPT = `
var target = {};
var sym = Symbol();
target[sym] = 1;
target.foo = 2;
target[0] = 3;
var getOwnKeys = [];
var proxy = new Proxy(target, {
getOwnPropertyDescriptor: function(_target, key) {
getOwnKeys.push(key);
},
});
Object.defineProperties({}, proxy);
true;
`
testScript(SCRIPT, valueTrue, t)
}
func ExampleObject_Delete() {
vm := New()
obj := vm.NewObject()
_ = obj.Set("test", true)
before := obj.Get("test")
_ = obj.Delete("test")
after := obj.Get("test")
fmt.Printf("before: %v, after: %v", before, after)
// Output: before: true, after: <nil>
}
func ExampleObject_GetOwnPropertyNames() {
vm := New()
obj := vm.NewObject()
obj.DefineDataProperty("test", vm.ToValue(true), FLAG_TRUE, FLAG_TRUE, FLAG_FALSE) // define a non-enumerable property that Keys() would not return
fmt.Print(obj.GetOwnPropertyNames())
// Output: [test]
}
func TestObjectEquality(t *testing.T) {
type CustomInt int
type S struct {
F CustomInt
}
vm := New()
vm.Set("s", S{})
// indexOf() and includes() use different equality checks (StrictEquals and SameValueZero respectively),
// but for objects they must behave the same. De-referencing s.F creates a new wrapper each time.
vm.testScriptWithTestLib(`
assert.sameValue([s.F].indexOf(s.F), 0, "indexOf");
assert([s.F].includes(s.F));
`, _undefined, t)
}
func BenchmarkPut(b *testing.B) {
v := &Object{}
o := &baseObject{
val: v,
extensible: true,
}
v.self = o
o.init()
var key Value = asciiString("test")
var val Value = valueInt(123)
for i := 0; i < b.N; i++ {
v.setOwn(key, val, false)
}
}
func BenchmarkPutStr(b *testing.B) {
v := &Object{}
o := &baseObject{
val: v,
extensible: true,
}
o.init()
v.self = o
var val Value = valueInt(123)
for i := 0; i < b.N; i++ {
o.setOwnStr("test", val, false)
}
}
func BenchmarkGet(b *testing.B) {
v := &Object{}
o := &baseObject{
val: v,
extensible: true,
}
o.init()
v.self = o
var n Value = asciiString("test")
for i := 0; i < b.N; i++ {
v.get(n, nil)
}
}
func BenchmarkGetStr(b *testing.B) {
v := &Object{}
o := &baseObject{
val: v,
extensible: true,
}
v.self = o
o.init()
for i := 0; i < b.N; i++ {
o.getStr("test", nil)
}
}
func __toString(v Value) string {
switch v := v.(type) {
case asciiString:
return string(v)
default