UNPKG

@awayfl/avm2

Version:

Virtual machine for executing AS3 code

503 lines (473 loc) 18.6 kB
/* * Copyright 2014 Mozilla Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { release } from '@awayfl/swf-loader'; import { ASXML, escapeAttributeValue } from './xml'; import { assert } from '@awayjs/graphics'; import { ASArray } from '../nat/ASArray'; import { AXSecurityDomain } from '../run/AXSecurityDomain'; import { isNullOrUndefined } from '@awayfl/swf-loader'; import { AXClass } from '../run/AXClass'; import { AXXMLClass } from '../run/AXXMLClass'; import { ClassInfo } from '../abc/lazy/ClassInfo'; import { CONSTANT } from '../abc/lazy/CONSTANT'; import { Namespace } from '../abc/lazy/Namespace'; import { Multiname } from '../abc/lazy/Multiname'; import { MethodTraitInfo } from '../abc/lazy/MethodTraitInfo'; import { TRAIT } from '../abc/lazy/TRAIT'; import { MetadataInfo } from '../abc/lazy/MetadataInfo'; import { SlotTraitInfo } from '../abc/lazy/SlotTraitInfo'; import { TraitInfo } from '../abc/lazy/TraitInfo'; const enum DescribeTypeFlags { HIDE_NSURI_METHODS = 0x0001, INCLUDE_BASES = 0x0002, INCLUDE_INTERFACES = 0x0004, INCLUDE_VARIABLES = 0x0008, INCLUDE_ACCESSORS = 0x0010, INCLUDE_METHODS = 0x0020, INCLUDE_METADATA = 0x0040, INCLUDE_CONSTRUCTOR = 0x0080, INCLUDE_TRAITS = 0x0100, USE_ITRAITS = 0x0200, HIDE_OBJECT = 0x0400 } function createNullOrUndefinedDescription(sec: AXSecurityDomain, o: any): any { return { __proto__: sec.objectPrototype, $Bgname: o === undefined ? 'void' : 'null', $BgisDynamic: false, $BgisFinal: true, $BgisStatic: false, $Bgtraits: { __proto__: sec.objectPrototype, $Bgvariables: null, $Bgaccessors: null, $Bgmetadata: sec.createArray([]), $Bgconstructor: null, $Bginterfaces: sec.createArray([]), $Bgmethods: null, $Bgbases: sec.createArray([]) } }; } export function describeTypeJSON(sec: AXSecurityDomain, o: any, flags: number): any { // Class traits aren't returned for numeric primitives, undefined, null, bound methods, or // non-class-constructor functions. const isInt = (o|0) === o; const nullOrUndefined = isNullOrUndefined(o); if (flags & DescribeTypeFlags.USE_ITRAITS && (nullOrUndefined || isInt)) { return null; } if (nullOrUndefined) { return createNullOrUndefinedDescription(sec, o); } // Use the object's own sec if we're not dealing with a primitive to make sure // type checks are correct. if (o.sec) { sec = o.sec; } o = sec.box(o); if (sec.AXFunction.axIsType(o)) { if (sec.AXMethodClosure.axIsType(o)) { if (flags & DescribeTypeFlags.USE_ITRAITS) { return null; } // Non-bound functions with a `receiver` property are bare functions, not ctors. } else if ('receiver' in o) { return null; } } const cls: AXClass = o.hasOwnProperty('classInfo') ? o : o.axClass; release || assert(cls, 'No class found for object ' + o); const describeClass = cls === o && !(flags & DescribeTypeFlags.USE_ITRAITS); const info: ClassInfo = cls.classInfo; const description: any = sec.createObject(); // For numeric literals that fit into ints, special case the name. if (isInt) { description.$Bgname = 'int'; } else { description.$Bgname = info.instanceInfo.multiname.toFQNString(true); } // More special casing for bound methods. See bug 1057750. description.$BgisDynamic = describeClass || !(info.instanceInfo.flags & CONSTANT.ClassSealed); description.$BgisFinal = describeClass || !!(info.instanceInfo.flags & CONSTANT.ClassFinal); //TODO: verify that `isStatic` is false for all instances, true for classes description.$BgisStatic = describeClass; if (flags & DescribeTypeFlags.INCLUDE_TRAITS) { description.$Bgtraits = addTraits(cls, info, describeClass, flags); } return description; } const tmpName = new Multiname(null, 0, CONSTANT.QName, [Namespace.PUBLIC], null, null, true); const tmpAttr = new Multiname(null, 0, CONSTANT.QNameA, [Namespace.PUBLIC], null, null, true); export function describeType(sec: AXSecurityDomain, value: any, flags: number): ASXML { // Ensure that the XML classes have been initialized: tmpName.name = 'XML'; const xmlClass = <AXXMLClass>sec.system.getClass(Multiname.FromSimpleName('XML')); const classDescription: any = describeTypeJSON(sec, value, flags); const x: ASXML = xmlClass.Create('<type/>'); tmpAttr.name = 'name'; x.setProperty(tmpAttr, classDescription.$Bgname); const bases = classDescription.$Bgtraits.$Bgbases.value; if (bases.length) { tmpAttr.name = 'base'; x.setProperty(tmpAttr, bases[0]); } tmpAttr.name = 'isDynamic'; x.setProperty(tmpAttr, classDescription.$BgisDynamic.toString()); tmpAttr.name = 'isFinal'; x.setProperty(tmpAttr, classDescription.$BgisFinal.toString()); tmpAttr.name = 'isStatic'; x.setProperty(tmpAttr, classDescription.$BgisStatic.toString()); describeTraits(x, classDescription.$Bgtraits); const instanceDescription: any = describeTypeJSON(sec, value, flags | DescribeTypeFlags.USE_ITRAITS); if (instanceDescription) { const e: ASXML = sec.AXXML.Create('<factory/>'); tmpAttr.name = 'type'; e.setProperty(tmpAttr, instanceDescription.$Bgname); if (describeTraits(e, instanceDescription.$Bgtraits)) { x.appendChild(e); } } return x; } function describeTraits(x: ASXML, traits: any): boolean { let traitsCount = 0; const bases = traits.$Bgbases && traits.$Bgbases.value; for (let i = 0; bases && i < bases.length; i++) { const base: string = bases[i]; const e: ASXML = x.sec.AXXML.Create('<extendsClass type="' + escapeAttributeValue(base) + '"/>'); x.appendChild(e); traitsCount++; } const interfaces = traits.$Bginterfaces && traits.$Bginterfaces.value; for (let i = 0; interfaces && i < interfaces.length; i++) { const e: ASXML = x.sec.AXXML.Create('<implementsInterface type="' + escapeAttributeValue(interfaces[i]) + '"/>'); x.appendChild(e); traitsCount++; } if (traits.$Bgconstructor !== null) { const e: ASXML = x.sec.AXXML.Create('<constructor/>'); describeParams(e, traits.$Bgconstructor); x.appendChild(e); traitsCount++; } const variables = traits.$Bgvariables && traits.$Bgvariables.value; for (let i = 0; variables && i < variables.length; i++) { const variable: any = variables[i]; const nodeName = variable.$Bgaccess === 'readonly' ? 'constant' : 'variable'; const e: ASXML = x.sec.AXXML.Create('<' + nodeName + ' name="' + escapeAttributeValue(variable.$Bgname) + '" type="' + variable.$Bgtype + '"/>'); finishTraitDescription(variable, e, x); traitsCount++; } const accessors = traits.$Bgaccessors && traits.$Bgaccessors.value; for (let i = 0; accessors && i < accessors.length; i++) { const accessor: any = accessors[i]; const e: ASXML = x.sec.AXXML.Create('<accessor ' + 'name="' + escapeAttributeValue(accessor.$Bgname) + '" access="' + accessor.$Bgaccess + '" type="' + escapeAttributeValue(accessor.$Bgtype) + '" declaredBy="' + escapeAttributeValue(accessor.$BgdeclaredBy) + '"/>'); finishTraitDescription(accessor, e, x); traitsCount++; } const methods = traits.$Bgmethods && traits.$Bgmethods.value; for (let i = 0; methods && i < methods.length; i++) { const method: any = methods[i]; const e: ASXML = x.sec.AXXML.Create('<method ' + 'name="' + escapeAttributeValue(method.$Bgname) + '" declaredBy="' + escapeAttributeValue(method.$BgdeclaredBy) + '" returnType="' + escapeAttributeValue(method.$BgreturnType) + '"/>'); describeParams(e, method.$Bgparameters.value); finishTraitDescription(method, e, x); traitsCount++; } describeMetadataXML(x, traits.$Bgmetadata); return traitsCount > 0; } function finishTraitDescription(trait, traitXML, traitsListXML) { if (trait.$Bguri !== null) { tmpAttr.name = 'uri'; traitXML.setProperty(tmpAttr, trait.$Bguri); } if (trait.$Bgmetadata !== null) { describeMetadataXML(traitXML, trait.$Bgmetadata); } traitsListXML.appendChild(traitXML); } function describeParams(x: ASXML, parameters: any[]): void { if (!parameters) { return; } for (let i = 0; i < parameters.length; i++) { const p = parameters[i]; const f: ASXML = x.sec.AXXML.Create('<parameter index="' + (i + 1) + '" type="' + escapeAttributeValue(p.$Bgtype) + '" optional="' + p.$Bgoptional + '"/>'); x.appendChild(f); } } function describeMetadataXML(x: ASXML, metadata_: ASArray): void { if (!metadata_) { return; } const metadata: any[] = metadata_.value; for (let i = 0; i < metadata.length; i++) { const md = metadata[i]; const m: ASXML = x.sec.AXXML.Create('<metadata name="' + escapeAttributeValue(md.$Bgname) + '"/>'); const values = md.$Bgvalue.value; for (let j = 0; j < values.length; j++) { const value = values[j]; const a: ASXML = x.sec.AXXML.Create('<arg key="' + escapeAttributeValue(value.$Bgkey) + '" value="' + escapeAttributeValue(value.$Bgvalue) + '"/>'); m.appendChild(a); } x.appendChild(m); } } function describeMetadataList(sec: AXSecurityDomain, list: MetadataInfo[]) { if (!list) { return null; } const result = sec.createArray([]); for (let i = 0; i < list.length; i++) { const metadata = list[i]; const key = metadata.name; // Filter out the [native] metadata nodes. These are implementation details Flash doesn't // expose, so we don't, either. if (key === 'native') { continue; } result.push(describeMetadata(sec, metadata)); } return result; } function describeMetadata(sec: AXSecurityDomain, metadata: MetadataInfo) { const result = sec.createObject(); result.$Bgname = metadata.name; const values = []; result.$Bgvalue = sec.createArray(values); for (let i = 0; i < metadata.keys.length; i++) { const val = sec.createObject(); val.$Bgvalue = metadata.values[i]; val.$Bgkey = metadata.keys[i]; values.push(val); } return result; } function addTraits(cls: AXClass, info: ClassInfo, describingClass: boolean, flags: DescribeTypeFlags) { const sec = cls.sec; const includeBases = flags & DescribeTypeFlags.INCLUDE_BASES; const includeMethods = flags & DescribeTypeFlags.INCLUDE_METHODS && !describingClass; const obj = sec.createObject(); const variablesVal = obj.$Bgvariables = flags & DescribeTypeFlags.INCLUDE_VARIABLES ? sec.createArray([]) : null; const accessorsVal = obj.$Bgaccessors = flags & DescribeTypeFlags.INCLUDE_ACCESSORS ? sec.createArray([]) : null; let metadataList: any[] = null; // Somewhat absurdly, class metadata is only included when describing instances. if (flags & DescribeTypeFlags.INCLUDE_METADATA && !describingClass) { const metadata: MetadataInfo[] = info.trait.metadata; if (metadata) { metadataList = describeMetadataList(sec, metadata); } } // This particular metadata list is always created, even if no metadata exists. obj.$Bgmetadata = metadataList || sec.createArray([]); // TODO: fill in. obj.$Bgconstructor = null; if (flags & DescribeTypeFlags.INCLUDE_INTERFACES) { obj.$Bginterfaces = sec.createArray([]); if (!describingClass) { const interfacesVal = obj.$Bginterfaces.value; const interfaces = cls.classInfo.instanceInfo.getInterfaces(cls); interfaces.forEach((iface) => interfacesVal.push(iface.name.toFQNString(true))); } } else { obj.$Bginterfaces = null; } const methodsVal = obj.$Bgmethods = includeMethods ? sec.createArray([]) : null; const basesVal = obj.$Bgbases = includeBases ? sec.createArray([]) : null; const encounteredKeys: any = Object.create(null); // Needed for accessor-merging. const encounteredGetters: any = Object.create(null); const encounteredSetters: any = Object.create(null); let addBase = false; const isInterface = info.instanceInfo.isInterface(); let className; while (cls) { className = cls.classInfo.instanceInfo.multiname.toFQNString(true); if (includeBases && addBase && !describingClass) { basesVal.push(className); } else { addBase = true; } if (flags & DescribeTypeFlags.HIDE_OBJECT && cls === sec.AXObject) { break; } if (!describingClass) { describeTraits(sec, cls.classInfo.instanceInfo.traits.traits, isInterface); } else { describeTraits(sec, cls.classInfo.traits.traits, isInterface); } cls = cls.superClass; } release || assert(cls === sec.AXObject || isInterface); // When describing Class objects, the bases to add are always Class and Object. if (describingClass) { // When describing Class objects, accessors are ignored. *Except* the `prototype` accessor. if (flags & DescribeTypeFlags.INCLUDE_ACCESSORS) { const val = sec.createObject(); val.$Bgname = 'prototype'; val.$Bgtype = '*'; val.$Bgaccess = 'readonly'; val.$Bgmetadata = null; val.$Bguri = null; val.$BgdeclaredBy = 'Class'; accessorsVal.push(val); } if (includeBases) { basesVal.pop(); basesVal.push('Class', 'Object'); cls = sec.AXClass; } } // Having a hot function closed over isn't all that great, but moving this out would involve // passing lots and lots of arguments. We might do that if performance becomes an issue. function describeTraits(sec: AXSecurityDomain, traits: TraitInfo[], isInterface: boolean) { release || assert(traits, 'No traits array found on class' + cls.name); // All types share some fields, but setting them in one place changes the order in which // they're defined - and hence show up in iteration. While it is somewhat unlikely that // real content relies on that order, tests certainly do, so we duplicate the code. for (let i = 0; i < traits.length; i++) { const t = traits[i]; const mn = t.multiname; const ns = mn.namespace; // Hide all non-public members whose namespace doesn't have a URI specified. // Or, if HIDE_NSURI_METHODS is set, hide those, too, because bugs in Flash. // For interfaces, include all traits. We should've made sure to only have // public methods in them during bytecode parsing/verification. if (!isInterface && (!ns.isPublic() && !ns.uri || (flags & DescribeTypeFlags.HIDE_NSURI_METHODS && ns.uri))) { continue; } // Strip the namespace off of interface methods. They're always treated as public. const name = isInterface ? mn.name : mn.toFQNString(true); if (encounteredGetters[name] !== encounteredSetters[name]) { const val = encounteredKeys[name]; val.$Bgaccess = 'readwrite'; if (t.kind === TRAIT.Getter) { const type = (<MethodTraitInfo>t).methodInfo.getType(); val.$Bgtype = type ? type.name.toFQNString(true) : '*'; } continue; } if (encounteredKeys[name]) { continue; } //TODO: check why we have public$$_init in `Object` const val = sec.createObject(); encounteredKeys[name] = val; const metadata: MetadataInfo[] = t.metadata; switch (t.kind) { case TRAIT.Const: case TRAIT.Slot: { if (!(flags & DescribeTypeFlags.INCLUDE_VARIABLES)) { continue; } val.$Bgname = name; val.$Bguri = ns.reflectedURI; const typeName = (<SlotTraitInfo>t).typeName; val.$Bgtype = typeName ? typeName.toFQNString(true) : '*'; val.$Bgaccess = 'readwrite'; val.$Bgmetadata = flags & DescribeTypeFlags.INCLUDE_METADATA ? describeMetadataList(sec, metadata) : null; variablesVal.push(val); break; } case TRAIT.Method: { if (!includeMethods) { continue; } const returnType = (<MethodTraitInfo>t).methodInfo.getType(); val.$BgreturnType = returnType ? returnType.classInfo.instanceInfo.multiname.toFQNString(true) : '*'; val.$Bgmetadata = flags & DescribeTypeFlags.INCLUDE_METADATA ? describeMetadataList(sec, metadata) : null; val.$Bgname = name; val.$Bguri = ns.reflectedURI; const parametersVal = val.$Bgparameters = sec.createArray([]); const parameters = (<MethodTraitInfo>t).methodInfo.parameters; for (let j = 0; j < parameters.length; j++) { const param = parameters[j]; const paramVal = sec.createObject(); paramVal.$Bgtype = param.typeName ? param.typeName.toFQNString(true) : '*'; paramVal.$Bgoptional = 'value' in param; parametersVal.push(paramVal); } val.$BgdeclaredBy = className; methodsVal.push(val); break; } case TRAIT.Getter: case TRAIT.Setter: { if (!(flags & DescribeTypeFlags.INCLUDE_ACCESSORS) || describingClass) { continue; } val.$Bgname = name; if (t.kind === TRAIT.Getter) { const returnType = (<MethodTraitInfo>t).methodInfo.getType(); val.$Bgtype = returnType ? returnType.classInfo.instanceInfo.multiname.toFQNString(true) : '*'; encounteredGetters[name] = val; } else { const paramType = (<MethodTraitInfo>t).methodInfo.parameters[0].typeName; val.$Bgtype = paramType ? paramType.toFQNString(true) : '*'; encounteredSetters[name] = val; } val.$Bgaccess = t.kind === TRAIT.Getter ? 'readonly' : 'writeonly'; val.$Bgmetadata = flags & DescribeTypeFlags.INCLUDE_METADATA ? describeMetadataList(sec, metadata) : null; val.$Bguri = ns.reflectedURI; val.$BgdeclaredBy = className; accessorsVal.push(val); break; } default: release || assert(false, 'Unknown trait type: ' + t.kind); break; } } } // `methods` and `variables` are the only list that are `null`-ed if empty. if (!methodsVal || methodsVal.value.length === 0) { obj.$Bgmethods = null; } if (!variablesVal || variablesVal.value.length === 0) { obj.$Bgvariables = null; } return obj; }