diff --git a/.tc39_test262_checkout.sh b/.tc39_test262_checkout.sh index 07fe7811..65dedfc6 100755 --- a/.tc39_test262_checkout.sh +++ b/.tc39_test262_checkout.sh @@ -1,6 +1,6 @@ #!/bin/sh # this is just the commit it was last tested with -sha=e87b0048c402479df1d9cb391fb86620cf3200fd +sha=926b0960d737b9f1dfd0ec0c1dfd95d836016d33 mkdir -p testdata/test262 cd testdata/test262 diff --git a/array.go b/array.go index 6cff33fe..f8028d27 100644 --- a/array.go +++ b/array.go @@ -127,9 +127,6 @@ func (a *arrayObject) setLengthInt(l uint32, throw bool) bool { } func (a *arrayObject) setLength(v uint32, throw bool) bool { - if v == a.length { - return true - } if !a.lengthProp.writable { a.val.runtime.typeErrorResult(throw, "length is not writable") return false @@ -201,7 +198,7 @@ func (a *arrayObject) getStr(name unistring.String, receiver Value) Value { return a.getStrWithOwnProp(a.getOwnPropStr(name), name, receiver) } -func (a *arrayObject) getLengthProp() Value { +func (a *arrayObject) getLengthProp() *valueProperty { a.lengthProp.value = intToValue(int64(a.length)) return &a.lengthProp } @@ -382,7 +379,10 @@ func (r *Runtime) defineArrayLength(prop *valueProperty, descr PropertyDescripto } if descr.Value != nil { - ret = setter(newLen, false) + oldLen := uint32(prop.value.ToInteger()) + if oldLen != newLen { + ret = setter(newLen, false) + } } else { ret = true } @@ -437,7 +437,7 @@ func (a *arrayObject) defineOwnPropertyStr(name unistring.String, descr Property return a._defineIdxProperty(idx, descr, throw) } if name == "length" { - return a.val.runtime.defineArrayLength(&a.lengthProp, descr, a.setLength, throw) + return a.val.runtime.defineArrayLength(a.getLengthProp(), descr, a.setLength, throw) } return a.baseObject.defineOwnPropertyStr(name, descr, throw) } diff --git a/array_sparse.go b/array_sparse.go index 0c0917a8..9a352aff 100644 --- a/array_sparse.go +++ b/array_sparse.go @@ -78,9 +78,6 @@ func (a *sparseArrayObject) setLengthInt(l uint32, throw bool) bool { } func (a *sparseArrayObject) setLength(v uint32, throw bool) bool { - if v == a.length { - return true - } if !a.lengthProp.writable { a.val.runtime.typeErrorResult(throw, "length is not writable") return false @@ -120,7 +117,7 @@ func (a *sparseArrayObject) getIdx(idx valueInt, receiver Value) Value { return prop } -func (a *sparseArrayObject) getLengthProp() Value { +func (a *sparseArrayObject) getLengthProp() *valueProperty { a.lengthProp.value = intToValue(int64(a.length)) return &a.lengthProp } @@ -369,7 +366,7 @@ func (a *sparseArrayObject) defineOwnPropertyStr(name unistring.String, descr Pr return a._defineIdxProperty(idx, descr, throw) } if name == "length" { - return a.val.runtime.defineArrayLength(&a.lengthProp, descr, a.setLength, throw) + return a.val.runtime.defineArrayLength(a.getLengthProp(), descr, a.setLength, throw) } return a.baseObject.defineOwnPropertyStr(name, descr, throw) } diff --git a/builtin_array.go b/builtin_array.go index 5003f255..954e1a23 100644 --- a/builtin_array.go +++ b/builtin_array.go @@ -144,8 +144,8 @@ func (r *Runtime) arrayproto_pop(call FunctionCall) Value { obj := call.This.ToObject(r) if a, ok := obj.self.(*arrayObject); ok { l := a.length + var val Value if l > 0 { - var val Value l-- if l < uint32(len(a.values)) { val = a.values[l] @@ -161,10 +161,15 @@ func (r *Runtime) arrayproto_pop(call FunctionCall) Value { //a._setLengthInt(l, false) a.values[l] = nil a.values = a.values[:l] + } else { + val = _undefined + } + if a.lengthProp.writable { a.length = l - return val + } else { + a.setLength(0, true) // will throw } - return _undefined + return val } else { return r.arrayproto_pop_generic(obj) } @@ -313,7 +318,7 @@ func (r *Runtime) arrayproto_slice(call FunctionCall) Value { a := arraySpeciesCreate(o, count) if src := r.checkStdArrayObj(o); src != nil { - if dst, ok := a.self.(*arrayObject); ok { + if dst := r.checkStdArrayObjWithProto(a); dst != nil { values := make([]Value, count) copy(values, src.values[start:]) setArrayValues(dst, values) @@ -396,7 +401,7 @@ func (r *Runtime) arrayproto_splice(call FunctionCall) Value { itemCount := max(int64(len(call.Arguments)-2), 0) newLength := length - actualDeleteCount + itemCount if src := r.checkStdArrayObj(o); src != nil { - if dst, ok := a.self.(*arrayObject); ok { + if dst := r.checkStdArrayObjWithProto(a); dst != nil { values := make([]Value, actualDeleteCount) copy(values, src.values[actualStart:]) setArrayValues(dst, values) @@ -484,7 +489,7 @@ func (r *Runtime) arrayproto_unshift(call FunctionCall) Value { argCount := int64(len(call.Arguments)) newLen := intToValue(length + argCount) newSize := length + argCount - if arr := r.checkStdArrayObj(o); arr != nil && newSize < math.MaxUint32 { + if arr := r.checkStdArrayObjWithProto(o); arr != nil && newSize < math.MaxUint32 { if int64(cap(arr.values)) >= newSize { arr.values = arr.values[:newSize] copy(arr.values[argCount:], arr.values[:length]) @@ -917,8 +922,11 @@ func (r *Runtime) arrayproto_reverse(call FunctionCall) Value { func (r *Runtime) arrayproto_shift(call FunctionCall) Value { o := call.This.ToObject(r) - if a := r.checkStdArrayObj(o); a != nil { + if a := r.checkStdArrayObjWithProto(o); a != nil { if len(a.values) == 0 { + if !a.lengthProp.writable { + a.setLength(0, true) // will throw + } return _undefined } first := a.values[0] @@ -1138,6 +1146,20 @@ func (r *Runtime) checkStdArrayObj(obj *Object) *arrayObject { return nil } +func (r *Runtime) checkStdArrayObjWithProto(obj *Object) *arrayObject { + if arr := r.checkStdArrayObj(obj); arr != nil { + if p1, ok := arr.prototype.self.(*arrayObject); ok && p1.propValueCount == 0 { + if p2, ok := p1.prototype.self.(*baseObject); ok && p2.prototype == nil { + p2.ensurePropOrder() + if p2.idxPropCount == 0 { + return arr + } + } + } + } + return nil +} + func (r *Runtime) checkStdArray(v Value) *arrayObject { if obj, ok := v.(*Object); ok { return r.checkStdArrayObj(obj) @@ -1195,7 +1217,7 @@ func (r *Runtime) array_from(call FunctionCall) Value { } iter := r.getIterator(items, usingIterator) if mapFn == nil { - if a := r.checkStdArrayObj(arr); a != nil { + if a := r.checkStdArrayObjWithProto(arr); a != nil { var values []Value iter.iterate(func(val Value) { values = append(values, val) @@ -1222,7 +1244,7 @@ func (r *Runtime) array_from(call FunctionCall) Value { arr = r.newArrayValues(nil) } if mapFn == nil { - if a := r.checkStdArrayObj(arr); a != nil { + if a := r.checkStdArrayObjWithProto(arr); a != nil { values := make([]Value, l) for k := int64(0); k < l; k++ { values[k] = nilSafe(arrayLike.self.getIdx(valueInt(k), nil)) @@ -1344,6 +1366,8 @@ func (r *Runtime) createArrayProto(val *Object) objectImpl { bl.setOwnStr("includes", valueTrue, true) bl.setOwnStr("keys", valueTrue, true) bl.setOwnStr("values", valueTrue, true) + bl.setOwnStr("groupBy", valueTrue, true) + bl.setOwnStr("groupByToMap", valueTrue, true) o._putSym(SymUnscopables, valueProp(bl.val, false, false, true)) return o diff --git a/object.go b/object.go index 5426733c..d3fc45f6 100644 --- a/object.go +++ b/object.go @@ -590,9 +590,7 @@ func (o *baseObject) setForeignStr(name unistring.String, val, receiver Value, t func (o *baseObject) setForeignIdx(name valueInt, val, receiver Value, throw bool) (bool, bool) { if idx := toIdx(name); idx != math.MaxUint32 { - if o.lastSortedPropLen != len(o.propNames) { - o.fixPropOrder() - } + o.ensurePropOrder() if o.idxPropCount == 0 { return o._setForeignIdx(name, name, nil, receiver, throw) } @@ -1238,9 +1236,7 @@ func copyNamesIfNeeded(names []unistring.String, extraCap int) []unistring.Strin } func (o *baseObject) iterateStringKeys() iterNextFunc { - if len(o.propNames) > o.lastSortedPropLen { - o.fixPropOrder() - } + o.ensurePropOrder() propNames := prepareNamesForCopy(o.propNames) o.propNames = propNames return (&objectPropIter{ @@ -1301,6 +1297,13 @@ func (o *baseObject) equal(objectImpl) bool { return false } +// hopefully this gets inlined +func (o *baseObject) ensurePropOrder() { + if o.lastSortedPropLen < len(o.propNames) { + o.fixPropOrder() + } +} + // Reorder property names so that any integer properties are shifted to the beginning of the list // in ascending order. This is to conform to https://262.ecma-international.org/#sec-ordinaryownpropertykeys. // Personally I think this requirement is strange. I can sort of understand where they are coming from, @@ -1336,9 +1339,7 @@ func (o *baseObject) fixPropOrder() { } func (o *baseObject) stringKeys(all bool, keys []Value) []Value { - if len(o.propNames) > o.lastSortedPropLen { - o.fixPropOrder() - } + o.ensurePropOrder() if all { for _, k := range o.propNames { keys = append(keys, stringValueFromRaw(k)) diff --git a/vm.go b/vm.go index 06d6179b..db0fc051 100644 --- a/vm.go +++ b/vm.go @@ -3088,12 +3088,20 @@ func (vm *vm) checkBindVarsGlobal(names []unistring.String) { sn := vm.r.global.stash.names if bo, ok := o.(*baseObject); ok { // shortcut - for _, name := range names { - if !bo.hasOwnPropertyStr(name) && !bo.extensible { - panic(vm.r.NewTypeError("Cannot define global variable '%s', global object is not extensible", name)) + if bo.extensible { + for _, name := range names { + if _, exists := sn[name]; exists { + panic(vm.alreadyDeclared(name)) + } } - if _, exists := sn[name]; exists { - panic(vm.alreadyDeclared(name)) + } else { + for _, name := range names { + if !bo.hasOwnPropertyStr(name) { + panic(vm.r.NewTypeError("Cannot define global variable '%s', global object is not extensible", name)) + } + if _, exists := sn[name]; exists { + panic(vm.alreadyDeclared(name)) + } } } } else {