UNPKG

@awayfl/avm2

Version:

Virtual machine for executing AS3 code

158 lines (129 loc) 4.34 kB
import { AXGlobal } from './AXGlobal'; import { Errors } from '../errors'; import { CONSTANT } from '../abc/lazy/CONSTANT'; import { Multiname } from '../abc/lazy/Multiname'; import { Namespace } from '../abc/lazy/Namespace'; import { AXObject } from './AXObject'; import { IS_AX_CLASS } from './AXClass'; import { ASFunction } from '../nat/ASFunction'; export class Scope { global: Scope; defaultNamespace: Namespace = null; private cache: AXObject[] = []; private static ID = 0; constructor( public readonly parent: Scope, public readonly object: AXObject, public readonly isWith: boolean = false ) { Scope.ID++; this[IS_AX_CLASS] = true; this.global = parent ? parent.global : this; object['__scope__'] = this; } public get superConstructor(): ASFunction | Function { const superCtr = (<any> this.object).superClass; if (!superCtr) { return null; } const r = superCtr[IS_AX_CLASS] ? superCtr.tPrototype.axInitializer : superCtr; // hack! // redefine field. Now it always return precomputed field. Object.defineProperty(this, 'superConstructor', { value: r }); return r; } public extend(object: AXObject) { if (object === this.object) return this; const c = object['__scope__']; if (c && c.parent == this) { return c; } return new Scope(this, object, false); } public findDepth(object: any): number { let current: Scope = this; let depth = 0; while (current) { if (current.object === object) { return depth; } depth++; current = current.parent; } return -1; } public getScopeObjects(): Object [] { const objects = []; let current: Scope = this; while (current) { objects.unshift(current.object); current = current.parent; } return objects; } public getScopeProperty(mn: Multiname, strict: boolean, scopeOnly: boolean): AXObject { return this.findScopeProperty(mn, strict, scopeOnly).axGetProperty(mn); } public findScopeProperty(mn: Multiname, strict: boolean, scopeOnly: boolean): AXObject { if (mn.mutable || scopeOnly) return this._findScopeProperty(mn, strict, scopeOnly); if (mn.scope === this.object && !this.isWith) return mn.value; const value = this._findScopeProperty(mn, strict, scopeOnly); if (!this.isWith) { mn.value = value; mn.scope = this.object; } return value; } private _findScopeProperty(mn: Multiname, strict: boolean, scopeOnly: boolean): AXObject { // Multinames with a `null` name are the any name, '*'. Need to catch those here, because // otherwise we'll get a failing assert in `RuntimeTraits#getTrait` below. if (mn.name === null) { this.global.object.sec.throwError('ReferenceError', Errors.UndefinedVarError, '*'); } let object: AXObject = this.cache[mn.id]; if (object) return object; // Scope lookups should not be trapped by proxies. Except for with scopes, check only trait // properties. if (this.object && this.object[IS_AX_CLASS] && (this.isWith ? this.object.axHasPropertyInternal(mn) : this.object.traits.getTrait(mn.namespaces, mn.name))) { return (this.isWith || mn.isRuntime()) ? this.object : (this.cache[mn.id] = this.object); } if (this.parent) { object = this.parent.findScopeProperty(mn, strict, scopeOnly); if (mn.kind === CONSTANT.QName) { this.cache[mn.id] = object; } return object; } if (scopeOnly) { return null; } // Attributes can't be stored on globals or be directly defined in scripts. if (mn.isAttribute()) { this.object.sec.throwError('ReferenceError', Errors.UndefinedVarError, mn.name); } // If we can't find the property look in the domain. const globalObject: AXGlobal = <AXGlobal> this.global.object; if ((object = globalObject.applicationDomain.findProperty(mn, strict, true))) { return object; } // If we still haven't found it, look for dynamic properties on the global. // No need to do this for non-strict lookups as we'll end up returning the // global anyways. if (strict) { if (!(mn.getPublicMangledName() in globalObject)) { this.global.object.sec.throwError('ReferenceError', Errors.UndefinedVarError, mn.name); } } // Can't find it still, return the global object. return globalObject; } public toString(): string { return '' + this.parent + ' => ' + this.object + ' ' + this.isWith; } }