Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge pull request #2363 from tschneidereit/avm1-set-variable-fix

Fix AVM1 SetVariable handling of property paths
  • Loading branch information...
commit b1feaedb3b2dfe49588da22e6439049724d56306 2 parents 006fe63 + ee18924
@yurydelendik yurydelendik authored
View
64 src/avm1/interpreter.ts
@@ -69,14 +69,16 @@ module Shumway.AVM1 {
class GlobalPropertiesScope extends AVM1Object {
constructor(context: AVM1Context, thisArg: AVM1Object) {
super(context);
- this.alSetOwnProperty('this', {
- flags: AVM1PropertyFlags.DATA | AVM1PropertyFlags.DONT_ENUM | AVM1PropertyFlags.DONT_DELETE | AVM1PropertyFlags.READ_ONLY,
- value: thisArg
- });
- this.alSetOwnProperty('_global', {
- flags: AVM1PropertyFlags.DATA | AVM1PropertyFlags.DONT_ENUM | AVM1PropertyFlags.DONT_DELETE | AVM1PropertyFlags.READ_ONLY,
- value: context.globals
- })
+ this.alSetOwnProperty('this', new AVM1PropertyDescriptor(AVM1PropertyFlags.DATA |
+ AVM1PropertyFlags.DONT_ENUM |
+ AVM1PropertyFlags.DONT_DELETE |
+ AVM1PropertyFlags.READ_ONLY,
+ thisArg));
+ this.alSetOwnProperty('_global', new AVM1PropertyDescriptor(AVM1PropertyFlags.DATA |
+ AVM1PropertyFlags.DONT_ENUM |
+ AVM1PropertyFlags.DONT_DELETE |
+ AVM1PropertyFlags.READ_ONLY,
+ context.globals));
}
}
@@ -408,11 +410,13 @@ module Shumway.AVM1 {
function as2HasProperty(context: AVM1Context, obj: any, name: any): boolean {
var avm1Obj: AVM1Object = alToObject(context, obj);
+ name = alNormalizeName(context, name);
return avm1Obj.alHasProperty(name);
}
function as2GetProperty(context: AVM1Context, obj: any, name: any): any {
var avm1Obj: AVM1Object = alToObject(context, obj);
+ name = alNormalizeName(context, name);
return avm1Obj.alGet(name);
}
@@ -424,6 +428,7 @@ module Shumway.AVM1 {
function as2DeleteProperty(context: AVM1Context, obj: any, name: any): any {
var avm1Obj: AVM1Object = alToObject(context, obj);
+ name = alNormalizeName(context, name);
var result = avm1Obj.alDeleteProperty(name);
as2SyncEvents(context, name);
return result;
@@ -539,10 +544,9 @@ module Shumway.AVM1 {
obj = cls.alConstruct(args).value;
}
if (obj instanceof AVM1Object) {
- (<AVM1Object>obj).alSetOwnProperty('__constructor__', {
- flags: AVM1PropertyFlags.DATA | AVM1PropertyFlags.DONT_ENUM,
- value: cls
- });
+ var desc = new AVM1PropertyDescriptor(AVM1PropertyFlags.DATA | AVM1PropertyFlags.DONT_ENUM,
+ cls);
+ (<AVM1Object>obj).alSetOwnProperty('__constructor__', desc);
}
return obj;
}
@@ -903,6 +907,7 @@ module Shumway.AVM1 {
}
function avm1ResolveSimpleVariable(scopeList: AVM1ScopeListItem, variableName: string, flags: AVM1ResolveVariableFlags): IAVM1ResolvedVariableResult {
+ release || Debug.assert(alIsName(scopeList.scope.context, variableName));
var currentTarget;
var resolved = cachedResolvedVariableResult;
for (var p = scopeList; p; p = p.previousScopeItem) {
@@ -946,6 +951,12 @@ module Shumway.AVM1 {
// For now it is just very much magical -- designed to pass some of the swfdec tests
// FIXME refactor
release || Debug.assert(variableName);
+ // Canonicalizing the name here is ok even for paths: the only thing that (potentially)
+ // happens is that the name is converted to lower-case, which is always valid for paths.
+ // The original name is saved because the final property name needs to be extracted from
+ // it for property name paths.
+ var originalName = variableName;
+ variableName = alNormalizeName(ectx.context, variableName);
if (!avm1VariableNameHasPath(variableName)) {
return avm1ResolveSimpleVariable(ectx.scopeList, variableName, flags);
}
@@ -1033,8 +1044,9 @@ module Shumway.AVM1 {
}
}
- if (!valueFound) {
- avm1Warn('Unable to resolve ' + propertyName + ' on ' + variableName.substring(q, i - 1) + ' (expr ' + variableName + ')');
+ if (!valueFound && !(flags & AVM1ResolveVariableFlags.WRITE)) {
+ avm1Warn('Unable to resolve ' + propertyName + ' on ' + variableName.substring(q, i - 1) +
+ ' (expr ' + variableName + ')');
return null;
}
@@ -1051,7 +1063,7 @@ module Shumway.AVM1 {
resolved = cachedResolvedVariableResult;
resolved.scope = scope;
- resolved.propertyName = propertyName;
+ resolved.propertyName = originalName.substring(q, i);
resolved.value = (flags & AVM1ResolveVariableFlags.GET_VALUE) ? obj : undefined;
return resolved;
}
@@ -1417,7 +1429,9 @@ module Shumway.AVM1 {
var resolved = avm1ResolveVariable(ectx, variableName,
AVM1ResolveVariableFlags.READ | AVM1ResolveVariableFlags.GET_VALUE);
if (isNullOrUndefined(resolved)) {
- avm1Warn("AVM1 warning: cannot look up variable '" + variableName + "'");
+ if (avm1WarningsEnabled.value) {
+ avm1Warn("AVM1 warning: cannot look up variable '" + variableName + "'");
+ }
return;
}
stack[sp] = resolved.value;
@@ -1428,12 +1442,15 @@ module Shumway.AVM1 {
var value = stack.pop();
var variableName = '' + stack.pop();
var resolved = avm1ResolveVariable(ectx, variableName, AVM1ResolveVariableFlags.WRITE);
- if (isNullOrUndefined(resolved)) {
- avm1Warn("AVM1 warning: cannot look up variable '" + variableName + "'");
+ if (!resolved) {
+ if (avm1WarningsEnabled.value) {
+ avm1Warn("AVM1 warning: cannot look up variable '" + variableName + "'");
+ }
return;
}
- resolved.scope.alPut(variableName, value);
- as2SyncEvents(ectx.context, variableName);
+ release || assert(resolved.propertyName);
+ resolved.scope.alPut(resolved.propertyName, value);
+ as2SyncEvents(ectx.context, resolved.propertyName);
}
function avm1_0x9A_ActionGetURL2(ectx: ExecutionContext, args: any[]) {
var stack = ectx.stack;
@@ -2086,10 +2103,9 @@ module Shumway.AVM1 {
var prototype = constr.alGetPrototypeProperty();
var prototypeSuper = constrSuper.alGetPrototypeProperty();
prototype.alPrototype = prototypeSuper;
- prototype.alSetOwnProperty('__constructor__', {
- flags: AVM1PropertyFlags.DATA | AVM1PropertyFlags.DONT_ENUM,
- value: constrSuper
- });
+ var desc = new AVM1PropertyDescriptor(AVM1PropertyFlags.DATA | AVM1PropertyFlags.DONT_ENUM,
+ constrSuper);
+ prototype.alSetOwnProperty('__constructor__', desc);
}
function avm1_0x2B_ActionCastOp(ectx: ExecutionContext) {
var stack = ectx.stack;
View
84 src/avm1/lib/AVM1Broadcaster.ts
@@ -30,50 +30,46 @@ module Shumway.AVM1.Lib {
}
public static initialize(context: AVM1Context, obj: AVM1Object): void {
- obj.alSetOwnProperty('_listeners', {
- flags: AVM1PropertyFlags.DATA | AVM1PropertyFlags.DONT_ENUM,
- value: new Natives.AVM1ArrayNative(context, [])
- });
- obj.alSetOwnProperty('broadcastMessage', {
- flags: AVM1PropertyFlags.DATA | AVM1PropertyFlags.DONT_ENUM,
- value: new AVM1NativeFunction(context, function broadcastMessage(eventName: string, ...args): void {
- var listenersField = this.alGet('_listeners');
- if (!(listenersField instanceof Natives.AVM1ArrayNative)) {
- return;
- }
- avm1BroadcastEvent(context, this, eventName, args);
- })
- });
- obj.alSetOwnProperty('addListener', {
- flags: AVM1PropertyFlags.DATA | AVM1PropertyFlags.DONT_ENUM,
- value: new AVM1NativeFunction(context, function addListener(listener: any): boolean {
- var listenersField = this.alGet('_listeners');
- if (!(listenersField instanceof Natives.AVM1ArrayNative)) {
- return false;
- }
- var listeners: any[] = (<Natives.AVM1ArrayNative>listenersField).value;
- listeners.push(listener);
- _updateAllSymbolEvents(<any>this);
- return true;
- })
- });
- obj.alSetOwnProperty('removeListener', {
- flags: AVM1PropertyFlags.DATA | AVM1PropertyFlags.DONT_ENUM,
- value: new AVM1NativeFunction(context, function removeListener(listener: any): boolean {
- var listenersField = this.alGet('_listeners');
- if (!(listenersField instanceof Natives.AVM1ArrayNative)) {
- return false;
- }
- var listeners: any[] = (<Natives.AVM1ArrayNative>listenersField).value;
- var i = listeners.indexOf(listener);
- if (i < 0) {
- return false;
- }
- listeners.splice(i, 1);
- _updateAllSymbolEvents(<any>this);
- return true;
- })
- });
+ var desc = new AVM1PropertyDescriptor(AVM1PropertyFlags.DATA | AVM1PropertyFlags.DONT_ENUM,
+ new Natives.AVM1ArrayNative(context, []));
+ obj.alSetOwnProperty('_listeners', desc);
+ desc = new AVM1PropertyDescriptor(AVM1PropertyFlags.DATA | AVM1PropertyFlags.DONT_ENUM,
+ new AVM1NativeFunction(context, function broadcastMessage(eventName: string, ...args): void {
+ var listenersField = this.alGet('_listeners');
+ if (!(listenersField instanceof Natives.AVM1ArrayNative)) {
+ return;
+ }
+ avm1BroadcastEvent(context, this, eventName, args);
+ }));
+ obj.alSetOwnProperty('broadcastMessage', desc);
+ desc = new AVM1PropertyDescriptor(AVM1PropertyFlags.DATA | AVM1PropertyFlags.DONT_ENUM,
+ new AVM1NativeFunction(context, function addListener(listener: any): boolean {
+ var listenersField = this.alGet('_listeners');
+ if (!(listenersField instanceof Natives.AVM1ArrayNative)) {
+ return false;
+ }
+ var listeners: any[] = (<Natives.AVM1ArrayNative>listenersField).value;
+ listeners.push(listener);
+ _updateAllSymbolEvents(<any>this);
+ return true;
+ }));
+ obj.alSetOwnProperty('addListener', desc);
+ desc = new AVM1PropertyDescriptor(AVM1PropertyFlags.DATA | AVM1PropertyFlags.DONT_ENUM,
+ new AVM1NativeFunction(context, function removeListener(listener: any): boolean {
+ var listenersField = this.alGet('_listeners');
+ if (!(listenersField instanceof Natives.AVM1ArrayNative)) {
+ return false;
+ }
+ var listeners: any[] = (<Natives.AVM1ArrayNative>listenersField).value;
+ var i = listeners.indexOf(listener);
+ if (i < 0) {
+ return false;
+ }
+ listeners.splice(i, 1);
+ _updateAllSymbolEvents(<any>this);
+ return true;
+ }));
+ obj.alSetOwnProperty('removeListener', desc);
}
}
}
View
15 src/avm1/lib/AVM1MovieClip.ts
@@ -80,10 +80,7 @@ module Shumway.AVM1.Lib {
}
_lookupChildByName(name: string): AVM1Object {
- release || assert(typeof name === 'string');
- if (!this.context.isPropertyCaseSensitive) {
- name = name.toLowerCase();
- }
+ release || assert(alIsName(this.context, name));
return this._childrenByName[name];
}
@@ -711,10 +708,7 @@ module Shumway.AVM1.Lib {
// Special and children names properties resolutions
private _resolveLevelNProperty(name: string): AVM1MovieClip {
- name = alToString(this.context, name);
- if (!this.context.isPropertyCaseSensitive) {
- name = name.toLowerCase();
- }
+ release || assert(alIsName(this.context, name));
if (name === '_level0') {
return this.context.resolveLevel(0);
} else if (name === '_root') {
@@ -742,12 +736,11 @@ module Shumway.AVM1.Lib {
return this._cachedPropertyResult;
}
- public alGetOwnProperty(p): AVM1PropertyDescriptor {
- var desc = super.alGetOwnProperty(p);
+ public alGetOwnProperty(name): AVM1PropertyDescriptor {
+ var desc = super.alGetOwnProperty(name);
if (desc) {
return desc;
}
- var name = alToString(this.context, p);
if (name[0] === '_') {
var level = this._resolveLevelNProperty(name);
if (level) {
View
10 src/avm1/lib/AVM1System.ts
@@ -35,10 +35,12 @@ module Shumway.AVM1.Lib {
this.alPrototype = context.builtins.Object.alGetPrototypeProperty();
var as3Capabilities = context.sec.flash.system.Capabilities.axClass;
capabilitiesProperties.forEach((name) => {
- this.alSetOwnProperty(name, {
- flags: AVM1PropertyFlags.ACCESSOR | AVM1PropertyFlags.DONT_DELETE | AVM1PropertyFlags.DONT_ENUM,
- get: { alCall: function () { return as3Capabilities.axGetPublicProperty(name); }}
- })
+ var getter = { alCall: function () { return as3Capabilities.axGetPublicProperty(name); }};
+ var desc = new AVM1PropertyDescriptor(AVM1PropertyFlags.ACCESSOR |
+ AVM1PropertyFlags.DONT_DELETE |
+ AVM1PropertyFlags.DONT_ENUM,
+ null, getter);
+ this.alSetOwnProperty(name, desc);
}, this);
}
}
View
31 src/avm1/lib/AVM1Utils.ts
@@ -126,7 +126,9 @@ module Shumway.AVM1.Lib {
var observer = this;
var context: AVM1Context = (<any>this).context;
events.forEach(function (event: AVM1EventHandler) {
- var propertyName = normalizeEventName(context, event.propertyName);
+ // Normalization will always stay valid in a player instance, so we can safely modify
+ // the event itself, here.
+ var propertyName = event.propertyName = normalizeEventName(context, event.propertyName);
eventsMap[propertyName] = event;
context.registerEventPropertyObserver(propertyName, observer);
observer._updateEvent(event);
@@ -698,31 +700,32 @@ module Shumway.AVM1.Lib {
var setterName = 'set' + memberName[0].toUpperCase() + memberName.slice(1, -1);
var setter = obj[setterName];
release || Debug.assert(getter || setter, 'define getter or setter')
- wrap.alSetOwnProperty(memberName.slice(0, -1), {
- flags: AVM1PropertyFlags.ACCESSOR | AVM1PropertyFlags.DONT_DELETE | AVM1PropertyFlags.DONT_ENUM,
- get: wrapFunction(getter),
- set: wrapFunction(setter)
- })
+ var desc = new AVM1PropertyDescriptor(AVM1PropertyFlags.ACCESSOR |
+ AVM1PropertyFlags.DONT_DELETE |
+ AVM1PropertyFlags.DONT_ENUM,
+ null, wrapFunction(getter), wrapFunction(setter));
+ wrap.alSetOwnProperty(memberName.slice(0, -1), desc);
return;
}
- var desc = getMemberDescriptor(memberName);
- if (!desc) {
+ var nativeDesc = getMemberDescriptor(memberName);
+ if (!nativeDesc) {
return;
}
- if (desc.get || desc.set) {
+ if (nativeDesc.get || nativeDesc.set) {
release || Debug.assert(false, 'Redefine ' + memberName + ' property getter/setter as functions');
return;
}
- var value = desc.value;
+ var value = nativeDesc.value;
if (typeof value === 'function') {
value = wrapFunction(value);
}
- wrap.alSetOwnProperty(memberName, {
- flags: AVM1PropertyFlags.DATA | AVM1PropertyFlags.DONT_DELETE | AVM1PropertyFlags.DONT_ENUM,
- value: value
- })
+ var desc = new AVM1PropertyDescriptor(AVM1PropertyFlags.DATA |
+ AVM1PropertyFlags.DONT_DELETE |
+ AVM1PropertyFlags.DONT_ENUM,
+ value);
+ wrap.alSetOwnProperty(memberName, desc);
});
}
View
14 src/avm1/lib/AVM1XML.ts
@@ -89,10 +89,10 @@ module Shumway.AVM1.Lib {
constructor(context: AVM1Context, as3XMLNode: flash.xml.XMLNode) {
super(context);
this._as3XMLNode = as3XMLNode;
- this._cachedNodePropertyDescriptor = {
- flags: AVM1PropertyFlags.DATA | AVM1PropertyFlags.DONT_DELETE | AVM1PropertyFlags.READ_ONLY,
- value: undefined
- };
+ this._cachedNodePropertyDescriptor = new AVM1PropertyDescriptor(AVM1PropertyFlags.DATA |
+ AVM1PropertyFlags.DONT_DELETE |
+ AVM1PropertyFlags.READ_ONLY,
+ undefined);
alDefineObjectProperties(this, {
length: {
get: this.getLength
@@ -127,10 +127,8 @@ module Shumway.AVM1.Lib {
super(context);
this.alPrototype = context.builtins.Object.alGetPrototypeProperty();
this._as3Attributes = as3Attributes;
- this._cachedNodePropertyDescriptor = {
- flags: AVM1PropertyFlags.DATA,
- value: undefined
- };
+ this._cachedNodePropertyDescriptor = new AVM1PropertyDescriptor(AVM1PropertyFlags.DATA,
+ undefined);
}
public alGetOwnProperty(p): AVM1PropertyDescriptor {
View
13 src/avm1/natives.ts
@@ -89,11 +89,8 @@ module Shumway.AVM1.Natives {
if (desc && !!(desc.flags & AVM1PropertyFlags.DONT_DELETE)) {
return false; // protected property
}
- this.alSetOwnProperty(name, {
- flags: AVM1PropertyFlags.ACCESSOR,
- get: getter,
- set: setter || undefined
- });
+ this.alSetOwnProperty(name, new AVM1PropertyDescriptor(AVM1PropertyFlags.ACCESSOR, null,
+ getter, setter || undefined));
return true;
}
@@ -603,10 +600,8 @@ module Shumway.AVM1.Natives {
// Array natives
- var cachedArrayPropertyDescriptor: AVM1PropertyDescriptor = {
- flags: AVM1PropertyFlags.DATA,
- value: undefined
- };
+ var cachedArrayPropertyDescriptor = new AVM1PropertyDescriptor(AVM1PropertyFlags.DATA,
+ undefined);
export class AVM1ArrayNative extends AVM1Object {
public value: any[];
View
221 src/avm1/runtime.ts
@@ -52,15 +52,17 @@ module Shumway.AVM1 {
userData: any;
}
- export interface AVM1PropertyDescriptor {
- flags: AVM1PropertyFlags;
- value?: any;
- get?: IAVM1Callable;
- set?: IAVM1Callable;
- watcher?: IAVM1PropertyWatcher;
+ export class AVM1PropertyDescriptor {
+ public originalName: string;
+ constructor(public flags: AVM1PropertyFlags,
+ public value?: any,
+ public get?: IAVM1Callable,
+ public set?: IAVM1Callable,
+ public watcher?: IAVM1PropertyWatcher) {
+ // Empty block
+ }
}
- var ESCAPED_PROPERTY_PREFIX = '__avm1';
var DEBUG_PROPERTY_PREFIX = '$Bg';
export interface IAVM1Builtins {
@@ -106,11 +108,15 @@ module Shumway.AVM1 {
// Using IAVM1Callable here to avoid circular calls between AVM1Object and
// AVM1Function during constructions.
// TODO do we need to support __proto__ for all SWF versions?
- this.alSetOwnProperty('__proto__', {
- flags: AVM1PropertyFlags.ACCESSOR | AVM1PropertyFlags.DONT_DELETE | AVM1PropertyFlags.DONT_ENUM,
- get: { alCall: function (thisArg: any, args?: any[]): any { return self.alPrototype; }},
- set: { alCall: function (thisArg: any, args?: any[]): any { self.alPrototype = args[0]; }}
- });
+ var getter = { alCall: function (thisArg: any, args?: any[]): any { return self.alPrototype; }};
+ var setter = { alCall: function (thisArg: any, args?: any[]): any { self.alPrototype = args[0]; }};
+ var desc = new AVM1PropertyDescriptor(AVM1PropertyFlags.ACCESSOR |
+ AVM1PropertyFlags.DONT_DELETE |
+ AVM1PropertyFlags.DONT_ENUM,
+ null,
+ getter,
+ setter);
+ this.alSetOwnProperty('__proto__', desc);
}
get alPrototype(): AVM1Object {
@@ -130,46 +136,25 @@ module Shumway.AVM1 {
this._prototype = v;
}
- public alGetPrototypeProperty(): any {
+ public alGetPrototypeProperty(): AVM1Object {
return this.alGet('prototype');
}
// TODO shall we add mode for readonly/native flags of the prototype property?
public alSetOwnPrototypeProperty(v: any): void {
- this.alSetOwnProperty('prototype', {
- flags: AVM1PropertyFlags.DATA | AVM1PropertyFlags.DONT_ENUM,
- value: v
- });
+ this.alSetOwnProperty('prototype', new AVM1PropertyDescriptor(AVM1PropertyFlags.DATA |
+ AVM1PropertyFlags.DONT_ENUM,
+ v));
}
- public alGetConstructorProperty(): any {
+ public alGetConstructorProperty(): AVM1Object {
return this.alGet('__constructor__');
}
public alSetOwnConstructorProperty(v: any): void {
- this.alSetOwnProperty('__constructor__', {
- flags: AVM1PropertyFlags.DATA | AVM1PropertyFlags.DONT_ENUM,
- value: v
- });
- }
-
- _escapeProperty(p: any): string {
- var context = this.context;
- var name = alToString(context, p);
- if (!context.isPropertyCaseSensitive) {
- name = name.toLowerCase();
- }
- if (name[0] === '_') {
- name = ESCAPED_PROPERTY_PREFIX + name
- }
- return name;
- }
-
- _unescapeProperty(name: string): string {
- if (name[0] === '_' && name.indexOf(ESCAPED_PROPERTY_PREFIX) === 0) {
- name = name.substring(ESCAPED_PROPERTY_PREFIX.length);
- }
- return name;
+ this.alSetOwnProperty('__constructor__', new AVM1PropertyDescriptor(AVM1PropertyFlags.DATA |
+ AVM1PropertyFlags.DONT_ENUM,
+ v));
}
_debugEscapeProperty(p: any): string {
@@ -181,31 +166,40 @@ module Shumway.AVM1 {
return DEBUG_PROPERTY_PREFIX + name;
}
- public alGetOwnProperty(p): AVM1PropertyDescriptor {
- var name = this._escapeProperty(p);
+ public alGetOwnProperty(name): AVM1PropertyDescriptor {
+ release || Debug.assert(alIsName(this.context, name));
// TODO __resolve
return this._ownProperties[name];
}
public alSetOwnProperty(p, desc: AVM1PropertyDescriptor): void {
- var name = this._escapeProperty(p);
- this._ownProperties[name] = desc;
- if (!release) { // adding data property on the main object for convenience of debugging
+ var name = alNormalizeName(this.context, p);
+ if (!desc.originalName && !this.context.isPropertyCaseSensitive) {
+ desc.originalName = p;
+ }
+ if (!release) {
+ Debug.assert(desc instanceof AVM1PropertyDescriptor);
+ // Ensure that a descriptor isn't used multiple times. If it were, we couldn't update
+ // values in-place.
+ Debug.assert(!desc['owningObject'] || desc['owningObject'] === this);
+ desc['owningObject'] = this;
+ // adding data property on the main object for convenience of debugging.
if ((desc.flags & AVM1PropertyFlags.DATA) &&
!(desc.flags & AVM1PropertyFlags.DONT_ENUM)) {
- Object.defineProperty(this, this._debugEscapeProperty(p),
+ Object.defineProperty(this, this._debugEscapeProperty(name),
{value: desc.value, enumerable: true, configurable: true});
}
}
+ this._ownProperties[name] = desc;
}
public alHasOwnProperty(p): boolean {
- var name = this._escapeProperty(p);
+ var name = alNormalizeName(this.context, p);
return !!this._ownProperties[name];
}
public alDeleteOwnProperty(p) {
- var name = this._escapeProperty(p);
+ var name = alNormalizeName(this.context, p);
delete this._ownProperties[name];
if (!release) {
delete this[this._debugEscapeProperty(p)];
@@ -214,11 +208,20 @@ module Shumway.AVM1 {
public alGetOwnPropertiesKeys(): string[] {
var keys: string[] = [];
- for (var i in this._ownProperties) {
- var desc = this._ownProperties[i];
- if (!(desc.flags & AVM1PropertyFlags.DONT_ENUM)) {
- var name = this._unescapeProperty(i);
- keys.push(name);
+ if (!this.context.isPropertyCaseSensitive) {
+ for (var name in this._ownProperties) {
+ var desc = this._ownProperties[name];
+ release || Debug.assert("originalName" in desc);
+ if (!(desc.flags & AVM1PropertyFlags.DONT_ENUM)) {
+ keys.push(desc.originalName);
+ }
+ }
+ } else {
+ for (var name in this._ownProperties) {
+ var desc = this._ownProperties[name];
+ if (!(desc.flags & AVM1PropertyFlags.DONT_ENUM)) {
+ keys.push(name);
+ }
}
}
return keys;
@@ -268,6 +271,10 @@ module Shumway.AVM1 {
}
public alPut(p, v) {
+ // Perform all lookups with the canonicalized name, but keep the original name around to
+ // pass it to `alSetOwnProperty`, which stores it on the descriptor.
+ var originalName = p;
+ p = alNormalizeName(this.context, p);
if (!this.alCanPut(p)) {
return;
}
@@ -277,11 +284,12 @@ module Shumway.AVM1 {
v = ownDesc.watcher.callback.alCall(this,
[ownDesc.watcher.name, ownDesc.value, v, ownDesc.watcher.userData]);
}
- var newDesc: AVM1PropertyDescriptor = {
- flags: ownDesc.flags,
- value: v
- };
- this.alSetOwnProperty(p, newDesc);
+ // Real properties (i.e., not things like "_root" on MovieClips) can be updated in-place.
+ if (p in this._ownProperties) {
+ ownDesc.value = v;
+ } else {
+ this.alSetOwnProperty(originalName, new AVM1PropertyDescriptor(ownDesc.flags, v));
+ }
return;
}
var desc = this.alGetProperty(p);
@@ -300,11 +308,8 @@ module Shumway.AVM1 {
v = desc.watcher.callback.alCall(this,
[desc.watcher.name, desc.value, v, desc.watcher.userData]);
}
- var newDesc: AVM1PropertyDescriptor = {
- flags: desc ? desc.flags : AVM1PropertyFlags.DATA,
- value: v
- };
- this.alSetOwnProperty(p, newDesc);
+ var newDesc = new AVM1PropertyDescriptor(desc ? desc.flags : AVM1PropertyFlags.DATA, v);
+ this.alSetOwnProperty(originalName, newDesc);
}
}
@@ -351,24 +356,24 @@ module Shumway.AVM1 {
public alDefaultValue(hint: AVM1DefaultValueHint = AVM1DefaultValueHint.NUMBER): any {
if (hint === AVM1DefaultValueHint.STRING) {
- var toString = this.alGet('toString');
+ var toString = this.alGet(alNormalizeName(this.context, 'toString'));
if (alIsFunction(toString)) {
var str = toString.alCall(this);
return str;
}
- var valueOf = this.alGet('valueOf');
+ var valueOf = this.alGet(alNormalizeName(this.context, 'valueOf'));
if (alIsFunction(valueOf)) {
var val = valueOf.alCall(this);
return val;
}
} else {
release || Debug.assert(hint === AVM1DefaultValueHint.NUMBER);
- var valueOf = this.alGet('valueOf');
+ var valueOf = this.alGet(alNormalizeName(this.context, 'valueOf'));
if (alIsFunction(valueOf)) {
var val = valueOf.alCall(this);
return val;
}
- var toString = this.alGet('toString');
+ var toString = this.alGet(alNormalizeName(this.context, 'toString'));
if (alIsFunction(toString)) {
var str = toString.alCall(this);
return str;
@@ -392,14 +397,37 @@ module Shumway.AVM1 {
// Merging two keys sets
// TODO check if we shall worry about __proto__ usage here
- var processed = Object.create(null);
- for (var i = 0; i < ownKeys.length; i++) {
- processed[ownKeys[i]] = true;
- }
- for (var i = 0; i < otherKeys.length; i++) {
- processed[otherKeys[i]] = true;
+ var context = this.context;
+ // If the context is case-insensitive, names only differing in their casing overwrite each
+ // other. Iterating over the keys returns the first original, case-preserved key that was
+ // ever used for the property, though.
+ if (!context.isPropertyCaseSensitive) {
+ var keyLists = [ownKeys, otherKeys];
+ var canonicalKeysMap = Object.create(null);
+ var keys = [];
+ for (var k = 0; k < keyLists.length; k++) {
+ var keyList = keyLists[k];
+ for (var i = keyList.length; i--;) {
+ var key = keyList[i];
+ var canonicalKey = alNormalizeName(context, key);
+ if (canonicalKeysMap[canonicalKey]) {
+ continue;
+ }
+ canonicalKeysMap[canonicalKey] = true;
+ keys.push(key);
+ }
+ }
+ return keys;
+ } else {
+ var processed = Object.create(null);
+ for (var i = 0; i < ownKeys.length; i++) {
+ processed[ownKeys[i]] = true;
+ }
+ for (var i = 0; i < otherKeys.length; i++) {
+ processed[otherKeys[i]] = true;
+ }
+ return Object.getOwnPropertyNames(processed);
}
- return Object.getOwnPropertyNames(processed);
}
}
@@ -478,9 +506,9 @@ module Shumway.AVM1 {
super(context);
var proto = new AVM1Object(context);
proto.alPrototype = context.builtins.Object.alGetPrototypeProperty();
- proto.alSetOwnProperty('constructor', {
- flags: AVM1PropertyFlags.DATA | AVM1PropertyFlags.DONT_ENUM | AVM1PropertyFlags.DONT_DELETE
- });
+ proto.alSetOwnProperty('constructor', new AVM1PropertyDescriptor(AVM1PropertyFlags.DATA |
+ AVM1PropertyFlags.DONT_ENUM |
+ AVM1PropertyFlags.DONT_DELETE));
this.alSetOwnPrototypeProperty(proto);
}
public alConstruct(args?: any[]): AVM1Object {
@@ -591,6 +619,34 @@ module Shumway.AVM1 {
}
}
+ var nameCache = Object.create(null);
+
+ /**
+ * Normalize the name according to the current AVM1Context's settings.
+ *
+ * This means converting it to lower-case for SWF versions below 7, and doing nothing otherwise.
+ */
+ export function alNormalizeName(context: IAVM1Context, v): string {
+ var name;
+ if (typeof v === 'string' && (name = nameCache[v])) {
+ return name;
+ }
+ name = alToString(context, v);
+ if (!context.isPropertyCaseSensitive) {
+ name = name.toLowerCase();
+ }
+ if (typeof v === 'string') {
+ nameCache[v] = name;
+ }
+ return name;
+ }
+
+ export function alIsName(context: IAVM1Context, v): boolean {
+ return typeof v === 'number' ||
+ typeof v === 'string' &&
+ (context.isPropertyCaseSensitive || v === v.toLowerCase());
+ }
+
export function alToObject(context: IAVM1Context, v): AVM1Object {
switch (typeof v) {
case 'undefined':
@@ -751,14 +807,7 @@ module Shumway.AVM1 {
flags |= AVM1PropertyFlags.DATA | AVM1PropertyFlags.DONT_DELETE |
AVM1PropertyFlags.DONT_ENUM | AVM1PropertyFlags.READ_ONLY;
}
- obj.alSetOwnProperty(name, getter || setter ? {
- flags: flags,
- get: getter,
- set: setter
- } : {
- flags: flags,
- value: value
- })
+ obj.alSetOwnProperty(name, new AVM1PropertyDescriptor(flags, value, getter, setter));
});
}
}
View
BIN  test/swfs/avm1/property-paths/property-paths-6.swf
Binary file not shown
View
6 test/swfs/avm1/property-paths/property-paths-6.swf.trace
@@ -0,0 +1,6 @@
+2
+TeSt
+1
+tEst
+1
+tEst
View
BIN  test/swfs/avm1/property-paths/property-paths-7.swf
Binary file not shown
View
7 test/swfs/avm1/property-paths/property-paths-7.swf.trace
@@ -0,0 +1,7 @@
+undefined
+tEst
+TeSt
+undefined
+TeSt
+tEst
+undefined
View
BIN  test/swfs/avm1/property-paths/property-paths.fla
Binary file not shown
View
BIN  test/swfs/avm1/propertycase/propertycase-preserving-6.swf
Binary file not shown
View
2  test/swfs/avm1/propertycase/propertycase-preserving-6.swf.trace
@@ -0,0 +1,2 @@
+MIXEDcase 4
+mixedCase 5
View
BIN  test/swfs/avm1/propertycase/propertycase-preserving-7.swf
Binary file not shown
View
5 test/swfs/avm1/propertycase/propertycase-preserving-7.swf.trace
@@ -0,0 +1,5 @@
+MixEdcase 2
+mixedCASE 4
+MIXEDcase 3
+MixEdcase 2
+mixedCase 5
View
BIN  test/swfs/avm1/propertycase/propertycase-preserving.fla
Binary file not shown
View
4 test/test_manifest_trace.json
@@ -23,7 +23,11 @@
"swfs/avm1/callee/callee.swf",
"swfs/avm1/watch/watch.swf",
"swfs/avm1/array/arraygeneric.swf",
+ "swfs/avm1/property-paths/property-paths-6.swf",
+ "swfs/avm1/property-paths/property-paths-7.swf",
"swfs/avm1/propertycase/propertycase.swf",
+ "swfs/avm1/propertycase/propertycase-preserving-6.swf",
+ "swfs/avm1/propertycase/propertycase-preserving-7.swf",
"swfs/avm1/scope/avm1-scope-1.swf",
"swfs/avm1/loadevent/loadevent.swf",
"swfs/avm1/nativeinheritance/avm1_inheritedproperties.swf",
Please sign in to comment.
Something went wrong with that request. Please try again.