UNPKG

@awayfl/avm2

Version:

Virtual machine for executing AS3 code

475 lines (402 loc) 15.1 kB
import { IMetaobjectProtocol } from '../run/IMetaobjectProtocol'; import { RuntimeTraits } from '../abc/lazy/RuntimeTraits'; import { ClassInfo } from '../abc/lazy/ClassInfo'; import { Multiname } from '../abc/lazy/Multiname'; import { AXSecurityDomain } from '../run/AXSecurityDomain'; import { Scope } from '../run/Scope'; import { AXFunction } from '../run/AXFunction'; import { Bytecode } from '../abc/ops'; import { release, isNumeric, defineNonEnumerableProperty } from '@awayfl/swf-loader'; import { addPrototypeFunctionAlias } from './addPrototypeFunctionAlias'; import { checkValue } from '../run/checkValue'; import { makeMultiname } from './makeMultiname'; import { axCoerceString } from '../run/axCoerceString'; import { qualifyPublicName } from '../nat/qualifyPublicName'; import { axCoerceName } from '../run/axCoerceName'; import { assert } from '@awayjs/graphics'; import { rn } from './rn'; import { Errors } from '../errors'; import { TRAIT } from '../abc/lazy/TRAIT'; import { AXClass } from '../run/AXClass'; import { validateCall } from '../run/validateCall'; import { validateConstruct } from '../run/validateConstruct'; import { AXObject } from '../run/AXObject'; export class ASObject implements IMetaobjectProtocol { traits: RuntimeTraits; sec: AXSecurityDomain; static forceNativeConstructor?: boolean = false; static forceNativeMethods?: boolean = false; // Declare all instance ASObject fields as statics here so that the TS // compiler can convert ASClass class objects to ASObject instances. static traits: RuntimeTraits; static dPrototype: ASObject; static tPrototype: ASObject; protected static _methodClosureCache: any; static classNatives: Object[]; static instanceNatives: Object[]; static sec: AXSecurityDomain; static classSymbols = null; static instanceSymbols = null; static classInfo: ClassInfo; static axResolveMultiname: (mn: Multiname) => any; static axHasProperty: (mn: Multiname) => boolean; static axDeleteProperty: (mn: Multiname) => boolean; static axCallProperty: (mn: Multiname, argArray: any[], isLex: boolean) => any; static axCallSuper: (mn: Multiname, scope: Scope, argArray: any[]) => any; static axConstructProperty: (mn: Multiname, args: any[]) => any; static axHasPropertyInternal: (mn: Multiname) => boolean; static axHasOwnProperty: (mn: Multiname) => boolean; static axSetProperty: (mn: Multiname, value: any, bc: Bytecode) => void; static axInitProperty: (mn: Multiname, value: any) => void; static axGetProperty: (mn: Multiname) => any; static axGetMethod: (name: string) => AXFunction; static axGetSuper: (mn: Multiname, scope: Scope) => any; static axSetSuper: (mn: Multiname, scope: Scope, value: any) => void; static axEnumerableKeys: any[]; static axGetEnumerableKeys: () => any[]; static axHasPublicProperty: (nm: any) => boolean; static axSetPublicProperty: (nm: any, value: any) => void; static axGetPublicProperty: (nm: any) => any; static axCallPublicProperty: (nm: any, argArray: any[]) => any; static axDeletePublicProperty: (nm: any) => boolean; static axSetNumericProperty: (nm: number, value: any) => void; static axGetNumericProperty: (nm: number) => any; static axCoerce: (v: any) => any; static axConstruct: (argArray?: any[]) => any; static axNextNameIndex: (index: number) => number; static axNextName: (index: number) => any; static axNextValue: (index: number) => any; static axGetSlot: (i: number) => any; static axSetSlot: (i: number, value: any) => void; static axIsType: (value: any) => boolean; static getPrototypeOf: () => boolean; static native_isPrototypeOf: (nm: string) => boolean; static native_hasOwnProperty: (nm: string) => boolean; static native_propertyIsEnumerable: (nm: string) => boolean; static native_setPropertyIsEnumerable: (nm: string, enumerable?: boolean) => boolean; static classInitializer() { const proto: any = this.dPrototype; const asProto: any = ASObject.prototype; addPrototypeFunctionAlias(proto, '$BghasOwnProperty', asProto.native_hasOwnProperty); addPrototypeFunctionAlias(proto, '$BgpropertyIsEnumerable', asProto.native_propertyIsEnumerable); addPrototypeFunctionAlias(proto, '$BgsetPropertyIsEnumerable', asProto.native_setPropertyIsEnumerable); addPrototypeFunctionAlias(proto, '$BgisPrototypeOf', asProto.native_isPrototypeOf); addPrototypeFunctionAlias(proto, '$BgtoLocaleString', asProto.toString); } /* constructor() { // To prevent accidental instantiation of template classes, make sure that we throw // right during construction. release;// 80pro: this errors when instancing our Loader class: || checkValue(this); } */ static _init() { // Nop. } static init() { // Nop. } static axClass: any; static axClassName: string; axClass: any; axClassName: string; getPrototypeOf: () => any; native_isPrototypeOf(v: any): boolean { return this.isPrototypeOf(this.sec.box(v)); } native_hasOwnProperty(nm: string): boolean { return this.axHasOwnProperty(makeMultiname(nm)); } native_propertyIsEnumerable(nm: string): boolean { const descriptor = Object.getOwnPropertyDescriptor(this, qualifyPublicName(axCoerceString(nm))); return !!descriptor && descriptor.enumerable; } native_setPropertyIsEnumerable(nm: string, enumerable: boolean = true): void { const qualifiedName = qualifyPublicName(axCoerceString(nm)); enumerable = !!enumerable; const instanceInfo = this.axClass.classInfo.instanceInfo; if (instanceInfo.isSealed() && this !== this.axClass.dPrototype) { this.sec.throwError('ReferenceError', Errors.WriteSealedError, nm, instanceInfo.multiname.name); } // Silently ignore trait properties. let descriptor = Object.getOwnPropertyDescriptor(this.axClass.tPrototype, qualifiedName); if (descriptor && this !== this.axClass.dPrototype) { return; } descriptor = Object.getOwnPropertyDescriptor(this, qualifiedName); // ... and non-existent properties. if (!descriptor) { return; } if (descriptor.enumerable !== enumerable) { descriptor.enumerable = enumerable; Object.defineProperty(this, qualifiedName, descriptor); } } axResolveMultiname(mn: Multiname): any { if (mn.numeric) return mn.numericValue; const s = mn.name; if (mn.mutable) { const t = this.traits.getTrait(mn.namespaces, s); return t ? t.multiname.getMangledName() : '$Bg' + s; } else { const c = mn.resolved[this.axClassName]; if (c) return c; const t = this.traits.getTraitMultiname(mn); const r = t ? t.multiname.getMangledName() : ('$Bg' + s); mn.resolved[this.axClassName] = r; return r; } } axHasProperty(mn: Multiname): boolean { return this.axHasPropertyInternal(mn); } axHasPublicProperty(nm: any): boolean { rn.set(nm); const result = this.axHasProperty(rn); //release || assert(rn.name === nm || isNaN(rn.name) && isNaN(nm)); rn.drop(); return result; } axSetProperty(mn: Multiname, value: any, bc: Bytecode) { //if(typeof value == "number" && isNaN(value)) // console.log("try to set NaN", mn); //release || checkValue(value); let name = mn.name; if (typeof name === 'number' || isNumeric(name = axCoerceName(name))) { //release || assert(mn.isRuntimeName()); this[+name] = value; return; } let freeze = false; const t = this.traits.getTrait(mn.namespaces, name); let mangledName: string; if (t) { mangledName = t.multiname.getMangledName(); switch (t.kind) { case TRAIT.Method: this.sec.throwError('ReferenceError', Errors.CannotAssignToMethodError, name, this.axClass.name.name); // Unreachable because of throwError. break; case TRAIT.Getter: this.sec.throwError('ReferenceError', Errors.ConstWriteError, name, this.axClass.name.name); // Unreachable because of throwError. break; case TRAIT.Class: case TRAIT.Const: // Technically, we need to check if the currently running function is the // initializer of whatever class/package the property is initialized on. // In practice, we freeze the property after first assignment, causing // an internal error to be thrown if it's being initialized a second time. // Invalid bytecode could leave out the assignent during first initialization, // but it's hard to see how that could convert into real-world problems. if (bc !== Bytecode.INITPROPERTY) { this.sec.throwError('ReferenceError', Errors.ConstWriteError, name, this.axClass.name.name); } freeze = true; break; } const type = t.getType(); if (type) value = type.axCoerce(value); } else { mangledName = '$Bg' + name; } this[mangledName] = value; if (freeze) { Object.defineProperty(this, mangledName, { writable: false }); } } axGetProperty(mn: Multiname): any { const name = this.axResolveMultiname(mn); const value = this[name]; if (typeof value === 'function' && !value.__isClosure) return this.axGetMethod(name); //80pro: workaround: if (typeof value === 'undefined' && typeof name !== 'number') { return this[mn.name]; } //release || checkValue(value); return value; } protected _methodClosureCache: any; axGetMethod(name: string): AXFunction { release || assert(typeof this[name] === 'function'); let cache = this._methodClosureCache; if (!cache) { Object.defineProperty(this, '_methodClosureCache', { value: Object.create(null) }); cache = this._methodClosureCache; } let method = cache[name]; if (!method) { method = cache[name] = this.sec.AXMethodClosure.Create(<any> this, this[name]); } return method; } axGetSuper(mn: Multiname, scope: Scope): any { const name = axCoerceName(mn.name); const namespaces = mn.namespaces; const trait = (<AXClass>scope.parent.object).tPrototype.traits.getTrait(namespaces, name); let value; if (trait.kind === TRAIT.Getter || trait.kind === TRAIT.GetterSetter) { value = trait.get.call(this); } else { const mangledName = trait.multiname.getMangledName(); const fun = (<AXClass>scope.parent.object).tPrototype[mangledName]; if (fun) return this.sec.AXMethodClosure.Create(<any> this, fun); } release || checkValue(value); return value; } axSetSuper(mn: Multiname, scope: Scope, value: any) { release || checkValue(value); const name = axCoerceName(mn.name); const namespaces = mn.namespaces; const trait = (<AXClass>scope.parent.object).tPrototype.traits.getTrait(namespaces, name); const type = trait.getType(); if (type) { value = type.axCoerce(value); } if (trait.kind === TRAIT.Setter || trait.kind === TRAIT.GetterSetter) { trait.set.call(this, value); } else { this[trait.multiname.getMangledName()] = value; } } axDeleteProperty(mn: Multiname): any { // Cannot delete traits. const name = axCoerceName(mn.name); const namespaces = mn.namespaces; if (this.traits.getTrait(namespaces, name)) { return false; } return delete this[mn.getPublicMangledName()]; } axCallProperty(mn: Multiname, args: any[], isLex: boolean): any { const fun = this[this.axResolveMultiname(mn)]; //console.log("call function name:", name); validateCall(this.sec, fun, args.length); return fun.axApply(isLex ? null : this, args); } axCallSuper(mn: Multiname, scope: Scope, args: any[]): any { const fun = (<AXClass>scope.parent.object).tPrototype[this.axResolveMultiname(mn)]; validateCall(this.sec, fun, args.length); return fun.axApply(this, args); } axConstructProperty(mn: Multiname, args: any[]): any { const ctor = this[this.axResolveMultiname(mn)]; validateConstruct(this.sec, ctor, args.length); return ctor.axConstruct(args); } axHasPropertyInternal(mn: Multiname): boolean { const key = this.axResolveMultiname(mn); return key in this; } axHasOwnProperty(mn: Multiname): boolean { const name = this.axResolveMultiname(mn); // We have to check for trait properties too if a simple hasOwnProperty fails. // This is different to JavaScript's hasOwnProperty behaviour where hasOwnProperty returns // false for properties defined on the property chain and not on the instance itself. return this.hasOwnProperty(name) || this.axClass.tPrototype.hasOwnProperty(name); } axGetEnumerableKeys(): any[] { if (this.sec.isPrimitive(this)) { return []; } const tPrototype = Object.getPrototypeOf(this); const keys = Object.keys(this); const result = []; for (let i = 0; i < keys.length; i++) { const key = keys[i]; if (isNumeric(key)) { result.push(key); } else { if (tPrototype.hasOwnProperty(key)) { continue; } const name = Multiname.stripPublicMangledName(key); if (name !== undefined) { result.push(name); } } } return result; } axGetPublicProperty(nm: any): any { return this[Multiname.getPublicMangledName(nm)]; } axSetPublicProperty(nm: any, value: any) { release || checkValue(value); this[Multiname.getPublicMangledName(nm)] = value; } axCallPublicProperty(nm: any, argArray: any[]): any { return this[Multiname.getPublicMangledName(nm)].axApply(this, argArray); } axDeletePublicProperty(nm: any): boolean { return delete this[Multiname.getPublicMangledName(nm)]; } axGetSlot(i: number): any { const t = this.traits.getSlot(i); const value = this[t.multiname.getMangledName()]; release || checkValue(value); return value; } axSetSlot(i: number, value: any) { release || checkValue(value); const t = this.traits.getSlot(i); const name = t.multiname.getMangledName(); const type = t.getType(); this[name] = type ? type.axCoerce(value) : value; } /** * Gets the next name index of an object. Index |zero| is actually not an * index, but rather an indicator to start the iteration. */ axNextNameIndex(index: number): number { const self: AXObject = <any> this; if (index === 0) { // Gather all enumerable keys since we're starting a new iteration. defineNonEnumerableProperty(self, 'axEnumerableKeys', self.axGetEnumerableKeys()); } rn.drop(); const axEnumerableKeys = <string[]>self.axEnumerableKeys; while (index < axEnumerableKeys.length) { const key = axEnumerableKeys[index]; rn.set(key); if (self.axHasPropertyInternal(rn)) { release || assert(rn.name === axEnumerableKeys[index]); return index + 1; } index++; } return 0; } /** * Gets the nextName after the specified |index|, which you would expect to * be index + 1, but it's actually index - 1; */ axNextName(index: number): any { const self: AXObject = <any> this; const axEnumerableKeys = self.axEnumerableKeys; release || assert(axEnumerableKeys && index > 0 && index < axEnumerableKeys.length + 1); return axEnumerableKeys[index - 1]; } axNextValue(index: number): any { return this.axGetPublicProperty(this.axNextName(index)); } axSetNumericProperty(nm: number, value: any) { this.axSetPublicProperty(nm, value); } axGetNumericProperty(nm: number): any { return this.axGetPublicProperty(nm); } axEnumerableKeys: any[]; }