UNPKG

@awayfl/avm2

Version:

Virtual machine for executing AS3 code

591 lines (590 loc) 23.9 kB
import { assert } from '@awayjs/graphics'; import { release, unexpected, warning } from '@awayfl/swf-loader'; import { Namespace } from './Namespace'; import { Multiname } from './Multiname'; import { MetadataInfo } from './MetadataInfo'; import { MethodInfo } from './MethodInfo'; import { MethodBodyInfo } from './MethodBodyInfo'; import { ClassInfo } from './ClassInfo'; import { ScriptInfo } from './ScriptInfo'; import { InstanceInfo } from './InstanceInfo'; import { Errors } from '../../errors'; import { internNamespace } from './internNamespace'; import { ParameterInfo } from './ParameterInfo'; import { Traits } from './Traits'; import { SlotTraitInfo } from './SlotTraitInfo'; import { MethodTraitInfo } from './MethodTraitInfo'; import { ClassTraitInfo } from './ClassTraitInfo'; import { ExceptionInfo } from './ExceptionInfo'; import { AbcStream } from '../stream'; var ABCFile = /** @class */ (function () { function ABCFile(env, _buffer) { this._buffer = _buffer; this._deferredMultinames = []; this.env = env; this._stream = new AbcStream(_buffer); this._checkMagic(); this._parseNumericConstants(); this._parseStringConstants(); this._parseNamespaces(); this._parseNamespaceSets(); this._parseMultinames(); this._parseMethodInfos(); this._parseMetaData(); this._parseInstanceAndClassInfos(); this._parseScriptInfos(); this._parseMethodBodyInfos(); } Object.defineProperty(ABCFile.prototype, "applicationDomain", { get: function () { release || assert(this.env.app); return this.env.app; }, enumerable: false, configurable: true }); ABCFile.prototype._parseNumericConstants = function () { var s = this._stream; // Parse Signed Integers var n = s.readU30(); var ints = new Int32Array(n); ints[0] = 0; for (var i = 1; i < n; i++) { ints[i] = s.readS32(); } this.ints = ints; // Parse Unsigned Integers n = s.readU30(); var uints = new Uint32Array(n); uints[0] = 0; for (var i = 1; i < n; i++) { uints[i] = s.readS32(); } this.uints = uints; // Parse Doubles n = s.readU30(); var doubles = new Float64Array(n); doubles[0] = NaN; for (var i = 1; i < n; i++) { doubles[i] = s.readDouble(); } this.doubles = doubles; }; ABCFile.prototype._parseStringConstants = function () { var s = this._stream; var n = s.readU30(); this._strings = new Array(n); this._strings[0] = null; // Record the offset of each string in |stringOffsets|. This array has one extra // element so that we can compute the length of the last string. for (var i = 1; i < n; i++) { var l = s.readU30(); this._strings[i] = s.readUTFString(l); } }; ABCFile.prototype._parseNamespaces = function () { var s = this._stream; var n = s.readU30(); this._namespaces = new Array(n); this._namespaces[0] = Namespace.PUBLIC; for (var i = 1; i < n; i++) { var kind = s.readU8(); var uriIndex = s.readU30(); var uri = this._strings[uriIndex]; var type = void 0; switch (kind) { case 8 /* CONSTANT.Namespace */: case 22 /* CONSTANT.PackageNamespace */: type = 0 /* NamespaceType.Public */; break; case 23 /* CONSTANT.PackageInternalNs */: type = 2 /* NamespaceType.PackageInternal */; break; case 24 /* CONSTANT.ProtectedNamespace */: type = 1 /* NamespaceType.Protected */; break; case 25 /* CONSTANT.ExplicitNamespace */: type = 4 /* NamespaceType.Explicit */; break; case 26 /* CONSTANT.StaticProtectedNs */: type = 5 /* NamespaceType.StaticProtected */; break; case 5 /* CONSTANT.PrivateNs */: type = 3 /* NamespaceType.Private */; break; default: this.applicationDomain.sec.throwError('VerifierError', Errors.CpoolEntryWrongTypeError, i); } if (uri && type !== 3 /* NamespaceType.Private */) { // TODO: deal with API versions here. Those are suffixed to the uri. We used to // just strip them out, but we also had an assert against them occurring at all, // so it might be the case that we don't even need to do anything at all. } else if (uri === null) { // Only private namespaces gets the empty string instead of undefined. A comment // in Tamarin source code indicates this might not be intentional, but oh well. uri = ''; } this._namespaces[i] = internNamespace(type, uri); } }; ABCFile.prototype._parseNamespaceSets = function () { var s = this._stream; var n = s.readU30(); this._namespaceSets = new Array(n); this._namespaceSets[0] = null; for (var i = 1; i < n; i++) { var c = s.readU30(); // Count var nss = this._namespaceSets[i] = new Array(c); for (var j = 0; j < c; j++) { nss[j] = this._namespaces[s.readU30()]; } } }; ABCFile.prototype._parseMultinames = function () { var s = this._stream; var n = s.readU30(); this._multinames = new Array(n); this._multinames[0] = null; for (var i = 1; i < n; i++) { this._multinames[i] = this._parseMultiname(i); } var o = s.position; while (this._deferredMultinames.length) { var _a = this._deferredMultinames.shift(), i = _a[0], o_1 = _a[1]; s.seek(o_1); this._multinames[i] = this._parseMultiname(i); } s.seek(o); }; ABCFile.prototype._parseMultiname = function (i) { var stream = this._stream; var o = stream.position; var namespaceIsRuntime = false; var namespaceIndex; var useNamespaceSet = true; var nameIndex = 0; var kind = stream.readU8(); switch (kind) { case 7 /* CONSTANT.QName */: case 13 /* CONSTANT.QNameA */: namespaceIndex = stream.readU30(); useNamespaceSet = false; nameIndex = stream.readU30(); break; case 15 /* CONSTANT.RTQName */: case 16 /* CONSTANT.RTQNameA */: namespaceIsRuntime = true; nameIndex = stream.readU30(); break; case 17 /* CONSTANT.RTQNameL */: case 18 /* CONSTANT.RTQNameLA */: namespaceIsRuntime = true; break; case 9 /* CONSTANT.Multiname */: case 14 /* CONSTANT.MultinameA */: nameIndex = stream.readU30(); namespaceIndex = stream.readU30(); break; case 27 /* CONSTANT.MultinameL */: case 28 /* CONSTANT.MultinameLA */: namespaceIndex = stream.readU30(); if (!release && namespaceIndex === 0) { // TODO: figure out what to do in this case. What would Tamarin do? warning('Invalid multiname: namespace-set index is 0'); } break; /** * This is undocumented, looking at Tamarin source for this one. */ case 29 /* CONSTANT.TypeName */: { var nameIndex_1 = stream.readU32(); var typeParameterCount = stream.readU32(); if (!release && typeParameterCount !== 1) { // TODO: figure out what to do in this case. What would Tamarin do? warning('Invalid multiname: bad type parameter count ' + typeParameterCount); } var typeParameter = this._multinames[stream.readU32()]; var factory = this._multinames[nameIndex_1]; if (typeParameter == null || factory == null) { this._deferredMultinames.push([i, o]); return; } return new Multiname(this, i, kind, factory.namespaces, factory.name, typeParameter); } default: unexpected(); break; } // A name index of 0 means that it's a runtime name. var name = nameIndex === 0 ? null : this._strings[nameIndex]; var namespaces = namespaceIsRuntime ? null : useNamespaceSet ? this._namespaceSets[namespaceIndex] : [this._namespaces[namespaceIndex]]; return new Multiname(this, i, kind, namespaces, name); }; ABCFile.prototype._checkMagic = function () { var magic = this._stream.readWord(); var flashPlayerBrannan = 46 << 16 | 15; if (magic < flashPlayerBrannan) { this.env.app.sec.throwError('VerifierError', Errors.InvalidMagicError, magic >> 16, magic & 0xffff); } }; /** * String duplicates exist in practice but are extremely rare. */ ABCFile.prototype._checkForDuplicateStrings = function () { var a = []; for (var i = 0; i < this._strings.length; i++) { a.push(this._strings[i]); } a.sort(); for (var i = 0; i < a.length - 1; i++) { if (a[i] === a[i + 1]) { return true; } } return false; }; /** * Returns the string at the specified index in the string table. */ ABCFile.prototype.getString = function (i) { release || assert(i >= 0 && i < this._strings.length); return this._strings[i]; }; /** * Returns the multiname at the specified index in the multiname table. */ ABCFile.prototype.getMultiname = function (i) { if (i < 0 || i >= this._multinames.length) { this.applicationDomain.sec.throwError('VerifierError', Errors.CpoolIndexRangeError, i, this._multinames.length); } return (i !== 0) ? this._multinames[i] || (this._multinames[i] = this._parseMultiname(i)) : null; }; /** * Returns the namespace at the specified index in the namespace table. */ ABCFile.prototype.getNamespace = function (i) { if (i < 0 || i >= this._namespaces.length) { this.applicationDomain.sec.throwError('VerifierError', Errors.CpoolIndexRangeError, i, this._namespaces.length); } return this._namespaces[i]; }; /** * Returns the namespace set at the specified index in the namespace set table. */ ABCFile.prototype.getNamespaceSet = function (i) { if (i < 0 || i >= this._namespaceSets.length) { this.applicationDomain.sec.throwError('VerifierError', Errors.CpoolIndexRangeError, i, this._namespaceSets.length); } return this._namespaceSets[i]; }; ABCFile.prototype._parseMethodInfos = function () { var s = this._stream; var n = s.readU30(); this._methods = new Array(n); for (var i = 0; i < n; ++i) { this._methods[i] = this._parseMethodInfo(i); } }; ABCFile.prototype._parseMethodInfo = function (j) { var s = this._stream; var parameterCount = s.readU30(); var returnType = this._multinames[s.readU30()]; var parameters = new Array(parameterCount); for (var i = 0; i < parameterCount; i++) { parameters[i] = new ParameterInfo(this, this._multinames[s.readU30()]); } var name = this._strings[s.readU30()] || 'anonymous'; var flags = s.readU8(); var optionalCount = 0; if (flags & 8 /* METHOD.HasOptional */) { optionalCount = s.readU30(); release || assert(parameterCount >= optionalCount); for (var i = parameterCount - optionalCount; i < parameterCount; i++) { parameters[i].optionalValueIndex = s.readU30(); parameters[i].optionalValueKind = s.readU8(); } } if (flags & 128 /* METHOD.HasParamNames */) { for (var i = 0; i < parameterCount; i++) { // NOTE: We can't get the parameter name as described in the spec because some SWFs have // invalid parameter names. Tamarin ignores parameter names and so do we. parameters[i].name = this._strings[s.readU30()]; } } return new MethodInfo(this, j, name, returnType, parameters, optionalCount, flags); }; /** * Returns the method info at the specified index in the method info table. */ ABCFile.prototype.getMethodInfo = function (i) { release || assert(i >= 0 && i < this._methods.length); return this._methods[i]; }; ABCFile.prototype.getMethodBodyInfo = function (i) { return this._methodBodies[i]; }; ABCFile.prototype._parseMetaData = function () { var s = this._stream; var n = s.readU30(); this._metadata = new Array(n); for (var i = 0; i < n; i++) { var name_1 = this._strings[s.readU30()]; // Name var itemCount = s.readU30(); // Item Count var keys = new Array(itemCount); for (var j = 0; j < itemCount; j++) { keys[j] = this._strings[s.readU30()]; } var values = new Array(itemCount); for (var j = 0; j < itemCount; j++) { values[j] = this._strings[s.readU30()]; } this._metadata[i] = new MetadataInfo(this, name_1, keys, values); } }; ABCFile.prototype.getMetadataInfo = function (i) { release || assert(i >= 0 && i < this._metadata.length); return this._metadata[i]; }; ABCFile.prototype._parseInstanceAndClassInfos = function () { var s = this._stream; var n = s.readU30(); var instances = this.instances = new Array(n); for (var i = 0; i < n; i++) { instances[i] = this._parseInstanceInfo(); } this._parseClassInfos(n); for (var i = 0; i < n; i++) { instances[i].classInfo = this.classes[i]; } }; ABCFile.prototype._parseInstanceInfo = function () { var s = this._stream; var multiname = this._multinames[s.readU30()]; var superName = this._multinames[s.readU30()]; var flags = s.readU8(); var protectedNs = (flags & 8 /* CONSTANT.ClassProtectedNs */) ? this._namespaces[s.readU30()] : Namespace.PUBLIC; var interfaceCount = s.readU30(); var interfaces = []; for (var i = 0; i < interfaceCount; i++) { interfaces[i] = this._multinames[s.readU30()]; } var methodInfo = this._methods[s.readU30()]; var traits = this._parseTraits(); var instanceInfo = new InstanceInfo(this, multiname, superName, flags, protectedNs, interfaces, methodInfo, traits); traits.attachHolder(instanceInfo); return instanceInfo; }; ABCFile.prototype._parseTraits = function (global) { if (global === void 0) { global = false; } var s = this._stream; var n = s.readU30(); var traits = new Array(n); for (var i = 0; i < n; i++) { traits[i] = this._parseTrait(); } return new Traits(traits, global); }; ABCFile.prototype._parseTrait = function () { var s = this._stream; var multiname = this._multinames[s.readU30()]; var tag = s.readU8(); var kind = tag & 0x0F; var attributes = (tag >> 4) & 0x0F; var trait; switch (kind) { case 0 /* TRAIT.Slot */: case 6 /* TRAIT.Const */: { var slot = s.readU30(); var typeName = this._multinames[s.readU30()]; var valueIndex = s.readU30(); var valueKind = -1; if (valueIndex !== 0) { valueKind = s.readU8(); } trait = new SlotTraitInfo(this, kind, multiname, slot, typeName, valueKind, valueIndex); break; } case 1 /* TRAIT.Method */: case 2 /* TRAIT.Getter */: case 3 /* TRAIT.Setter */: { s.readU30(); // Tamarin optimization. var methodInfo = this._methods[s.readU30()]; trait = methodInfo.trait = new MethodTraitInfo(this, kind, multiname, methodInfo); break; } case 4 /* TRAIT.Class */: { var slot = s.readU30(); var classInfo = this.classes[s.readU30()]; trait = classInfo.trait = new ClassTraitInfo(this, kind, multiname, slot, classInfo); break; } default: this.applicationDomain.sec.throwError('VerifierError', Errors.UnsupportedTraitsKindError, kind); } if (attributes & 4 /* ATTR.Metadata */) { var n = s.readU30(); var metadata = new Array(n); for (var i = 0; i < n; i++) { metadata[i] = this._metadata[s.readU30()]; } trait.metadata = metadata; } return trait; }; ABCFile.prototype._parseClassInfos = function (n) { var classes = this.classes = new Array(n); for (var i = 0; i < n; i++) { classes[i] = this._parseClassInfo(i); } }; ABCFile.prototype._parseClassInfo = function (i) { var methodInfo = this._methods[this._stream.readU30()]; var traits = this._parseTraits(true); var classInfo = new ClassInfo(this, this.instances[i], methodInfo, traits); traits.attachHolder(classInfo); return classInfo; }; ABCFile.prototype._parseScriptInfos = function () { var n = this._stream.readU30(); var scripts = this.scripts = new Array(n); for (var i = 0; i < n; i++) { scripts[i] = this._parseScriptInfo(); } }; ABCFile.prototype._parseScriptInfo = function () { var methodInfo = this._methods[this._stream.readU30()]; var traits = this._parseTraits(true); var scriptInfo = new ScriptInfo(this, methodInfo, traits); traits.attachHolder(scriptInfo); return scriptInfo; }; ABCFile.prototype._parseMethodBodyInfos = function () { var s = this._stream; var methodBodies = this._methodBodies = new Array(this._methods.length); var n = s.readU30(); for (var i = 0; i < n; i++) { var methodInfo = s.readU30(); var maxStack = s.readU30(); var localCount = s.readU30(); var initScopeDepth = s.readU30(); var maxScopeDepth = s.readU30(); var code = s.viewU8s(s.readU30()); var e = s.readU30(); var exceptions = new Array(e); for (var j = 0; j < e; ++j) { exceptions[j] = this._parseException(); } var traits = this._parseTraits(); methodBodies[methodInfo] = new MethodBodyInfo(maxStack, localCount, initScopeDepth, maxScopeDepth, code, exceptions, traits); traits.attachHolder(methodBodies[methodInfo]); } }; ABCFile.prototype._parseException = function () { var s = this._stream; var start = s.readU30(); var end = s.readU30(); var target = s.readU30(); var typeIndex = s.readU30(); var nameIndex = s.readU30(); return new ExceptionInfo(this, start, end, target, this._multinames[nameIndex], this._multinames[typeIndex]); }; ABCFile.prototype.getConstant = function (kind, i) { switch (kind) { case 3 /* CONSTANT.Int */: return this.ints[i]; case 4 /* CONSTANT.UInt */: return this.uints[i]; case 6 /* CONSTANT.Double */: return this.doubles[i]; case 1 /* CONSTANT.Utf8 */: return this._strings[i]; case 11 /* CONSTANT.True */: return true; case 10 /* CONSTANT.False */: return false; case 12 /* CONSTANT.Null */: return null; case 0 /* CONSTANT.Undefined */: return undefined; case 8 /* CONSTANT.Namespace */: case 23 /* CONSTANT.PackageInternalNs */: return this._namespaces[i]; case 7 /* CONSTANT.QName */: case 14 /* CONSTANT.MultinameA */: case 15 /* CONSTANT.RTQName */: case 16 /* CONSTANT.RTQNameA */: case 17 /* CONSTANT.RTQNameL */: case 18 /* CONSTANT.RTQNameLA */: case 19 /* CONSTANT.NameL */: case 20 /* CONSTANT.NameLA */: return this._multinames[i]; case 2 /* CONSTANT.Float */: warning('TODO: CONSTANT.Float may be deprecated?'); break; default: release || assert(false, 'Not Implemented Kind ' + kind); } }; ABCFile.prototype.trace = function (writer) { writer.writeLn('Multinames: ' + this._multinames.length); writer.indent(); for (var i = 0; i < this._multinames.length; i++) { writer.writeLn(i + ' ' + this._multinames[i]); } writer.outdent(); writer.writeLn('Namespace Sets: ' + this._namespaceSets.length); writer.indent(); for (var i = 0; i < this._namespaceSets.length; i++) { writer.writeLn(i + ' ' + this._multinames[i]); } writer.outdent(); writer.writeLn('Namespaces: ' + this._namespaces.length); writer.indent(); for (var i = 0; i < this._namespaces.length; i++) { writer.writeLn(i + ' ' + this._namespaces[i]); } writer.outdent(); writer.writeLn('Strings: ' + this._strings.length); writer.indent(); for (var i = 0; i < this._strings.length; i++) { writer.writeLn(i + ' ' + this._strings[i]); } writer.outdent(); writer.writeLn('MethodInfos: ' + this._methods.length); writer.indent(); for (var i = 0; i < this._methods.length; i++) { writer.writeLn(i + ' ' + this.getMethodInfo(i)); if (this._methodBodies[i]) { this._methodBodies[i].trace(writer); } } writer.outdent(); writer.writeLn('InstanceInfos: ' + this.instances.length); writer.indent(); for (var i = 0; i < this.instances.length; i++) { writer.writeLn(i + ' ' + this.instances[i]); this.instances[i].trace(writer); } writer.outdent(); writer.writeLn('ClassInfos: ' + this.classes.length); writer.indent(); for (var i = 0; i < this.classes.length; i++) { this.classes[i].trace(writer); } writer.outdent(); writer.writeLn('ScriptInfos: ' + this.scripts.length); writer.indent(); for (var i = 0; i < this.scripts.length; i++) { this.scripts[i].trace(writer); } writer.outdent(); }; return ABCFile; }()); export { ABCFile };