AS3 getouterscope instruction

During my journeys around ActionScript 3 bytecode I encountered interesting undocumented instruction called getouterscope.
The instruction is not documented in official Avm2 overview available online which makes it harder to understand.

So I tried to investigate what really this instruction does and came to this conclusion:

It has an opcode 0x67, and accepts U30 number – the scopeIndex as an argument.
At first I thought it should be similar to getscopeobject (0x65) instruction, but what’s “outer” scope here?
So I tried to insert the instruction into a script and then print the result to user.

It seems that it works this way:
In AS3 you have stack based execution, and besides classic stack you have also so-called scope stack.
You can push an item to the scopestack by calling pushscope (0x30) instruction.
For AS3 classes, this is done in script initializers, class initializers and also the methods them self.
If there is an unqualified property accessed in a part of code, it is searched in items in the scopestack.
Items from scopestack can be popped using popscope (0x1d) instruction and it’s done when the scope is closed.

Inside a method, instruction getscopeobject can access so-called local scope stack. This means all items on scopestack which were added in current executed function (like “this” from local register 0, newactivation objects or pushwith items).

On the other-side, getouterscope (0x67) can (and only) access scope objects outside current function. This means all these scopes added in script and class initializers.

The argument or getouterscope is 0-based index of scope starting from global object and finishing at the scope directly outside current method.

See the following code:

{
//script initializer
	getlocal0; //global object
	pushscope;
        getlex "Object";
        pushscope;
        getlex "RunParent";
        pushscope;
        ...
        newclass ...;       
}
package
{
	public class Run extends RunParent
	{
		{
			//class initializer
			getlocal0; //class Run
			pushscope;
		}
		public function run() : void
		{
			getlocal0
			pushscope
			//at this place:
			getouterscope 0;   //global object,    can also be get with getglobalscope
			getouterscope 1;   //class Object,     from script initializer
			getouterscope 2;   //class RunParent,  from script initializer
			getouterscope 3;   //class Run,        from class initializer
			getouterscope 4;   //VerifyError: Error #1019: Getscopeobject  is out of bounds.
			//This differs from getscopeobject opcode, which accesses scopestack inside the current function:
			getscopeobject 0;   //object Run,         from pushing getlocal0 at the beginning of run() method
			getscopeobject 1;   //Run/run,            from pushing newactivation if it is used
			getscopeobject 2;   //something,		from pushwith if it is used
			getscopeobject 3;   //VerifyError: Error #1019: Getscopeobject 3 is out of bounds, when it is ouside the bounds
			//when inner function is used here:
			newactivation;
			pushscope;
			var f:Function = function() {
				getouterscope 0 to 3;  //same as above
				getouterscope 4        //object Run,       from pushing getlocal0 at the beginning of run() method
				getouterscope 5        //method Run/run,   from pushing newactivation object
				getouterscope 6;       //VerifyError: Error #1019: Getscopeobject  is out of bounds.
				
				//when inner function inside inner function, then there is just new scope from newactivation
			}
		}
	}
}

In my decompiler I decided to decompile such instruction call as the objects it refers to:

package
{
	public class Run extends RunParent
	{
		public function run() : void
		{
			var v:* = RunParent;  // pcode: getouterscope 2
			trace(v); //prints "[class RunParent]"
		}
	}
}

Other decompilers (For example ASV) print out the instruction name instead.

I hope this info helps somebody.


Posted

in

by

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *