123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490 |
- package otto
- import (
- "encoding/json"
- )
- type objectClass struct {
- getOwnProperty func(*object, string) *property
- getProperty func(*object, string) *property
- get func(*object, string) Value
- canPut func(*object, string) bool
- put func(*object, string, Value, bool)
- hasProperty func(*object, string) bool
- hasOwnProperty func(*object, string) bool
- defineOwnProperty func(*object, string, property, bool) bool
- delete func(*object, string, bool) bool
- enumerate func(*object, bool, func(string) bool)
- clone func(*object, *object, *cloner) *object
- marshalJSON func(*object) json.Marshaler
- }
- func objectEnumerate(obj *object, all bool, each func(string) bool) {
- for _, name := range obj.propertyOrder {
- if all || obj.property[name].enumerable() {
- if !each(name) {
- return
- }
- }
- }
- }
- var classObject,
- classArray,
- classString,
- classArguments,
- classGoStruct,
- classGoMap,
- classGoArray,
- classGoSlice *objectClass
- func init() {
- classObject = &objectClass{
- objectGetOwnProperty,
- objectGetProperty,
- objectGet,
- objectCanPut,
- objectPut,
- objectHasProperty,
- objectHasOwnProperty,
- objectDefineOwnProperty,
- objectDelete,
- objectEnumerate,
- objectClone,
- nil,
- }
- classArray = &objectClass{
- objectGetOwnProperty,
- objectGetProperty,
- objectGet,
- objectCanPut,
- objectPut,
- objectHasProperty,
- objectHasOwnProperty,
- arrayDefineOwnProperty,
- objectDelete,
- objectEnumerate,
- objectClone,
- nil,
- }
- classString = &objectClass{
- stringGetOwnProperty,
- objectGetProperty,
- objectGet,
- objectCanPut,
- objectPut,
- objectHasProperty,
- objectHasOwnProperty,
- objectDefineOwnProperty,
- objectDelete,
- stringEnumerate,
- objectClone,
- nil,
- }
- classArguments = &objectClass{
- argumentsGetOwnProperty,
- objectGetProperty,
- argumentsGet,
- objectCanPut,
- objectPut,
- objectHasProperty,
- objectHasOwnProperty,
- argumentsDefineOwnProperty,
- argumentsDelete,
- objectEnumerate,
- objectClone,
- nil,
- }
- classGoStruct = &objectClass{
- goStructGetOwnProperty,
- objectGetProperty,
- objectGet,
- goStructCanPut,
- goStructPut,
- objectHasProperty,
- objectHasOwnProperty,
- objectDefineOwnProperty,
- objectDelete,
- goStructEnumerate,
- objectClone,
- goStructMarshalJSON,
- }
- classGoMap = &objectClass{
- goMapGetOwnProperty,
- objectGetProperty,
- objectGet,
- objectCanPut,
- objectPut,
- objectHasProperty,
- objectHasOwnProperty,
- goMapDefineOwnProperty,
- goMapDelete,
- goMapEnumerate,
- objectClone,
- nil,
- }
- classGoArray = &objectClass{
- goArrayGetOwnProperty,
- objectGetProperty,
- objectGet,
- objectCanPut,
- objectPut,
- objectHasProperty,
- objectHasOwnProperty,
- goArrayDefineOwnProperty,
- goArrayDelete,
- goArrayEnumerate,
- objectClone,
- nil,
- }
- classGoSlice = &objectClass{
- goSliceGetOwnProperty,
- objectGetProperty,
- objectGet,
- objectCanPut,
- objectPut,
- objectHasProperty,
- objectHasOwnProperty,
- goSliceDefineOwnProperty,
- goSliceDelete,
- goSliceEnumerate,
- objectClone,
- nil,
- }
- }
- // Allons-y
- // 8.12.1.
- func objectGetOwnProperty(obj *object, name string) *property {
- // Return a _copy_ of the prop
- prop, exists := obj.readProperty(name)
- if !exists {
- return nil
- }
- return &prop
- }
- // 8.12.2.
- func objectGetProperty(obj *object, name string) *property {
- prop := obj.getOwnProperty(name)
- if prop != nil {
- return prop
- }
- if obj.prototype != nil {
- return obj.prototype.getProperty(name)
- }
- return nil
- }
- // 8.12.3.
- func objectGet(obj *object, name string) Value {
- if prop := obj.getProperty(name); prop != nil {
- return prop.get(obj)
- }
- return Value{}
- }
- // 8.12.4.
- func objectCanPut(obj *object, name string) bool {
- canPut, _, _ := objectCanPutDetails(obj, name)
- return canPut
- }
- func objectCanPutDetails(obj *object, name string) (canPut bool, prop *property, setter *object) { //nolint:nonamedreturns
- prop = obj.getOwnProperty(name)
- if prop != nil {
- switch propertyValue := prop.value.(type) {
- case Value:
- return prop.writable(), prop, nil
- case propertyGetSet:
- setter = propertyValue[1]
- return setter != nil, prop, setter
- default:
- panic(obj.runtime.panicTypeError("unexpected type %T to Object.CanPutDetails", prop.value))
- }
- }
- if obj.prototype == nil {
- return obj.extensible, nil, nil
- }
- prop = obj.prototype.getProperty(name)
- if prop == nil {
- return obj.extensible, nil, nil
- }
- switch propertyValue := prop.value.(type) {
- case Value:
- if !obj.extensible {
- return false, nil, nil
- }
- return prop.writable(), nil, nil
- case propertyGetSet:
- setter = propertyValue[1]
- return setter != nil, prop, setter
- default:
- panic(obj.runtime.panicTypeError("unexpected type %T to Object.CanPutDetails", prop.value))
- }
- }
- // 8.12.5.
- func objectPut(obj *object, name string, value Value, throw bool) {
- if true {
- // Shortcut...
- //
- // So, right now, every class is using objectCanPut and every class
- // is using objectPut.
- //
- // If that were to no longer be the case, we would have to have
- // something to detect that here, so that we do not use an
- // incompatible canPut routine
- canPut, prop, setter := objectCanPutDetails(obj, name)
- switch {
- case !canPut:
- obj.runtime.typeErrorResult(throw)
- case setter != nil:
- setter.call(toValue(obj), []Value{value}, false, nativeFrame)
- case prop != nil:
- prop.value = value
- obj.defineOwnProperty(name, *prop, throw)
- default:
- obj.defineProperty(name, value, 0o111, throw)
- }
- return
- }
- // The long way...
- //
- // Right now, code should never get here, see above
- if !obj.canPut(name) {
- obj.runtime.typeErrorResult(throw)
- return
- }
- prop := obj.getOwnProperty(name)
- if prop == nil {
- prop = obj.getProperty(name)
- if prop != nil {
- if getSet, isAccessor := prop.value.(propertyGetSet); isAccessor {
- getSet[1].call(toValue(obj), []Value{value}, false, nativeFrame)
- return
- }
- }
- obj.defineProperty(name, value, 0o111, throw)
- return
- }
- switch propertyValue := prop.value.(type) {
- case Value:
- prop.value = value
- obj.defineOwnProperty(name, *prop, throw)
- case propertyGetSet:
- if propertyValue[1] != nil {
- propertyValue[1].call(toValue(obj), []Value{value}, false, nativeFrame)
- return
- }
- if throw {
- panic(obj.runtime.panicTypeError("Object.Put nil second parameter to propertyGetSet"))
- }
- default:
- panic(obj.runtime.panicTypeError("Object.Put unexpected type %T", prop.value))
- }
- }
- // 8.12.6.
- func objectHasProperty(obj *object, name string) bool {
- return obj.getProperty(name) != nil
- }
- func objectHasOwnProperty(obj *object, name string) bool {
- return obj.getOwnProperty(name) != nil
- }
- // 8.12.9.
- func objectDefineOwnProperty(obj *object, name string, descriptor property, throw bool) bool {
- reject := func(reason string) bool {
- if throw {
- panic(obj.runtime.panicTypeError("Object.DefineOwnProperty: %s", reason))
- }
- return false
- }
- prop, exists := obj.readProperty(name)
- if !exists {
- if !obj.extensible {
- return reject("not exists and not extensible")
- }
- if newGetSet, isAccessor := descriptor.value.(propertyGetSet); isAccessor {
- if newGetSet[0] == &nilGetSetObject {
- newGetSet[0] = nil
- }
- if newGetSet[1] == &nilGetSetObject {
- newGetSet[1] = nil
- }
- descriptor.value = newGetSet
- }
- obj.writeProperty(name, descriptor.value, descriptor.mode)
- return true
- }
- if descriptor.isEmpty() {
- return true
- }
- // TODO Per 8.12.9.6 - We should shortcut here (returning true) if
- // the current and new (define) properties are the same
- configurable := prop.configurable()
- if !configurable {
- if descriptor.configurable() {
- return reject("property and descriptor not configurable")
- }
- // Test that, if enumerable is set on the property descriptor, then it should
- // be the same as the existing property
- if descriptor.enumerateSet() && descriptor.enumerable() != prop.enumerable() {
- return reject("property not configurable and enumerable miss match")
- }
- }
- value, isDataDescriptor := prop.value.(Value)
- getSet, _ := prop.value.(propertyGetSet)
- switch {
- case descriptor.isGenericDescriptor():
- // GenericDescriptor
- case isDataDescriptor != descriptor.isDataDescriptor():
- // DataDescriptor <=> AccessorDescriptor
- if !configurable {
- return reject("property descriptor not configurable")
- }
- case isDataDescriptor && descriptor.isDataDescriptor():
- // DataDescriptor <=> DataDescriptor
- if !configurable {
- if !prop.writable() && descriptor.writable() {
- return reject("property not configurable or writeable and descriptor not writeable")
- }
- if !prop.writable() {
- if descriptor.value != nil && !sameValue(value, descriptor.value.(Value)) {
- return reject("property not configurable or writeable and descriptor not the same")
- }
- }
- }
- default:
- // AccessorDescriptor <=> AccessorDescriptor
- newGetSet, _ := descriptor.value.(propertyGetSet)
- presentGet, presentSet := true, true
- if newGetSet[0] == &nilGetSetObject {
- // Present, but nil
- newGetSet[0] = nil
- } else if newGetSet[0] == nil {
- // Missing, not even nil
- newGetSet[0] = getSet[0]
- presentGet = false
- }
- if newGetSet[1] == &nilGetSetObject {
- // Present, but nil
- newGetSet[1] = nil
- } else if newGetSet[1] == nil {
- // Missing, not even nil
- newGetSet[1] = getSet[1]
- presentSet = false
- }
- if !configurable {
- if (presentGet && (getSet[0] != newGetSet[0])) || (presentSet && (getSet[1] != newGetSet[1])) {
- return reject("access descriptor not configurable")
- }
- }
- descriptor.value = newGetSet
- }
- // This section will preserve attributes of
- // the original property, if necessary
- value1 := descriptor.value
- if value1 == nil {
- value1 = prop.value
- } else if newGetSet, isAccessor := descriptor.value.(propertyGetSet); isAccessor {
- if newGetSet[0] == &nilGetSetObject {
- newGetSet[0] = nil
- }
- if newGetSet[1] == &nilGetSetObject {
- newGetSet[1] = nil
- }
- value1 = newGetSet
- }
- mode1 := descriptor.mode
- if mode1&0o222 != 0 {
- // TODO Factor this out into somewhere testable
- // (Maybe put into switch ...)
- mode0 := prop.mode
- if mode1&0o200 != 0 {
- if descriptor.isDataDescriptor() {
- mode1 &= ^0o200 // Turn off "writable" missing
- mode1 |= (mode0 & 0o100)
- }
- }
- if mode1&0o20 != 0 {
- mode1 |= (mode0 & 0o10)
- }
- if mode1&0o2 != 0 {
- mode1 |= (mode0 & 0o1)
- }
- mode1 &= 0o311 // 0311 to preserve the non-setting on "writable"
- }
- obj.writeProperty(name, value1, mode1)
- return true
- }
- func objectDelete(obj *object, name string, throw bool) bool {
- prop := obj.getOwnProperty(name)
- if prop == nil {
- return true
- }
- if prop.configurable() {
- obj.deleteProperty(name)
- return true
- }
- return obj.runtime.typeErrorResult(throw)
- }
- func objectClone(in *object, out *object, clone *cloner) *object {
- *out = *in
- out.runtime = clone.runtime
- if out.prototype != nil {
- out.prototype = clone.object(in.prototype)
- }
- out.property = make(map[string]property, len(in.property))
- out.propertyOrder = make([]string, len(in.propertyOrder))
- copy(out.propertyOrder, in.propertyOrder)
- for index, prop := range in.property {
- out.property[index] = clone.property(prop)
- }
- switch value := in.value.(type) {
- case nativeFunctionObject:
- out.value = value
- case bindFunctionObject:
- out.value = bindFunctionObject{
- target: clone.object(value.target),
- this: clone.value(value.this),
- argumentList: clone.valueArray(value.argumentList),
- }
- case nodeFunctionObject:
- out.value = nodeFunctionObject{
- node: value.node,
- stash: clone.stash(value.stash),
- }
- case argumentsObject:
- out.value = value.clone(clone)
- }
- return out
- }
|