UNPKG

@awayfl/avm1

Version:

Virtual machine for executing AS1 and AS2 code

382 lines (381 loc) 13.1 kB
/* * Copyright 2015 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 { __extends } from "tslib"; import { AVM1ArrayNative, AVM1BooleanNative, AVM1NumberNative, AVM1StringNative } from './natives'; import { AVM1Object } from './runtime/AVM1Object'; import { AVM1Function } from './runtime/AVM1Function'; import { AVM1PropertyDescriptor } from './runtime/AVM1PropertyDescriptor'; import { Debug, release, isNullOrUndefined, isIndex } from '@awayfl/swf-loader'; /** * Base class for ActionScript functions with native JavaScript implementation. */ var AVM1NativeFunction = /** @class */ (function (_super) { __extends(AVM1NativeFunction, _super); /** * @param {IAVM1Context} context * @param {Function} fn The native function for regular calling. * @param {Function} ctor The native function for construction. */ function AVM1NativeFunction(context, fn, ctor) { var _this = _super.call(this, context) || this; _this._fn = fn; if (ctor) { _this._ctor = ctor; } return _this; } AVM1NativeFunction.prototype.alConstruct = function (args) { if (!this._ctor) { throw new Error('not a constructor'); } /* eslint-disable-next-line prefer-spread */ return this._ctor.apply(this, args); }; AVM1NativeFunction.prototype.alCall = function (thisArg, args) { if (!this._fn) { throw new Error('not callable'); } return this._fn.apply(thisArg, args); }; return AVM1NativeFunction; }(AVM1Function)); export { AVM1NativeFunction }; /** * Base class the is used for the interpreter. * See {AVM1InterpretedFunction} implementation */ var AVM1EvalFunction = /** @class */ (function (_super) { __extends(AVM1EvalFunction, _super); function AVM1EvalFunction(context) { var _this = _super.call(this, context) || this; var proto = new AVM1Object(context); proto.alPrototype = context.builtins.Object.alGetPrototypeProperty(); proto.alSetOwnProperty('constructor', new AVM1PropertyDescriptor(64 /* AVM1PropertyFlags.DATA */ | 1 /* AVM1PropertyFlags.DONT_ENUM */ | 2 /* AVM1PropertyFlags.DONT_DELETE */)); _this.alSetOwnPrototypeProperty(proto); return _this; } AVM1EvalFunction.prototype.alConstruct = function (args) { var obj = new AVM1Object(this.context); var objPrototype = this.alGetPrototypeProperty(); if (!(objPrototype instanceof AVM1Object)) { objPrototype = this.context.builtins.Object.alGetPrototypeProperty(); } obj.alPrototype = objPrototype; obj.alSetOwnConstructorProperty(this); var result = this.alCall(obj, args); return result instanceof AVM1Object ? result : obj; }; return AVM1EvalFunction; }(AVM1Function)); export { AVM1EvalFunction }; // TODO create classes for the ActionScript errors. function AVM1TypeError(msg) { } AVM1TypeError.prototype = Object.create(Error.prototype); export function alToPrimitive(context, v, preferredType) { var t = typeof v; if (t === 'string' || t === 'number' || t === 'undefined') { return v; } if (!(v instanceof AVM1Object)) { return v; } var obj = v; return preferredType !== undefined ? obj.alDefaultValue(preferredType) : obj.alDefaultValue(); } export function bToRuntimeBool(context, v) { var is5 = context.swfVersion >= 5; //const is7 = context.swfVersion >= 7; if (is5) { return !!v; } return +v; } export function alToBoolean(context, v) { var is7 = context.swfVersion >= 7; switch (typeof v) { case 'undefined': return false; case 'object': return v !== null; case 'boolean': return v; case 'string': { if (is7) { // In files published for Flash Player 7 and later, the result is true if the string has a length // greater than 0; the value is false for an empty string. return !!v; } // In files published for Flash Player 6 and earlier, the string is first converted to a number. // The value is true if the number is not 0, otherwise the return value is false. return !isNaN(+v) && (+v) !== 0; } case 'number': return !!v; default: release || Debug.assert(false); } } export function alToNumber(context, v) { if (typeof v === 'object' && v !== null) { v = alToPrimitive(context, v, 0 /* AVM1DefaultValueHint.NUMBER */); } switch (typeof v) { case 'undefined': return context.swfVersion >= 7 ? NaN : 0; case 'object': if (v === null) { return context.swfVersion >= 7 ? NaN : 0; } // for xml nodes we want to get the nodeValue here if (typeof v.nodeValue !== 'undefined') return parseFloat(v.nodeValue); return context.swfVersion >= 5 ? NaN : 0; case 'boolean': return v ? 1 : 0; case 'number': return v; case 'string': if (v === '' && context.swfVersion < 5) { return 0; } if (v === '') { return NaN; } return +v; default: release || Debug.assert(false); } } export function alToInteger(context, v) { var n = alToNumber(context, v); if (isNaN(n)) { return 0; } if (n === 0 || n === Number.POSITIVE_INFINITY || n === Number.NEGATIVE_INFINITY) { return n; } return n < 0 ? Math.ceil(n) : Math.floor(n); } export function alToInt32(context, v) { var n = alToNumber(context, v); return n | 0; } export function alToString(context, v) { if (typeof v === 'object' && v !== null) { v = alToPrimitive(context, v, 1 /* AVM1DefaultValueHint.STRING */); } switch (typeof v) { case 'undefined': return context.swfVersion >= 7 ? 'undefined' : ''; case 'object': if (v === null) { return 'null'; } if (v && v instanceof Array) { var outputStr = ''; for (var i = 0; i < v.length; i++) { outputStr += alToString(context, v[i]); outputStr += (i === v.length - 1) ? '' : ','; } return outputStr; } return '[type ' + alGetObjectClass(v) + ']'; case 'boolean': return v ? 'true' : 'false'; case 'number': { // int if ((v | 0) === v) { return '' + v; } if (isFinite(v)) { // https://esbench.com/bench/5f888a98b4632100a7dcd403 var e = Math.floor(Math.log10(Math.abs(+v))); if (Math.abs(e) < 14) { var p = Math.pow(10, 14 - e); v = Math.round(v * p) / p; } } return '' + v; } case 'string': return v; default: release || Debug.assert(false); } } export function alIsName(context, v) { return typeof v === 'number' || typeof v === 'string' && (!context.isPropertyCaseSensitive || v === v.toLowerCase()); } export function alToObject(context, v) { switch (typeof v) { case 'undefined': throw new AVM1TypeError(); case 'object': if (v === null) { throw new AVM1TypeError(); } // TODO verify if all objects here are inherited from AVM1Object if (Array.isArray(v)) { return new AVM1ArrayNative(context, v); } return v; case 'boolean': return new AVM1BooleanNative(context, v); case 'number': return new AVM1NumberNative(context, v); case 'string': return new AVM1StringNative(context, v); default: release || Debug.assert(false); } } export function alNewObject(context) { var obj = new AVM1Object(context); obj.alPrototype = context.builtins.Object.alGetPrototypeProperty(); obj.alSetOwnConstructorProperty(context.builtins.Object); return obj; } export function alGetObjectClass(obj) { if (obj instanceof AVM1Function) { return 'Function'; } // TODO more cases return 'Object'; } /** * Non-standard string coercion function roughly matching the behavior of AVM2's axCoerceString. * * This is useful when dealing with AVM2 objects in the implementation of AVM1 builtins: they * frequently expect either a string or `null`, but not `undefined`. */ export function alCoerceString(context, x) { if (x instanceof AVM1Object) return alToString(context, x); if (typeof x === 'string') return x; if (x == undefined) return null; return x + ''; } export function alCoerceNumber(context, x) { if (isNullOrUndefined(x)) { return undefined; } return alToNumber(context, x); } export function alIsIndex(context, p) { if (p instanceof AVM1Object) { return isIndex(alToString(context, p)); } return isIndex(p); } export function alForEachProperty(obj, fn, thisArg) { obj.alGetKeys().forEach(fn, thisArg); } export function alIsFunction(obj) { return obj instanceof AVM1Function; } export function alCallProperty(obj, p, args) { var callable = obj.alGet(p); callable.alCall(obj, args); } export function alInstanceOf(context, obj, cls) { if (!(obj instanceof AVM1Object)) { return false; } if (!(cls instanceof AVM1Object)) { return false; } var proto = cls.alGetPrototypeProperty(); for (var i = obj; i; i = i.alPrototype) { if (i === proto) { return true; } } return false; } export function alIsArray(context, v) { return alInstanceOf(context, v, context.builtins.Array); } export function alIsArrayLike(context, v) { if (!(v instanceof AVM1Object)) { return false; } var length = alToInteger(context, v.alGet('length')); if (isNaN(length) || length < 0 || length >= 4294967296) { return false; } return true; } /* eslint-disable-next-line max-len */ export function alIterateArray(context, arr, fn, thisArg) { if (thisArg === void 0) { thisArg = null; } var length = alToInteger(context, arr.alGet('length')); if (isNaN(length) || length >= 4294967296) { return; } for (var i = 0; i < length; i++) { fn.call(thisArg, arr.alGet(i), i); } } export function alIsString(context, v) { return typeof v === 'string'; } export function alDefineObjectProperties(obj, descriptors) { var context = obj.context; Object.getOwnPropertyNames(descriptors).forEach(function (name) { var desc = descriptors[name]; var value, getter, setter; var flags = 0; if (typeof desc === 'object') { if (desc.get || desc.set) { getter = desc.get ? new AVM1NativeFunction(context, desc.get) : undefined; setter = desc.set ? new AVM1NativeFunction(context, desc.set) : undefined; flags |= 128 /* AVM1PropertyFlags.ACCESSOR */; } else { value = desc.value; if (typeof value === 'function') { value = new AVM1NativeFunction(context, value); } flags |= 64 /* AVM1PropertyFlags.DATA */; if (!desc.writable) { flags |= 4 /* AVM1PropertyFlags.READ_ONLY */; } } if (!desc.enumerable) { flags |= 1 /* AVM1PropertyFlags.DONT_ENUM */; } if (!desc.configurable) { flags |= 2 /* AVM1PropertyFlags.DONT_DELETE */; } } else { value = desc; if (typeof value === 'function') { value = new AVM1NativeFunction(context, value); } flags |= 64 /* AVM1PropertyFlags.DATA */ | 2 /* AVM1PropertyFlags.DONT_DELETE */ | 1 /* AVM1PropertyFlags.DONT_ENUM */ | 4 /* AVM1PropertyFlags.READ_ONLY */; } obj.alSetOwnProperty(name, new AVM1PropertyDescriptor(flags, value, getter, setter)); }); }