[Tip] Skipping super() - Interesting behavior

The AS3 docs state that super() is called no matter what. This has basically been the common wisdom, too.

This isn’t strictly true. It turns out that you can avoid calling the superconstructor by throwing an error:

// this is really AS3
class Base {
	public function Base(){
		trace('Base constructor');
	}
}

class Subclass extends Base {
	public function Subclass(){
		try {
			Error.throwError(Error, 0);
			super();
		} catch(err:Error){}
	}
}

If you take the above code and (elsewhere) construct a new Subclass(), you’ll notice that you never get the trace that’s supposed to occur.

(Note: It’s fairly tricky to get that call to super() to be accepted without using Error.throwError, because there are some conditions on where you can use super(): “1201: A super statement cannot occur after a this, super, return, or throw statement.” I think those guards are there to enforce that the call to super() is made once and only once, although I’ve demonstrated that it isn’t being enforced well.)

You can even defer the call to super() until an arbitrary amount of time later:

// really AS3 still
class Base {
	public function Base(){
		trace('Base constructor');
	}
}

import flash.utils.*;

class Subclass extends Base {
	private var konstructor:Function;
	
	public function Subclass(){
		try {
			if(!konstructor){
				konstructor = arguments.callee;
				setTimeout(konstructor, 5000);
				Error.throwError(Error, 0);
			}
			super();
		} catch(err:Error){}
	}
}

Now, you might be wondering what good could ever possibly come of this. I was wondering the same thing, until I remembered the classic “null-stage” problem. If you’re not familiar with it, it’s an error that often occurs when you’re using somebody else’s compiled code that assumes that DisplayObject#stage is going to be defined when the class is instantiated. In actuality, stage will be null sometimes, such as when an SWF is loaded into another SWF, and what was originally the document class is now just another random DisplayObject that may not even be on a display list linked to the stage (which happens when the Loader was never added to the stage).

This whole skipping super() approach sort of finds a solution for this problem, although it’s not as complete as I wish it could be. It still can’t solve the external SWF stage-null problem due to limitations on how you can mix the definitions that are included in a Loader’s LoaderContext’s ApplicationDomain.

However, you can solve a somewhat wimpier null-stage problem, where you just have an SWC in which there’s a class that assumes its stage is defined:

// AS3

/*
    Assume that this is compiled into an SWC and you lose the source code later.
*/

package {
	import flash.display.*;
	
	public class NullStage extends Sprite {		
		public function NullStage() {
			trace('I\'m about to access a null stage, haha!');
			stage.scaleMode = StageScaleMode.NO_SCALE;
			trace('Success');
		}
	}
	
}

Trying to use this class would normally result in an unfixable error! So, the solution is to create a wrapper subclass that takes advantage of the trick that I’ve been describing. You could just wrap a try block around the call to super() in this subclass so that the stage access wouldn’t break your own code, but then you would never get to execute the code in NullStage’s constructor. You definitely want to execute that code, because it’s the whole reason you’re using the NullStage SWC instead of rewriting the code yourself.

So, you need to defer the call to NullStage’s constructor like this:

// AS3
import flash.events.*;

class NullStageWrapper extends NullStage {
	
	public function NullStageWrapper(){
		try {
			if(!stage){
				addEventListener(Event.ADDED_TO_STAGE, arguments.callee);
				Error.throwError(Error, 0);
			}
			super();
		} catch(err:Error){}
	}
}

Now, whenever you get around to adding NullStageWrapper to the root display list, NullStage will have its constructor called for the first time and it should behave the same as the original SWC author intended, and you won’t have had to decompile, post-process, or rewrite any code!

Admittedly, that’s a bit of a stretch of a use case. From my perspective, it was neat enough just to notice that you can get away without calling constructors despite all of the documentation stating that it was quite necessary.

It’s worth noting that instance initialization functions, which include the non-static code that’s within class blocks but not inside of any function, are still called unavoidably. Since they’re called before constructors are, you don’t get a chance to cancel their execution with custom code of your won.

I also found that this behavior reveals a rather bad bug in Flash Player, where you can crash the player immediately if you subclass Dictionary and cancel the call to its constructor. I logged the bug in Tamarin and in Adobe’s bugbase as FP-6626 (if it’s ever made public). You can [URL=“http://temp.reclipse.net/DictionaryConstructBug/DictionaryConstructBug.html”]test the bug here to see if it crashes your player too! :trout:

Let me know what you think; I’m curious to see if there are any other neat or weird things that you can do with this.