566 lines
13 KiB
Go
566 lines
13 KiB
Go
package goja
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"math/bits"
|
|
"reflect"
|
|
"strconv"
|
|
|
|
"github.com/dop251/goja/unistring"
|
|
)
|
|
|
|
type arrayIterObject struct {
|
|
baseObject
|
|
obj *Object
|
|
nextIdx int64
|
|
kind iterationKind
|
|
}
|
|
|
|
func (ai *arrayIterObject) next() Value {
|
|
if ai.obj == nil {
|
|
return ai.val.runtime.createIterResultObject(_undefined, true)
|
|
}
|
|
if ta, ok := ai.obj.self.(*typedArrayObject); ok {
|
|
ta.viewedArrayBuf.ensureNotDetached(true)
|
|
}
|
|
l := toLength(ai.obj.self.getStr("length", nil))
|
|
index := ai.nextIdx
|
|
if index >= l {
|
|
ai.obj = nil
|
|
return ai.val.runtime.createIterResultObject(_undefined, true)
|
|
}
|
|
ai.nextIdx++
|
|
idxVal := valueInt(index)
|
|
if ai.kind == iterationKindKey {
|
|
return ai.val.runtime.createIterResultObject(idxVal, false)
|
|
}
|
|
elementValue := nilSafe(ai.obj.self.getIdx(idxVal, nil))
|
|
var result Value
|
|
if ai.kind == iterationKindValue {
|
|
result = elementValue
|
|
} else {
|
|
result = ai.val.runtime.newArrayValues([]Value{idxVal, elementValue})
|
|
}
|
|
return ai.val.runtime.createIterResultObject(result, false)
|
|
}
|
|
|
|
func (r *Runtime) createArrayIterator(iterObj *Object, kind iterationKind) Value {
|
|
o := &Object{runtime: r}
|
|
|
|
ai := &arrayIterObject{
|
|
obj: iterObj,
|
|
kind: kind,
|
|
}
|
|
ai.class = classObject
|
|
ai.val = o
|
|
ai.extensible = true
|
|
o.self = ai
|
|
ai.prototype = r.getArrayIteratorPrototype()
|
|
ai.init()
|
|
|
|
return o
|
|
}
|
|
|
|
type arrayObject struct {
|
|
baseObject
|
|
values []Value
|
|
length uint32
|
|
objCount int
|
|
propValueCount int
|
|
lengthProp valueProperty
|
|
}
|
|
|
|
func (a *arrayObject) init() {
|
|
a.baseObject.init()
|
|
a.lengthProp.writable = true
|
|
|
|
a._put("length", &a.lengthProp)
|
|
}
|
|
|
|
func (a *arrayObject) _setLengthInt(l uint32, throw bool) bool {
|
|
ret := true
|
|
if l <= a.length {
|
|
if a.propValueCount > 0 {
|
|
// Slow path
|
|
for i := len(a.values) - 1; i >= int(l); i-- {
|
|
if prop, ok := a.values[i].(*valueProperty); ok {
|
|
if !prop.configurable {
|
|
l = uint32(i) + 1
|
|
ret = false
|
|
break
|
|
}
|
|
a.propValueCount--
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if l <= uint32(len(a.values)) {
|
|
if l >= 16 && l < uint32(cap(a.values))>>2 {
|
|
ar := make([]Value, l)
|
|
copy(ar, a.values)
|
|
a.values = ar
|
|
} else {
|
|
ar := a.values[l:len(a.values)]
|
|
for i := range ar {
|
|
ar[i] = nil
|
|
}
|
|
a.values = a.values[:l]
|
|
}
|
|
}
|
|
a.length = l
|
|
if !ret {
|
|
a.val.runtime.typeErrorResult(throw, "Cannot redefine property: length")
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (a *arrayObject) setLengthInt(l uint32, throw bool) bool {
|
|
if l == a.length {
|
|
return true
|
|
}
|
|
if !a.lengthProp.writable {
|
|
a.val.runtime.typeErrorResult(throw, "length is not writable")
|
|
return false
|
|
}
|
|
return a._setLengthInt(l, throw)
|
|
}
|
|
|
|
func (a *arrayObject) setLength(v uint32, throw bool) bool {
|
|
if !a.lengthProp.writable {
|
|
a.val.runtime.typeErrorResult(throw, "length is not writable")
|
|
return false
|
|
}
|
|
return a._setLengthInt(v, throw)
|
|
}
|
|
|
|
func (a *arrayObject) getIdx(idx valueInt, receiver Value) Value {
|
|
prop := a.getOwnPropIdx(idx)
|
|
if prop == nil {
|
|
if a.prototype != nil {
|
|
if receiver == nil {
|
|
return a.prototype.self.getIdx(idx, a.val)
|
|
}
|
|
return a.prototype.self.getIdx(idx, receiver)
|
|
}
|
|
}
|
|
if prop, ok := prop.(*valueProperty); ok {
|
|
if receiver == nil {
|
|
return prop.get(a.val)
|
|
}
|
|
return prop.get(receiver)
|
|
}
|
|
return prop
|
|
}
|
|
|
|
func (a *arrayObject) getOwnPropStr(name unistring.String) Value {
|
|
if len(a.values) > 0 {
|
|
if i := strToArrayIdx(name); i != math.MaxUint32 {
|
|
if i < uint32(len(a.values)) {
|
|
return a.values[i]
|
|
}
|
|
}
|
|
}
|
|
if name == "length" {
|
|
return a.getLengthProp()
|
|
}
|
|
return a.baseObject.getOwnPropStr(name)
|
|
}
|
|
|
|
func (a *arrayObject) getOwnPropIdx(idx valueInt) Value {
|
|
if i := toIdx(idx); i != math.MaxUint32 {
|
|
if i < uint32(len(a.values)) {
|
|
return a.values[i]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
return a.baseObject.getOwnPropStr(idx.string())
|
|
}
|
|
|
|
func (a *arrayObject) sortLen() int {
|
|
return len(a.values)
|
|
}
|
|
|
|
func (a *arrayObject) sortGet(i int) Value {
|
|
v := a.values[i]
|
|
if p, ok := v.(*valueProperty); ok {
|
|
v = p.get(a.val)
|
|
}
|
|
return v
|
|
}
|
|
|
|
func (a *arrayObject) swap(i int, j int) {
|
|
a.values[i], a.values[j] = a.values[j], a.values[i]
|
|
}
|
|
|
|
func (a *arrayObject) getStr(name unistring.String, receiver Value) Value {
|
|
return a.getStrWithOwnProp(a.getOwnPropStr(name), name, receiver)
|
|
}
|
|
|
|
func (a *arrayObject) getLengthProp() *valueProperty {
|
|
a.lengthProp.value = intToValue(int64(a.length))
|
|
return &a.lengthProp
|
|
}
|
|
|
|
func (a *arrayObject) setOwnIdx(idx valueInt, val Value, throw bool) bool {
|
|
if i := toIdx(idx); i != math.MaxUint32 {
|
|
return a._setOwnIdx(i, val, throw)
|
|
} else {
|
|
return a.baseObject.setOwnStr(idx.string(), val, throw)
|
|
}
|
|
}
|
|
|
|
func (a *arrayObject) _setOwnIdx(idx uint32, val Value, throw bool) bool {
|
|
var prop Value
|
|
if idx < uint32(len(a.values)) {
|
|
prop = a.values[idx]
|
|
}
|
|
|
|
if prop == nil {
|
|
if proto := a.prototype; proto != nil {
|
|
// we know it's foreign because prototype loops are not allowed
|
|
if res, ok := proto.self.setForeignIdx(valueInt(idx), val, a.val, throw); ok {
|
|
return res
|
|
}
|
|
}
|
|
// new property
|
|
if !a.extensible {
|
|
a.val.runtime.typeErrorResult(throw, "Cannot add property %d, object is not extensible", idx)
|
|
return false
|
|
} else {
|
|
if idx >= a.length {
|
|
if !a.setLengthInt(idx+1, throw) {
|
|
return false
|
|
}
|
|
}
|
|
if idx >= uint32(len(a.values)) {
|
|
if !a.expand(idx) {
|
|
a.val.self.(*sparseArrayObject).add(idx, val)
|
|
return true
|
|
}
|
|
}
|
|
a.objCount++
|
|
}
|
|
} else {
|
|
if prop, ok := prop.(*valueProperty); ok {
|
|
if !prop.isWritable() {
|
|
a.val.runtime.typeErrorResult(throw)
|
|
return false
|
|
}
|
|
prop.set(a.val, val)
|
|
return true
|
|
}
|
|
}
|
|
a.values[idx] = val
|
|
return true
|
|
}
|
|
|
|
func (a *arrayObject) setOwnStr(name unistring.String, val Value, throw bool) bool {
|
|
if idx := strToArrayIdx(name); idx != math.MaxUint32 {
|
|
return a._setOwnIdx(idx, val, throw)
|
|
} else {
|
|
if name == "length" {
|
|
return a.setLength(a.val.runtime.toLengthUint32(val), throw)
|
|
} else {
|
|
return a.baseObject.setOwnStr(name, val, throw)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (a *arrayObject) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) {
|
|
return a._setForeignIdx(idx, a.getOwnPropIdx(idx), val, receiver, throw)
|
|
}
|
|
|
|
func (a *arrayObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
|
|
return a._setForeignStr(name, a.getOwnPropStr(name), val, receiver, throw)
|
|
}
|
|
|
|
type arrayPropIter struct {
|
|
a *arrayObject
|
|
limit int
|
|
idx int
|
|
}
|
|
|
|
func (i *arrayPropIter) next() (propIterItem, iterNextFunc) {
|
|
for i.idx < len(i.a.values) && i.idx < i.limit {
|
|
name := asciiString(strconv.Itoa(i.idx))
|
|
prop := i.a.values[i.idx]
|
|
i.idx++
|
|
if prop != nil {
|
|
return propIterItem{name: name, value: prop}, i.next
|
|
}
|
|
}
|
|
|
|
return i.a.baseObject.iterateStringKeys()()
|
|
}
|
|
|
|
func (a *arrayObject) iterateStringKeys() iterNextFunc {
|
|
return (&arrayPropIter{
|
|
a: a,
|
|
limit: len(a.values),
|
|
}).next
|
|
}
|
|
|
|
func (a *arrayObject) stringKeys(all bool, accum []Value) []Value {
|
|
for i, prop := range a.values {
|
|
name := strconv.Itoa(i)
|
|
if prop != nil {
|
|
if !all {
|
|
if prop, ok := prop.(*valueProperty); ok && !prop.enumerable {
|
|
continue
|
|
}
|
|
}
|
|
accum = append(accum, asciiString(name))
|
|
}
|
|
}
|
|
return a.baseObject.stringKeys(all, accum)
|
|
}
|
|
|
|
func (a *arrayObject) hasOwnPropertyStr(name unistring.String) bool {
|
|
if idx := strToArrayIdx(name); idx != math.MaxUint32 {
|
|
return idx < uint32(len(a.values)) && a.values[idx] != nil
|
|
} else {
|
|
return a.baseObject.hasOwnPropertyStr(name)
|
|
}
|
|
}
|
|
|
|
func (a *arrayObject) hasOwnPropertyIdx(idx valueInt) bool {
|
|
if idx := toIdx(idx); idx != math.MaxUint32 {
|
|
return idx < uint32(len(a.values)) && a.values[idx] != nil
|
|
}
|
|
return a.baseObject.hasOwnPropertyStr(idx.string())
|
|
}
|
|
|
|
func (a *arrayObject) hasPropertyIdx(idx valueInt) bool {
|
|
if a.hasOwnPropertyIdx(idx) {
|
|
return true
|
|
}
|
|
|
|
if a.prototype != nil {
|
|
return a.prototype.self.hasPropertyIdx(idx)
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (a *arrayObject) expand(idx uint32) bool {
|
|
targetLen := idx + 1
|
|
if targetLen > uint32(len(a.values)) {
|
|
if targetLen < uint32(cap(a.values)) {
|
|
a.values = a.values[:targetLen]
|
|
} else {
|
|
if idx > 4096 && (a.objCount == 0 || idx/uint32(a.objCount) > 10) {
|
|
//log.Println("Switching standard->sparse")
|
|
sa := &sparseArrayObject{
|
|
baseObject: a.baseObject,
|
|
length: a.length,
|
|
propValueCount: a.propValueCount,
|
|
}
|
|
sa.setValues(a.values, a.objCount+1)
|
|
sa.val.self = sa
|
|
sa.lengthProp.writable = a.lengthProp.writable
|
|
sa._put("length", &sa.lengthProp)
|
|
return false
|
|
} else {
|
|
if bits.UintSize == 32 {
|
|
if targetLen >= math.MaxInt32 {
|
|
panic(a.val.runtime.NewTypeError("Array index overflows int"))
|
|
}
|
|
}
|
|
tl := int(targetLen)
|
|
newValues := make([]Value, tl, growCap(tl, len(a.values), cap(a.values)))
|
|
copy(newValues, a.values)
|
|
a.values = newValues
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (r *Runtime) defineArrayLength(prop *valueProperty, descr PropertyDescriptor, setter func(uint32, bool) bool, throw bool) bool {
|
|
var newLen uint32
|
|
ret := true
|
|
if descr.Value != nil {
|
|
newLen = r.toLengthUint32(descr.Value)
|
|
}
|
|
|
|
if descr.Configurable == FLAG_TRUE || descr.Enumerable == FLAG_TRUE || descr.Getter != nil || descr.Setter != nil {
|
|
ret = false
|
|
goto Reject
|
|
}
|
|
|
|
if descr.Value != nil {
|
|
oldLen := uint32(prop.value.ToInteger())
|
|
if oldLen != newLen {
|
|
ret = setter(newLen, false)
|
|
}
|
|
} else {
|
|
ret = true
|
|
}
|
|
|
|
if descr.Writable != FLAG_NOT_SET {
|
|
w := descr.Writable.Bool()
|
|
if prop.writable {
|
|
prop.writable = w
|
|
} else {
|
|
if w {
|
|
ret = false
|
|
goto Reject
|
|
}
|
|
}
|
|
}
|
|
|
|
Reject:
|
|
if !ret {
|
|
r.typeErrorResult(throw, "Cannot redefine property: length")
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
func (a *arrayObject) _defineIdxProperty(idx uint32, desc PropertyDescriptor, throw bool) bool {
|
|
var existing Value
|
|
if idx < uint32(len(a.values)) {
|
|
existing = a.values[idx]
|
|
}
|
|
prop, ok := a.baseObject._defineOwnProperty(unistring.String(strconv.FormatUint(uint64(idx), 10)), existing, desc, throw)
|
|
if ok {
|
|
if idx >= a.length {
|
|
if !a.setLengthInt(idx+1, throw) {
|
|
return false
|
|
}
|
|
}
|
|
if a.expand(idx) {
|
|
a.values[idx] = prop
|
|
a.objCount++
|
|
if _, ok := prop.(*valueProperty); ok {
|
|
a.propValueCount++
|
|
}
|
|
} else {
|
|
a.val.self.(*sparseArrayObject).add(idx, prop)
|
|
}
|
|
}
|
|
return ok
|
|
}
|
|
|
|
func (a *arrayObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
|
|
if idx := strToArrayIdx(name); idx != math.MaxUint32 {
|
|
return a._defineIdxProperty(idx, descr, throw)
|
|
}
|
|
if name == "length" {
|
|
return a.val.runtime.defineArrayLength(a.getLengthProp(), descr, a.setLength, throw)
|
|
}
|
|
return a.baseObject.defineOwnPropertyStr(name, descr, throw)
|
|
}
|
|
|
|
func (a *arrayObject) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
|
|
if idx := toIdx(idx); idx != math.MaxUint32 {
|
|
return a._defineIdxProperty(idx, descr, throw)
|
|
}
|
|
return a.baseObject.defineOwnPropertyStr(idx.string(), descr, throw)
|
|
}
|
|
|
|
func (a *arrayObject) _deleteIdxProp(idx uint32, throw bool) bool {
|
|
if idx < uint32(len(a.values)) {
|
|
if v := a.values[idx]; v != nil {
|
|
if p, ok := v.(*valueProperty); ok {
|
|
if !p.configurable {
|
|
a.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of %s", idx, a.val.toString())
|
|
return false
|
|
}
|
|
a.propValueCount--
|
|
}
|
|
a.values[idx] = nil
|
|
a.objCount--
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (a *arrayObject) deleteStr(name unistring.String, throw bool) bool {
|
|
if idx := strToArrayIdx(name); idx != math.MaxUint32 {
|
|
return a._deleteIdxProp(idx, throw)
|
|
}
|
|
return a.baseObject.deleteStr(name, throw)
|
|
}
|
|
|
|
func (a *arrayObject) deleteIdx(idx valueInt, throw bool) bool {
|
|
if idx := toIdx(idx); idx != math.MaxUint32 {
|
|
return a._deleteIdxProp(idx, throw)
|
|
}
|
|
return a.baseObject.deleteStr(idx.string(), throw)
|
|
}
|
|
|
|
func (a *arrayObject) export(ctx *objectExportCtx) interface{} {
|
|
if v, exists := ctx.get(a.val); exists {
|
|
return v
|
|
}
|
|
arr := make([]interface{}, a.length)
|
|
ctx.put(a.val, arr)
|
|
if a.propValueCount == 0 && a.length == uint32(len(a.values)) && uint32(a.objCount) == a.length {
|
|
for i, v := range a.values {
|
|
if v != nil {
|
|
arr[i] = exportValue(v, ctx)
|
|
}
|
|
}
|
|
} else {
|
|
for i := uint32(0); i < a.length; i++ {
|
|
v := a.getIdx(valueInt(i), nil)
|
|
if v != nil {
|
|
arr[i] = exportValue(v, ctx)
|
|
}
|
|
}
|
|
}
|
|
return arr
|
|
}
|
|
|
|
func (a *arrayObject) exportType() reflect.Type {
|
|
return reflectTypeArray
|
|
}
|
|
|
|
func (a *arrayObject) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
|
|
r := a.val.runtime
|
|
if iter := a.getSym(SymIterator, nil); iter == r.getArrayValues() || iter == nil {
|
|
l := toIntStrict(int64(a.length))
|
|
if typ.Kind() == reflect.Array {
|
|
if dst.Len() != l {
|
|
return fmt.Errorf("cannot convert an Array into an array, lengths mismatch (have %d, need %d)", l, dst.Len())
|
|
}
|
|
} else {
|
|
dst.Set(reflect.MakeSlice(typ, l, l))
|
|
}
|
|
ctx.putTyped(a.val, typ, dst.Interface())
|
|
for i := 0; i < l; i++ {
|
|
if i >= len(a.values) {
|
|
break
|
|
}
|
|
val := a.values[i]
|
|
if p, ok := val.(*valueProperty); ok {
|
|
val = p.get(a.val)
|
|
}
|
|
err := r.toReflectValue(val, dst.Index(i), ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("could not convert array element %v to %v at %d: %w", val, typ, i, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
return a.baseObject.exportToArrayOrSlice(dst, typ, ctx)
|
|
}
|
|
|
|
func (a *arrayObject) setValuesFromSparse(items []sparseArrayItem, newMaxIdx int) {
|
|
a.values = make([]Value, newMaxIdx+1)
|
|
for _, item := range items {
|
|
a.values[item.idx] = item.value
|
|
}
|
|
a.objCount = len(items)
|
|
}
|
|
|
|
func toIdx(v valueInt) uint32 {
|
|
if v >= 0 && v < math.MaxUint32 {
|
|
return uint32(v)
|
|
}
|
|
return math.MaxUint32
|
|
}
|