UNPKG

@awayfl/avm2

Version:

Virtual machine for executing AS3 code

454 lines (453 loc) 19.5 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 { escapeAttributeValue } from './xml'; import { assert } from '@awayjs/graphics'; import { isNullOrUndefined } from '@awayfl/swf-loader'; import { Namespace } from '../abc/lazy/Namespace'; import { Multiname } from '../abc/lazy/Multiname'; function createNullOrUndefinedDescription(sec, o) { 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, o, flags) { // Class traits aren't returned for numeric primitives, undefined, null, bound methods, or // non-class-constructor functions. var isInt = (o | 0) === o; var nullOrUndefined = isNullOrUndefined(o); if (flags & 512 /* 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 & 512 /* DescribeTypeFlags.USE_ITRAITS */) { return null; } // Non-bound functions with a `receiver` property are bare functions, not ctors. } else if ('receiver' in o) { return null; } } var cls = o.hasOwnProperty('classInfo') ? o : o.axClass; release || assert(cls, 'No class found for object ' + o); var describeClass = cls === o && !(flags & 512 /* DescribeTypeFlags.USE_ITRAITS */); var info = cls.classInfo; var description = 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 & 1 /* CONSTANT.ClassSealed */); description.$BgisFinal = describeClass || !!(info.instanceInfo.flags & 2 /* CONSTANT.ClassFinal */); //TODO: verify that `isStatic` is false for all instances, true for classes description.$BgisStatic = describeClass; if (flags & 256 /* DescribeTypeFlags.INCLUDE_TRAITS */) { description.$Bgtraits = addTraits(cls, info, describeClass, flags); } return description; } var tmpName = new Multiname(null, 0, 7 /* CONSTANT.QName */, [Namespace.PUBLIC], null, null, true); var tmpAttr = new Multiname(null, 0, 13 /* CONSTANT.QNameA */, [Namespace.PUBLIC], null, null, true); export function describeType(sec, value, flags) { // Ensure that the XML classes have been initialized: tmpName.name = 'XML'; var xmlClass = sec.system.getClass(Multiname.FromSimpleName('XML')); var classDescription = describeTypeJSON(sec, value, flags); var x = xmlClass.Create('<type/>'); tmpAttr.name = 'name'; x.setProperty(tmpAttr, classDescription.$Bgname); var 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); var instanceDescription = describeTypeJSON(sec, value, flags | 512 /* DescribeTypeFlags.USE_ITRAITS */); if (instanceDescription) { var e = 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, traits) { var traitsCount = 0; var bases = traits.$Bgbases && traits.$Bgbases.value; for (var i = 0; bases && i < bases.length; i++) { var base = bases[i]; var e = x.sec.AXXML.Create('<extendsClass type="' + escapeAttributeValue(base) + '"/>'); x.appendChild(e); traitsCount++; } var interfaces = traits.$Bginterfaces && traits.$Bginterfaces.value; for (var i = 0; interfaces && i < interfaces.length; i++) { var e = x.sec.AXXML.Create('<implementsInterface type="' + escapeAttributeValue(interfaces[i]) + '"/>'); x.appendChild(e); traitsCount++; } if (traits.$Bgconstructor !== null) { var e = x.sec.AXXML.Create('<constructor/>'); describeParams(e, traits.$Bgconstructor); x.appendChild(e); traitsCount++; } var variables = traits.$Bgvariables && traits.$Bgvariables.value; for (var i = 0; variables && i < variables.length; i++) { var variable = variables[i]; var nodeName = variable.$Bgaccess === 'readonly' ? 'constant' : 'variable'; var e = x.sec.AXXML.Create('<' + nodeName + ' name="' + escapeAttributeValue(variable.$Bgname) + '" type="' + variable.$Bgtype + '"/>'); finishTraitDescription(variable, e, x); traitsCount++; } var accessors = traits.$Bgaccessors && traits.$Bgaccessors.value; for (var i = 0; accessors && i < accessors.length; i++) { var accessor = accessors[i]; var e = 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++; } var methods = traits.$Bgmethods && traits.$Bgmethods.value; for (var i = 0; methods && i < methods.length; i++) { var method = methods[i]; var e = 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, parameters) { if (!parameters) { return; } for (var i = 0; i < parameters.length; i++) { var p = parameters[i]; var f = x.sec.AXXML.Create('<parameter index="' + (i + 1) + '" type="' + escapeAttributeValue(p.$Bgtype) + '" optional="' + p.$Bgoptional + '"/>'); x.appendChild(f); } } function describeMetadataXML(x, metadata_) { if (!metadata_) { return; } var metadata = metadata_.value; for (var i = 0; i < metadata.length; i++) { var md = metadata[i]; var m = x.sec.AXXML.Create('<metadata name="' + escapeAttributeValue(md.$Bgname) + '"/>'); var values = md.$Bgvalue.value; for (var j = 0; j < values.length; j++) { var value = values[j]; var a = x.sec.AXXML.Create('<arg key="' + escapeAttributeValue(value.$Bgkey) + '" value="' + escapeAttributeValue(value.$Bgvalue) + '"/>'); m.appendChild(a); } x.appendChild(m); } } function describeMetadataList(sec, list) { if (!list) { return null; } var result = sec.createArray([]); for (var i = 0; i < list.length; i++) { var metadata = list[i]; var 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, metadata) { var result = sec.createObject(); result.$Bgname = metadata.name; var values = []; result.$Bgvalue = sec.createArray(values); for (var i = 0; i < metadata.keys.length; i++) { var val = sec.createObject(); val.$Bgvalue = metadata.values[i]; val.$Bgkey = metadata.keys[i]; values.push(val); } return result; } function addTraits(cls, info, describingClass, flags) { var sec = cls.sec; var includeBases = flags & 2 /* DescribeTypeFlags.INCLUDE_BASES */; var includeMethods = flags & 32 /* DescribeTypeFlags.INCLUDE_METHODS */ && !describingClass; var obj = sec.createObject(); var variablesVal = obj.$Bgvariables = flags & 8 /* DescribeTypeFlags.INCLUDE_VARIABLES */ ? sec.createArray([]) : null; var accessorsVal = obj.$Bgaccessors = flags & 16 /* DescribeTypeFlags.INCLUDE_ACCESSORS */ ? sec.createArray([]) : null; var metadataList = null; // Somewhat absurdly, class metadata is only included when describing instances. if (flags & 64 /* DescribeTypeFlags.INCLUDE_METADATA */ && !describingClass) { var metadata = 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 & 4 /* DescribeTypeFlags.INCLUDE_INTERFACES */) { obj.$Bginterfaces = sec.createArray([]); if (!describingClass) { var interfacesVal_1 = obj.$Bginterfaces.value; var interfaces = cls.classInfo.instanceInfo.getInterfaces(cls); interfaces.forEach(function (iface) { return interfacesVal_1.push(iface.name.toFQNString(true)); }); } } else { obj.$Bginterfaces = null; } var methodsVal = obj.$Bgmethods = includeMethods ? sec.createArray([]) : null; var basesVal = obj.$Bgbases = includeBases ? sec.createArray([]) : null; var encounteredKeys = Object.create(null); // Needed for accessor-merging. var encounteredGetters = Object.create(null); var encounteredSetters = Object.create(null); var addBase = false; var isInterface = info.instanceInfo.isInterface(); var className; while (cls) { className = cls.classInfo.instanceInfo.multiname.toFQNString(true); if (includeBases && addBase && !describingClass) { basesVal.push(className); } else { addBase = true; } if (flags & 1024 /* 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 & 16 /* DescribeTypeFlags.INCLUDE_ACCESSORS */) { var 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, traits, isInterface) { 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 (var i = 0; i < traits.length; i++) { var t = traits[i]; var mn = t.multiname; var 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 & 1 /* DescribeTypeFlags.HIDE_NSURI_METHODS */ && ns.uri))) { continue; } // Strip the namespace off of interface methods. They're always treated as public. var name_1 = isInterface ? mn.name : mn.toFQNString(true); if (encounteredGetters[name_1] !== encounteredSetters[name_1]) { var val_1 = encounteredKeys[name_1]; val_1.$Bgaccess = 'readwrite'; if (t.kind === 2 /* TRAIT.Getter */) { var type = t.methodInfo.getType(); val_1.$Bgtype = type ? type.name.toFQNString(true) : '*'; } continue; } if (encounteredKeys[name_1]) { continue; } //TODO: check why we have public$$_init in `Object` var val = sec.createObject(); encounteredKeys[name_1] = val; var metadata = t.metadata; switch (t.kind) { case 6 /* TRAIT.Const */: case 0 /* TRAIT.Slot */: { if (!(flags & 8 /* DescribeTypeFlags.INCLUDE_VARIABLES */)) { continue; } val.$Bgname = name_1; val.$Bguri = ns.reflectedURI; var typeName = t.typeName; val.$Bgtype = typeName ? typeName.toFQNString(true) : '*'; val.$Bgaccess = 'readwrite'; val.$Bgmetadata = flags & 64 /* DescribeTypeFlags.INCLUDE_METADATA */ ? describeMetadataList(sec, metadata) : null; variablesVal.push(val); break; } case 1 /* TRAIT.Method */: { if (!includeMethods) { continue; } var returnType = t.methodInfo.getType(); val.$BgreturnType = returnType ? returnType.classInfo.instanceInfo.multiname.toFQNString(true) : '*'; val.$Bgmetadata = flags & 64 /* DescribeTypeFlags.INCLUDE_METADATA */ ? describeMetadataList(sec, metadata) : null; val.$Bgname = name_1; val.$Bguri = ns.reflectedURI; var parametersVal = val.$Bgparameters = sec.createArray([]); var parameters = t.methodInfo.parameters; for (var j = 0; j < parameters.length; j++) { var param = parameters[j]; var 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 2 /* TRAIT.Getter */: case 3 /* TRAIT.Setter */: { if (!(flags & 16 /* DescribeTypeFlags.INCLUDE_ACCESSORS */) || describingClass) { continue; } val.$Bgname = name_1; if (t.kind === 2 /* TRAIT.Getter */) { var returnType = t.methodInfo.getType(); val.$Bgtype = returnType ? returnType.classInfo.instanceInfo.multiname.toFQNString(true) : '*'; encounteredGetters[name_1] = val; } else { var paramType = t.methodInfo.parameters[0].typeName; val.$Bgtype = paramType ? paramType.toFQNString(true) : '*'; encounteredSetters[name_1] = val; } val.$Bgaccess = t.kind === 2 /* TRAIT.Getter */ ? 'readonly' : 'writeonly'; val.$Bgmetadata = flags & 64 /* 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; }