The assert function is found in many languages to provide a way for you to check for errors only in debug builds of your code. For release/production builds, the asserts are removed to make the compiled code smaller and remove all of the overhead of the error checking. Flash doesn’t come with such a feature built-in, but can we build one ourselves? Today’s article will try to do just that using nothing but Adobe’s modern AS3 compiler: ASC 2.0.

Let’s start out with a “naïve” assert:

public static function assert(truth:*): void
{
	if (!truth)
	{
		throw new Error("Assertion failed!");
	}
}

Using this function is easy, just pass an expression that should evaluate to true:

// Make sure an index int is in a valid range for a Vector
assert(index >= 0 && index < myVector.length);
 
// Make sure a name String isn't null or empty
assert(name);

If either expression is false, an Error is thrown to alert you to the problem. You might want to make your own custom AssertionError derivative class of Error, but that’s beside the point of this article. For our current purposes, the biggest problem with the above function is that is always runs the error check and always throws the Error if it fails. We only want it to run in debug builds, not release builds, so this doesn’t quite suit our needs.

The next step in creating the “naïve” assert is to put in an if statement:

// Only set the flag when running in a debug version of Flash Player or AIR.
// You might want to set this manually or by some other criteria.
private static const ASSERT_ENABLED:Boolean = Capabilities.isDebugger;
 
public static function assert(truth:*): void
{
	if (ASSERT_ENABLED && !truth)
	{
		throw new Error("Assertion failed!");
	}
}

By checking the ASSERT_ENABLED flag, we’ve created a function that effectively throws Error objects in debug builds and doesn’t throw them in release/production builds. Due to short-circuiting, it doesn’t even evaluate the truth parameter. However, this code does have several major drawbacks.

First, the ASSERT_ENABLED flag is checked every single time assert is called in release builds, which may be slow if you call it a lot. Second, the assert function itself is called in release builds, which is definitely slow as AS3 function are notoriously expensive. Third, the assert function is still built into release SWFs and therefore increases the file size. Lastly, the expression to pass to assert is still evaluated, which may be slow if it is complex.

How can we address all of these issues? Well, one big step is to use a compile-time constant to knock out the body of the assert function from even getting compiled into the SWF, let alone executed. This should take care of the first drawback and help reduce, but not eliminate, the SWF size. Here’s a first attempt at that:

public static function assert(truth:*): void
{
	ASSERT::enabled
	{
		if (!truth)
		{
			throw new Error("Assertion failed!");
		}
	}
}

Plus a compiler parameter:

# Debug builds
-define=ASSERT::enabled,true
 
# Release/production builds
-define=ASSERT::enabled,false

In a debug build, the function will look like this:

public static function assert(truth:*): void
{
	if (!truth)
	{
		throw new Error("Assertion failed!");
	}
}

So we’ve effectively removed the ASSERT_ENABLED check in debug builds, which is a nice speedup. Here’s how the function looks in release builds:

public static function assert(truth:*): void
{
}

This is very good progress and a big improvement over the previous version. But can we do anything about the actual function call to assert? That’s where ASC 2.0′s [Inline] metadata comes in:

[Inline]
public static function assert(truth:*): void
{
	ASSERT::enabled
	{
		if (!truth)
		{
			throw new Error("Assertion failed!");
		}
	}
}

Now when we call the function in a debug build the bytecode looks like this: (annotations are mine)

             // push "hello" to the stack
      4      pushstring      "hello"     
             // set "hello" from the stack to local variable #1
      5      setlocal1                   
             // get "hello" from local variable #1
      6      getlocal1                   
             // if "hello" is true, go to bb2 block
      7      iftrue          bb2         
    bb1
      succs=[]
              // "hello" was not true, the assertion failed, find the Error class
      8       finddef        Error                  
              // push "Assertion failed!" to the stack
      9       pushstring     "Assertion failed!"    
              // construct the Error with "Assertion failed!" as its argument
      10      constructprop                         
              // throw the Error
      11      throw                                 
    bb2
      succs=[]
              // "hello" was true, the assertion did not fail
      12      returnvoid

All of this is even better for the debug build because there is no longer any overhead for the function call to assert. But let’s take a look at the release version:

             // find the assert function
      4      findpropstrict  assert     
             // push "hello"
      5      pushstring      "hello"     
             // call the assert function "hello" as the argument
      6      callpropvoid                
             // done with the function
      7      returnvoid

Unfortunately, the compiler (version 2.0.0 build 354071 with -inline and -optimize) does not inline the function call for some reason. So we need to trick it by adding some pointless code:

[Inline]
public static function assert(truth:*): void
{
	ASSERT::enabled
	{
		if (!truth)
		{
			throw new Error("Assertion failed!");
		}
	}
	return; // <-- added
}

Now how does the release version look?

             // push "hello"
      4      pushstring      "hello"     
             // set "hello" to local variable #1
      5      setlocal1                   
             // don't call the function or do any assert work, just move on
      6      returnvoid

With this trick the release build no longer calls assert or does any of the work that assert does, like test for the error case or throw an Error object. The assert function is still built into the SWF, but its size is now extremely small. Here’s the bytecode, which shows just an empty function:

  public static function assert(*):void
  {
    //  derivedName  assert  
    //  method_info  1       
    //  max_stack    1       
    //  max_regs     2       
    //  scope_depth  0       
    //  max_scope    1       
    //  code_length  1       
    bb0
      succs=[]
      0      returnvoid      
  }

The last hurdle to overcome is that the expression passed to assert is still evaluated. One potential way of fixing this is to use the -omit-trace-statements compiler parameter and wrap all our assert calls in trace calls like this:

trace(assert(index >= 0 && index < myVector.length));

Theoretically, the whole line should be removed by the compiler. Unfortunately, here’s the bytecode:

      15      pushbyte
      16      setlocal1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
      17      getlex          trace                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
      18      getglobalscope                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
      19      getlocal1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
      20      pushbyte
      21      greaterequals                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
      22      dup                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
      23      iffalse         bb2                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
    bb1
      succs=[bb2]
      24      pop                    
      25      getlocal1              
      26      getlocal2              
      27      getproperty  length    
      28      lessthan               
    bb2
      succs=[]
      29      setlocal3           
      30      pushundefined       
      31      call             1  
      32      returnvoid

I haven’t annotated it, but you can clearly see that all of the expression is still being evaluated and now even have an additional, expensive call to trace. Unfortunately again, it seems that the only way to fully eliminate the expression is to wrap the calling code as well in an ASSERT::enabled block:

ASSERT::enabled { assert(index >= 0 && index < myVector.length); }

The bytecode is now, finally, empty.

However, it can easily be argued that there’s a lot of typing that needs to happen now for each and every assert call. This is true, and probably both annoying to develop with and to code. There don’t seem to be any truly effective alternatives. However, you can mitigate the extra typing in a couple of ways. First, if you’re calling assert multiple times, you only need one ASSERT::enabled block:

ASSERT::enabled {
	assert(index >= 0 && index < myVector.length);
	assert(name);
	assert(!error);
}

You can also set either the first or second parts of the compile-time constant to shorter strings to lessen the typing load. Of course you’ll want to still preserve readability, which probably means avoiding really short names like A::E. However, some compromise is probably acceptable. For example, ASSERT::on is a bit shorter, DEBUG::on is one character shorter than that, and DBG::on is probably as far as you can go.

In conclusion, we’ve created an assert and come up with a strategy for calling it that is almost completely removed in release/production builds of our code. The error expression isn’t evaluated, the function isn’t called, the truth of the expression isn’t checked, and no Error is thrown. The only tiny remnant is an empty function in the SWF, which should be acceptable to all but the tiniest Flash apps such as banner ads.

Spot a bug? Have a way of improving assert? Post a comment!