@awayfl/avm2
Version:
Virtual machine for executing AS3 code
475 lines (402 loc) • 15.1 kB
text/typescript
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[];
}