Find JSRs
Submit this Search


Ad Banner
 
 
 
 

Proposal: A Simple Assertion Facility For the Java™ Programming Language

Proposal: A Simple Assertion Facility For the Java™ Programming Language

Introduction

We propose to add a simple assertion facility to the Java programming language. An assertion is a statement containing a boolean expression that the programmer believes to be true at the time the statement is executed. For example, after unmarshalling all of the arguments from a data buffer, a programmer might assert that the number of bytes of data remaining in the buffer is zero. The system executes the assertion by evaluating the boolean expression and reporting an error if it evaluates to false. By verifying that the boolean expression is indeed true, the system corroborates the programmer's knowledge of the program and increases his confidence that the program is free of bugs. Assertion checking may be disabled for increased performance. Typically, assertion-checking is enabled during program development and testing, and disabled during deployment.

Because assertions may be disabled, programs must not assume that the expressions contained in assertions will be evaluated. Thus, it is extremely bad practice for these expressions to have any side effects: evaluating the boolean expression should never affect any state that is visible after the evaluation is complete. (It is not, in general, possible for the compiler to enforce a prohibition on side-effects in assertions, so the language specification does not disallow the practice.)

Along similar lines, assertions should not be used for argument-checking in public methods. Argument-checking is typically part of the contract of a method, and this contract must be upheld whether assertions are enabled or disabled. Another problem with using assertions for argument checking is that erroneous arguments should result in an appropriate runtime exception (e.g., IllegalArgumentException, IndexOutOfBoundsException, NullPointerException). An assertion failure will not throw an appropriate exception. (Again, it is not, in general, possible for the compiler to enforce a prohibition on the use of assertions for argument checking on public methods, so the language specification will not disallow the practice.)

Syntax

A new keyword would be added to the language: assert. The use of this keyword is governed by two new productions in the grammar:
    StatementWithoutTrailingSubstatement:
        <All current possibilities, as per JLS, Section 14.4>
        AssertStatement

    AssertStatement:
        assert(Expression) ;
        assert(Expression, Expression) ;
In both forms of the assert statement, the initial Expression must have type boolean or a compile-time error occurs. In the second form of the assert statement, the second Expression must have a reference type or a compile-time error occurs.

Semantics

Assertions may be enabled or disabled on a per-class basis. At the time that a class is loaded, its class loader determines whether assertions are enabled or disabled as described below. Once a class has been loaded, its assertion status (enabled or disabled) does not change as long as it remains loaded.

If assertions are disabled in a class, all of the assertions contained in methods of that class have no effect at runtime. In particular, the "arguments" to the assert statements (the boolean Expression and the reference-type Expression, if present) are not evaluated.

If assertions are enabled in a class, the execution of an assert statement proceeds as follows:

  1. Evaluate the boolean Expression in the assert statement. If evaluation of the Expression completes abruptly for some reason, the assert statement completes abruptly for the same reason.
  2. If the resulting boolean value is false:
    1. If the assert statement is of the second form (with a reference-type Expression), evaluate the reference type Expression and invoke the toString() method on the result.
    2. If the computation in the previous step completes abruptly for some reason, or if the assert statement is of the first form (with only a boolean Expression), obtain an AssertionError object with no "detail message"; otherwise construct an AssertionError object with with the String object computed in the previous step as its detail message.
    3. Throw the AssertionError object computed in the previous step.

Enabling and Disabling Assertions

Each class loader maintains a default assertion status, a boolean value that determines whether assertions are, by default, enabled or disabled in new classes that are subsequently loaded by the class loader. A newly created class loader's default assertion status is false (disabled). It can be changed at any time by invoking a new method in class ClassLoader:
    /**
     * Sets the default assertion status for this class loader.
     * This setting determines whether classes loaded by this class loader
     * will have assertions enabled or disabled by default.  This setting
     * may be overridden on a per-package basis with the 
     * setDefaultAssertionStatus(String, boolean) method, or on a 
     * per-class basis, with the setAssertionStatus(String, boolean) method.
     *
     * @param enabled true if classes loaded by this class loader
     *        will henceforth have assertions enabled by default,
     *        false if they will have assertions disabled by default.
     */
    public void setDefaultAssertionStatus(boolean enabled);
If, at the time that a class is loaded, its class loader has been given specific instructions regarding the assertion status of the class's package name or its class name (via either of the two new methods described below), those instructions take precedence over the class loader's default assertion status. Otherwise, the class's assertions are enabled or disabled as specified by its class loader's default assertion status.

The following method allows the invoker to set a per-package default assertion status:

    /**
     * Sets the package-default assertion status for the named
     * package in this class loader.  The package-default assertion status
     * determines the assertion status for classes belonging to the
     * named package that are loaded in the future.  This setting takes
     * precedence over the class loader's default assertion status, and
     * may be overridden on a per-class basis by invoking the
     * setAssertionStatus method.
     *
     * @param packageName the name of the package whose default assertion
     *        status  is to be set.
     * @param enabled true if classes loaded by this class loader
     *        and belonging to the named package will henceforth have
     *        assertions enabled by default, false if they will
     *        have assertions disabled by default.
     */
    public boolean setDefaultAssertionStatus(String packageName,
                                             boolean enabled);
The following method is used to set assertion status on a per-class basis:
    /**
     * Sets the assertion status for the named class in this class
     * loader.  This method overrides the class loader default and
     * package default (if applicable).  This method is permitted only
     * if the named class has not yet been loaded by this class loader.
     * Once a class is loaded, its assertion status may not change.
     *
     * The assertion status of an anonymous inner class cannot be set 
     * with this method.  Anonymous inner classes automatically take on
     * the assertion status of their immediate outer class.  Named inner
     * classes take on the assertion status of their immediate outer class
     * by default, but this default may be overridden with this method.
     *
     * @param className the fully qualified class name of the class whose
     *        assertion status is to be set.
     * @param enabled true if the named class is to have assertions
     *        enabled when (and if) it is loaded by this class loader,
     *        false if the class is to have assertions disabled.
     * @throws IllegalStateException if the named class has already been
     *         loaded.
     * @see    setDefaultAssertionStatus(boolean)
     */
    public boolean setAssertionStatus(String className, boolean enabled);

Two switches would be added to our java interpreters to selectively enable or disable assertions. (These command-line switches are not part of the specification for assertions, but are just illustrative of how an implementation of the Java runtime environment might enable and disable assertions. We do not specify flags as part of the Java platform, they are just documented as things known to our tools.)

The following switch enables assertions at various granularities:

java [ -enableassertions | -ea  ] [:<package name>".*" | :<class name> ]
With no arguments, the switch enables assertions by default. With one argument ending in ".*", it enables assertions in the named package by default. With one argument not ending in ".*", it enables assertions in the named class.

The following switch disables assertions in similar fashion:

java [ -disableassertions | -da ] [ :<package name>".*" | :<class name> ]
If a single command line contains multiple instances of these switches, they are processed in order before loading any classes. So, for example, to run a program with assertions enabled only in package com.wombat.fruitbat, the following command could be used:
java -da -ea:com.wombat.fruitbat.*      ...
To run a program with assertions enabled only in package com.wombat.fruitbat but disabled in class com.wombat.fruitbat.Brickbat, the following command could be used:
java -da -ea:com.wombat.fruitbat.* -da:com.wombat.fruitbat.Brickbat   ...
Also, a system-specific mechanism, such as a well-known Properties file, can be used to persistently specify a set of packages or classes that should have assertions turned on (or off). Installation scripts could set such properties appropriately. For example, installing a production release might turn off assertions in all of its packages for enhanced performance, while installing a Beta release might turn on assertions to provide more thorough testing. (There are dangers associated with this approach: turning off asserts could expose timing-dependent bugs.) Command line switches take precedence over persistent specifications.

New Error Class

The following error class is added to package java.lang:
class AssertionError extends Error () {
    public AssertionError(Class clazz, java.lang.reflect.Method method,
                          String fileName, int lineNumber);
    /**
     * Returns the method containing the failed assertion.
     */
    java.lang.reflect.Method getMethod();

    /**
     * Returns the name of the "source unit" containing the source code of
     * the failed assertion.  The exact interpretation of this value is
     * implementation-dependent, to allow for repository-based
     * implementations, classes for which no source code exists, and
     * the like.  Implementations are encouraged to return reasonably
     * complete information.  For example, a fully qualified file name
     * is more appropriate than a "simple file name."
     *
     * If no file name information exists, this method should return
     * the empty string ("").
     */
    String getSourceName();

    /**
     * Returns the line number in the "source unit" of the line containing
     * the failed assertion.  The exact interpretation of this value is
     * implementation-dependent, to allow for repository-based
     * implementations, classes for which no source code exists, and the
     * like.
     *
     * If no line number information exists, this method should return -1.
     */
    int getLineNumber();
}

Implementation Notes

It is highly desirable that the execution of disabled assertions have little or no cost at runtime. Further, it is highly desirable that the assertion facility require minimal change to existing JVM implementations and no change to the class file format. An implementation that meets these criteria is sketched below.

Class ClassLoader gets three new private fields, which it uses to keep track of the desired assertion status of classes not yet loaded:

    private boolean defaultAssertionStatus = false;

    // Maps String packageName to Boolean assertionStatus
    private Map packageDefaultAssertionStatus = new HashMap();

    // Maps String fullyQualifiedClassName to Boolean assertionStatus
    private Map classAssertionStatus          = new HashMap();
These fields are maintained by the three new public ClassLoader methods, in the obvious fashion. In fact, the only thing that these methods do is to modify these fields (or the maps they reference).

The compiler adds a "synthetic" field to every class (except anonymous inner classes), containing the actual assertion status of the class (and any anonymous inner classes).

    final private static boolean $assertionsEnabled =
        ClassLoader.desiredAssertionStatus(className);
The name of this field need not be part of the specification: it is local to the class and can vary from implementation to implementation and class to class.

The method ClassLoader.desiredAssertionStatus(className) returns the desired assertion status for the named class by consulting the three new ClassLoader fields (described above) in the obvious fashion. (The behavior is slightly more complicated in the case of named inner classes. This detail is omitted for brevity.) I'm afraid that this method must be public, as it's invoked from every every package. If it is possible to avoid this through some (ab)use of native methods, I believe that is probably the right thing to do.

The assert statement, then, is merely syntactic sugar for this if statement:

if ($assertionsEnabled && !(Expression))
    throw new AssertionError(Expression);
This implementation works equally well in static and instance methods (and constructors and static blocks, for that matter).

The runtime does not need to treat this code specially in order to "strip out" assertions from classes where they're disabled! Because $assertionsEnabled is final, the JIT can easily determine that the entire if statement is a no-op, and eliminate it in the compiled code that it generates. I believe that present day JITs (including HotSpot) do this optimization (though I've yet to confirm this).

Note also that $assertionsEnabled resolves to the so-named field in the "declaring class" for the containing method. The resulting behavior is clearly desirable, though it may be somewhat surprising. If one class extends another, and assertions are enabled only in the subclass, assertions contained in (any) inherited superclass methods will not be executed.

Note that I'm glossing over the details of computing the correct AssertionError object to throw (as specified in the Semantics section of this proposal). It should be clear, however, that this presents no real difficulties.

It is unfortunate that ClassLoader.desiredAssertionStatus(className) must be made public. The desirable semantics for a public method differ slightly from what's required to make the implementation work, but performance concerns probably dictate that we stick with what's required (which is to return the assertion status that would apply if the class were to be loaded at the time of the invocation, whether or not it's already loaded). If the class is already loaded at the time of the invocation, its actual assertion status may differ from the value returned by the invocation, though it typically won't.

As described above, this implementation requires no change to the JVM implementation, but does not support control over assertions in "low-level" system classes (ClassLoader and any classes that are loaded before ClassLoader). In order to support this level of control (via command line switches to java), minimal changes to the JVM implementation would be required. The maps maintained in packageDefaultAssertionStatus and classAssertionStatus would be internal (C/C++) hash tables, and the public methods that manipulate these maps would become (or would rely on) native methods. There is no reason even to consider doing this if none of these "system kernel classes" contain any assertions.

Open Issues

  1. It would be nice if there were a simply way to enable assertions for an entire "package tree", such as javax.swing.*. In other words, one should be able to specify, either on the command line or via an API, that assertions in a particular package and all of its subpackages are enabled. The current proposal does not provide any way of doing this.
  2. It is somewhat surprising that an exception encountered during the evaluation of the second (String) expression in a two-expression assert statement is intercepted. On the other hand, it seems wrong not to throw an AssertionError once it has been determined that an assertion has failed.
  3. The proposal currently contains no way to query the assertion status of a class (enabled or disabled). Is there any compelling reason to provide a method to return this information?


See Also
Details of the Community Process
New Specification Proposals
Open Calls for Experts