@awayfl/avm2
Version:
Virtual machine for executing AS3 code
591 lines (590 loc) • 23.9 kB
JavaScript
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 };