@awayfl/avm2
Version:
Virtual machine for executing AS3 code
343 lines (342 loc) • 14.4 kB
JavaScript
import { Multiname } from '../abc/lazy/Multiname';
import { release, isNumeric, defineNonEnumerableProperty } from '@awayfl/swf-loader';
import { addPrototypeFunctionAlias } from './addPrototypeFunctionAlias';
import { checkValue } from '../run/checkValue';
import { makeMultiname } from './makeMultiname';
import { axCoerceString } from '../run/axCoerceString';
import { qualifyPublicName } from '../nat/qualifyPublicName';
import { axCoerceName } from '../run/axCoerceName';
import { assert } from '@awayjs/graphics';
import { rn } from './rn';
import { Errors } from '../errors';
import { validateCall } from '../run/validateCall';
import { validateConstruct } from '../run/validateConstruct';
var ASObject = /** @class */ (function () {
function ASObject() {
}
ASObject.classInitializer = function () {
var proto = this.dPrototype;
var asProto = ASObject.prototype;
addPrototypeFunctionAlias(proto, '$BghasOwnProperty', asProto.native_hasOwnProperty);
addPrototypeFunctionAlias(proto, '$BgpropertyIsEnumerable', asProto.native_propertyIsEnumerable);
addPrototypeFunctionAlias(proto, '$BgsetPropertyIsEnumerable', asProto.native_setPropertyIsEnumerable);
addPrototypeFunctionAlias(proto, '$BgisPrototypeOf', asProto.native_isPrototypeOf);
addPrototypeFunctionAlias(proto, '$BgtoLocaleString', asProto.toString);
};
/*
constructor() {
// To prevent accidental instantiation of template classes, make sure that we throw
// right during construction.
release;// 80pro: this errors when instancing our Loader class: || checkValue(this);
}
*/
ASObject._init = function () {
// Nop.
};
ASObject.init = function () {
// Nop.
};
ASObject.prototype.native_isPrototypeOf = function (v) {
return this.isPrototypeOf(this.sec.box(v));
};
ASObject.prototype.native_hasOwnProperty = function (nm) {
return this.axHasOwnProperty(makeMultiname(nm));
};
ASObject.prototype.native_propertyIsEnumerable = function (nm) {
var descriptor = Object.getOwnPropertyDescriptor(this, qualifyPublicName(axCoerceString(nm)));
return !!descriptor && descriptor.enumerable;
};
ASObject.prototype.native_setPropertyIsEnumerable = function (nm, enumerable) {
if (enumerable === void 0) { enumerable = true; }
var qualifiedName = qualifyPublicName(axCoerceString(nm));
enumerable = !!enumerable;
var instanceInfo = this.axClass.classInfo.instanceInfo;
if (instanceInfo.isSealed() && this !== this.axClass.dPrototype) {
this.sec.throwError('ReferenceError', Errors.WriteSealedError, nm, instanceInfo.multiname.name);
}
// Silently ignore trait properties.
var descriptor = Object.getOwnPropertyDescriptor(this.axClass.tPrototype, qualifiedName);
if (descriptor && this !== this.axClass.dPrototype) {
return;
}
descriptor = Object.getOwnPropertyDescriptor(this, qualifiedName);
// ... and non-existent properties.
if (!descriptor) {
return;
}
if (descriptor.enumerable !== enumerable) {
descriptor.enumerable = enumerable;
Object.defineProperty(this, qualifiedName, descriptor);
}
};
ASObject.prototype.axResolveMultiname = function (mn) {
if (mn.numeric)
return mn.numericValue;
var s = mn.name;
if (mn.mutable) {
var t = this.traits.getTrait(mn.namespaces, s);
return t ? t.multiname.getMangledName() : '$Bg' + s;
}
else {
var c = mn.resolved[this.axClassName];
if (c)
return c;
var t = this.traits.getTraitMultiname(mn);
var r = t ? t.multiname.getMangledName() : ('$Bg' + s);
mn.resolved[this.axClassName] = r;
return r;
}
};
ASObject.prototype.axHasProperty = function (mn) {
return this.axHasPropertyInternal(mn);
};
ASObject.prototype.axHasPublicProperty = function (nm) {
rn.set(nm);
var result = this.axHasProperty(rn);
//release || assert(rn.name === nm || isNaN(rn.name) && isNaN(nm));
rn.drop();
return result;
};
ASObject.prototype.axSetProperty = function (mn, value, bc) {
//if(typeof value == "number" && isNaN(value))
// console.log("try to set NaN", mn);
//release || checkValue(value);
var name = mn.name;
if (typeof name === 'number' || isNumeric(name = axCoerceName(name))) {
//release || assert(mn.isRuntimeName());
this[+name] = value;
return;
}
var freeze = false;
var t = this.traits.getTrait(mn.namespaces, name);
var mangledName;
if (t) {
mangledName = t.multiname.getMangledName();
switch (t.kind) {
case 1 /* TRAIT.Method */:
this.sec.throwError('ReferenceError', Errors.CannotAssignToMethodError, name, this.axClass.name.name);
// Unreachable because of throwError.
break;
case 2 /* TRAIT.Getter */:
this.sec.throwError('ReferenceError', Errors.ConstWriteError, name, this.axClass.name.name);
// Unreachable because of throwError.
break;
case 4 /* TRAIT.Class */:
case 6 /* TRAIT.Const */:
// Technically, we need to check if the currently running function is the
// initializer of whatever class/package the property is initialized on.
// In practice, we freeze the property after first assignment, causing
// an internal error to be thrown if it's being initialized a second time.
// Invalid bytecode could leave out the assignent during first initialization,
// but it's hard to see how that could convert into real-world problems.
if (bc !== 104 /* Bytecode.INITPROPERTY */) {
this.sec.throwError('ReferenceError', Errors.ConstWriteError, name, this.axClass.name.name);
}
freeze = true;
break;
}
var type = t.getType();
if (type)
value = type.axCoerce(value);
}
else {
mangledName = '$Bg' + name;
}
this[mangledName] = value;
if (freeze) {
Object.defineProperty(this, mangledName, { writable: false });
}
};
ASObject.prototype.axGetProperty = function (mn) {
var name = this.axResolveMultiname(mn);
var value = this[name];
if (typeof value === 'function' && !value.__isClosure)
return this.axGetMethod(name);
//80pro: workaround:
if (typeof value === 'undefined' && typeof name !== 'number') {
return this[mn.name];
}
//release || checkValue(value);
return value;
};
ASObject.prototype.axGetMethod = function (name) {
release || assert(typeof this[name] === 'function');
var cache = this._methodClosureCache;
if (!cache) {
Object.defineProperty(this, '_methodClosureCache', { value: Object.create(null) });
cache = this._methodClosureCache;
}
var method = cache[name];
if (!method) {
method = cache[name] = this.sec.AXMethodClosure.Create(this, this[name]);
}
return method;
};
ASObject.prototype.axGetSuper = function (mn, scope) {
var name = axCoerceName(mn.name);
var namespaces = mn.namespaces;
var trait = scope.parent.object.tPrototype.traits.getTrait(namespaces, name);
var value;
if (trait.kind === 2 /* TRAIT.Getter */ || trait.kind === 7 /* TRAIT.GetterSetter */) {
value = trait.get.call(this);
}
else {
var mangledName = trait.multiname.getMangledName();
var fun = scope.parent.object.tPrototype[mangledName];
if (fun)
return this.sec.AXMethodClosure.Create(this, fun);
}
release || checkValue(value);
return value;
};
ASObject.prototype.axSetSuper = function (mn, scope, value) {
release || checkValue(value);
var name = axCoerceName(mn.name);
var namespaces = mn.namespaces;
var trait = scope.parent.object.tPrototype.traits.getTrait(namespaces, name);
var type = trait.getType();
if (type) {
value = type.axCoerce(value);
}
if (trait.kind === 3 /* TRAIT.Setter */ || trait.kind === 7 /* TRAIT.GetterSetter */) {
trait.set.call(this, value);
}
else {
this[trait.multiname.getMangledName()] = value;
}
};
ASObject.prototype.axDeleteProperty = function (mn) {
// Cannot delete traits.
var name = axCoerceName(mn.name);
var namespaces = mn.namespaces;
if (this.traits.getTrait(namespaces, name)) {
return false;
}
return delete this[mn.getPublicMangledName()];
};
ASObject.prototype.axCallProperty = function (mn, args, isLex) {
var fun = this[this.axResolveMultiname(mn)];
//console.log("call function name:", name);
validateCall(this.sec, fun, args.length);
return fun.axApply(isLex ? null : this, args);
};
ASObject.prototype.axCallSuper = function (mn, scope, args) {
var fun = scope.parent.object.tPrototype[this.axResolveMultiname(mn)];
validateCall(this.sec, fun, args.length);
return fun.axApply(this, args);
};
ASObject.prototype.axConstructProperty = function (mn, args) {
var ctor = this[this.axResolveMultiname(mn)];
validateConstruct(this.sec, ctor, args.length);
return ctor.axConstruct(args);
};
ASObject.prototype.axHasPropertyInternal = function (mn) {
var key = this.axResolveMultiname(mn);
return key in this;
};
ASObject.prototype.axHasOwnProperty = function (mn) {
var name = this.axResolveMultiname(mn);
// We have to check for trait properties too if a simple hasOwnProperty fails.
// This is different to JavaScript's hasOwnProperty behaviour where hasOwnProperty returns
// false for properties defined on the property chain and not on the instance itself.
return this.hasOwnProperty(name) || this.axClass.tPrototype.hasOwnProperty(name);
};
ASObject.prototype.axGetEnumerableKeys = function () {
if (this.sec.isPrimitive(this)) {
return [];
}
var tPrototype = Object.getPrototypeOf(this);
var keys = Object.keys(this);
var result = [];
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (isNumeric(key)) {
result.push(key);
}
else {
if (tPrototype.hasOwnProperty(key)) {
continue;
}
var name_1 = Multiname.stripPublicMangledName(key);
if (name_1 !== undefined) {
result.push(name_1);
}
}
}
return result;
};
ASObject.prototype.axGetPublicProperty = function (nm) {
return this[Multiname.getPublicMangledName(nm)];
};
ASObject.prototype.axSetPublicProperty = function (nm, value) {
release || checkValue(value);
this[Multiname.getPublicMangledName(nm)] = value;
};
ASObject.prototype.axCallPublicProperty = function (nm, argArray) {
return this[Multiname.getPublicMangledName(nm)].axApply(this, argArray);
};
ASObject.prototype.axDeletePublicProperty = function (nm) {
return delete this[Multiname.getPublicMangledName(nm)];
};
ASObject.prototype.axGetSlot = function (i) {
var t = this.traits.getSlot(i);
var value = this[t.multiname.getMangledName()];
release || checkValue(value);
return value;
};
ASObject.prototype.axSetSlot = function (i, value) {
release || checkValue(value);
var t = this.traits.getSlot(i);
var name = t.multiname.getMangledName();
var type = t.getType();
this[name] = type ? type.axCoerce(value) : value;
};
/**
* Gets the next name index of an object. Index |zero| is actually not an
* index, but rather an indicator to start the iteration.
*/
ASObject.prototype.axNextNameIndex = function (index) {
var self = this;
if (index === 0) {
// Gather all enumerable keys since we're starting a new iteration.
defineNonEnumerableProperty(self, 'axEnumerableKeys', self.axGetEnumerableKeys());
}
rn.drop();
var axEnumerableKeys = self.axEnumerableKeys;
while (index < axEnumerableKeys.length) {
var key = axEnumerableKeys[index];
rn.set(key);
if (self.axHasPropertyInternal(rn)) {
release || assert(rn.name === axEnumerableKeys[index]);
return index + 1;
}
index++;
}
return 0;
};
/**
* Gets the nextName after the specified |index|, which you would expect to
* be index + 1, but it's actually index - 1;
*/
ASObject.prototype.axNextName = function (index) {
var self = this;
var axEnumerableKeys = self.axEnumerableKeys;
release || assert(axEnumerableKeys && index > 0 && index < axEnumerableKeys.length + 1);
return axEnumerableKeys[index - 1];
};
ASObject.prototype.axNextValue = function (index) {
return this.axGetPublicProperty(this.axNextName(index));
};
ASObject.prototype.axSetNumericProperty = function (nm, value) {
this.axSetPublicProperty(nm, value);
};
ASObject.prototype.axGetNumericProperty = function (nm) {
return this.axGetPublicProperty(nm);
};
ASObject.forceNativeConstructor = false;
ASObject.forceNativeMethods = false;
ASObject.classSymbols = null;
ASObject.instanceSymbols = null;
return ASObject;
}());
export { ASObject };