Use of JCP site is subject to the
JCP Terms of Use and the
Oracle Privacy Policy
|
A Simple Assertion Facility For the Javatm Programming LanguagePublic DraftI. IntroductionThis document specifies a simple assertion facility for the Java programming language. In its intended usage, 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 the expression evaluates to false. By verifying that the boolean expression is indeed true, the system corroborates the programmer's knowledge of the program and increases one's 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 for deployment.Because assertions may be disabled, programs must not assume that the expressions contained in assertions will be evaluated. Thus, these boolean expressions should generally be free of side effects: evaluating such a boolean expression should not affect any state that is visible after the evaluation is complete. It is not illegal for a boolean expression contained in an assertion to have a side effect, but it is generally inappropriate, as it could cause program behavior to vary depending on whether assertions were enabled or disabled. 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 (such as IllegalArgumentException, IndexOutOfBoundsException or NullPointerException). An assertion failure will not throw an appropriate exception. Again, it is not illegal to use assertions for argument checking on public methods, but it is generally inappropriate. II. SyntaxA new keyword is added to the language: assert. The use of this keyword is governed by one modified production and one new production in the grammar:StatementWithoutTrailingSubstatement: <All current possibilities, as per JLS, Section 14.4> AssertStatement AssertStatement: assert Expression1 ; assert(Expression1, Expression2) ;In both forms of the assert statement, Expression1 must have type boolean or a compile-time error occurs. III. SemanticsAssertions 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.If assertions are disabled in a class, an assert statement contained in that class does nothing. In particular, its "arguments" ( Expression1 and, if it is present, Expression2) are not evaluated. Execution of an assert statement that is contained in a class in which assertions are disabled always completes normally. If assertions are enabled in a class, an assert statement contained in that class is executed by first evaluating Expression1. If evaluation of Expression1 completes abruptly for some reason, the assert statement completes abruptly for the same reason. Otherwise, execution continues by making a choice based on the value of Expression1:
IV. Enabling and Disabling AssertionsEach 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 in the future by this class * loader will have assertions enabled or disabled by default. This * setting may be overridden on a per-package or per-class basis by * invoking setPackageAssertionStatus or * setClassAssertionStatus. * * @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 in ClassLoader 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. Note that a per-package default actually applies to a package and any "subpackages." /** * Sets the package default assertion status for the named * package. The package default assertion status determines the * assertion status for classes loaded in the future that belong * to the named package or any of its "subpackages." * * A subpackage of a package named p is any package whose name * begins with "p." . For example, javax.swing.text is * a subpackage of javax.swing, and both java.util * and java.lang.reflect are subpackages of java. * * In the event that multiple package defaults apply to a given * class, the package default pertaining to the most specific package * takes precedence over the others. For example, if * javax.lang and javax.lang.reflect both have * package defaults associated with them, the latter package * default applies to classes in javax.lang.reflect. * * Package defaults take precedence over the class loader's default * assertion status, and may be overridden on a per-class basis by * invoking setClassAssertionStatus. * * @param packageName the name of the package whose package default * assertion status is to be set. A null value * indicates the unnamed package that is "current" * (JLS 7.4.2). * @param enabled true if classes loaded henceforth * by this class loader and belonging to the named package * or any of its subpackages will have assertions enabled * by default, false if they will have assertions * disabled by default. * @throws IllegalArgumentException if packageName is non-null and * does not constitute a valid name, as defined in JLS 6.2. */ public void setPackageAssertionStatus(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 top-level class in * this class loader and any nested classes contained therein. * This setting takes precedence over the class loader's default * assertion status, and over any applicable per-package default. * This method has no effect if the named class has already been * loaded by this class loader. (Once a class is loaded, its assertion * status cannot change.) The return value of this method may * be examined to determine whether an invocation had any effect. * * If the named class is not a top-level class, this call will have no * effect on the actual assertion status of any class, and its return * value is undefined. * * @param className the fully qualified class name of the top-level 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. * @returns the assertion status of the named class if it is already * loaded, or enabled if it is not. (In the absence of * a second, subsequent invocation of this method, the return * value is the assertion status that the named class has or will * have once it is loaded.) * @throws IllegalArgumentException if className does not * constitute a valid name as defined in JLS 6.2. */ public boolean setClassAssertionStatus(String className, boolean enabled);Finally, a method is added to ClassLoader to allow the desired assertion status for a specified class (as set by the three methods above) to be queried. Few programmers will have any need for this method; it is provided for the benefit of the JRE itself. (It allows a class to determine at the time that it is loaded whether assertions should be enabled.) /** * Returns the assertion status that would be assigned to the specified * class if it were to be loaded at the time this method is invoked. If * the named class has had its assertion status set, the most recent * setting will be returned; otherwise, if any package default assertion * status pertains to this class, the most recent setting for the most * specific pertinent package default assertion status is returned; * otherwise, this class loader's default assertion status is returned. * * The behavior of this method is unspecified if className * does not constitute a valid name as defined in JLS 6.2. * * Few programmers will have any need for this method; it is provided * for the benefit of the JRE itself. (It allows a class to determine at * the time that it is loaded whether assertions should be enabled.) * Note that this method is not guaranteed to return the actual * assertion status that was (or will be) associated with the specified * class when it was (or will be) loaded. * * @param className the fully qualified class name of the top-level class * whose desired assertion status is being queried. * @return the desired assertion status of the specified class. * @see setClassAssertionStatus * @see setPackageAssertionStatus * @see setDefaultAssertionStatus */ public boolean desiredAssertionStatus(String className);The three new ClassLoader instance methods for setting desired assertion status will generally not be used by normal Java programs, but by Java interpreters. Java interpreters should provide some interpreter-specific facility for users to enable and disable asserts. One possible approach is described in Appendix II. V. New Error ClassThe following error class is added to package java.lang:/** * Thrown to indicate that an assertion has failed. */ public class AssertionError extends Error () { /** * Constructs an AssertionError with no detail message. */ public AssertionError(); /** * Constructs an AssertionError with the specified detail message. */ public AssertionError(String detailMessage); } The material contained in the appendices below is not part of this specification, but is intended to inform the use and implementation of the facility. In the parlance of the standards community, these appendices are non-normative. Appendix I. Usage NotesIn this Appendix we present examples of appropriate and inappropriate use off the assert construct. This appendix is not meant to be exhaustive, but merely to convey the flavor of the intended usage of the construct.Internal InvariantsIt is generally appropriate to liberally sprinkle code with short assertions indicating important assumptions concerning the programs behavior. In the absence of an assertion facility, many programmers did this with comments. For example: if (i%3 == 0) { ... } else if (i%3 == 1) { ... } else { // (i%3 == 2) ... }Whenever you come across a comment that asserts an invariant, you should change it to an assert: if (i % 3 == 0) { ... } else if (i%3 == 1) { ... } else { assert i%3 == 2; ... }The above example, where an assert protects the else-clause in a multiway if-statement is quite common. Note, incidentally, that the assertion in the above example may fail if i is negative, as the % operator is not a true mod operator, but computes the remainder, which may be negative. Control-Flow InvariantsAnother good candidate for an assertion is a switch statement with no default case: switch(suit) { case Suit.CLUBS: ... break; case Suit.DIAMONDS: ... break; case Suit.HEARTS: ... break; case Suit.SPADES: ... }It is probably the case that the programmer believes that one of the four cases in the above switch statement will always be executed. This assumption may be tested by adding the following default case: default: assert false;More generally, the statement assert false;should be placed at in locations the programmer believes to be "unreached" For example, suppose you have a method that looks like this: void foo() { for (...) { if (...) return; } // Execution should never reach this point!!! }The final comment should be replaced by assert false;This technique must be used with some discretion; if a statement is unreachable as defined in (JLS 14.19), you'll get a compile time error if you try to assert that it is unreached. Preconditions, Postconditions, and Class InvariantsWhile the assert construct is not a full-blown design-by-contract facility, it can help support an informal design-by-contract style of programming. PreconditionsBy existing convention in Java, preconditions on public methods are enforced by explicit checks inside methods resulting in particular, specified exceptions. For example: /** * Sets the refresh rate. * * @param rate refresh rate, in frames per second. * @throws IllegalArgumentException if rate <= 0 or * rate > MAX_REFRESH_RATE. */ public void setRefreshRate(int rate) { // Enforce specified precondition in public method if (rate <= 0 || rate > MAX_REFRESH_RATE) throw new IllegalArgumentException("Illegal rate: " + rate); setRefreshInterval(1000/rate); }This convention is unaffected by the addition of the assert construct. This construct is inappropriate for such preconditions, as the enclosing method guarantees that it will enforce the argument checks, whether or not assertions are enabled. Further, the assert construct does not throw an exception of the specified type. If, however, there is a precondition on a nonpublic method and the author of a class believes the precondition to hold no matter what a client does with the class, then an assertion is entirely appropriate: /** * Sets the refresh interval (which must correspond to a legal frame rate). * * @param interval refresh interval in milliseconds. */ private void setRefreshInterval(int interval) { // Confirm adherence to precondition in nonpublic method assert interval > 0 && interval <= 1000/MAX_REFRESH_RATE; ... // Set the refresh interval }Note, by the way, that the above assertion will fail if MAX_REFRESH_RATE is greater than 1000 and the user selects a refresh rate greater than 1000; this would, in fact, indicate a bug in the library! PostconditionsPostcondition checks are best implemented via assertions, whether or not they are specified in public methods. For example: /** * Returns a BigInteger whose value is (this-1 mod m). * * @param m the modulus. * @return this-1 mod m. * @throws ArithmeticException m <= 0, or this BigInteger * has no multiplicative inverse mod m (that is, this BigInteger * is not relatively prime to m). */ public BigInteger modInverse(BigInteger m) { if (m.signum <= 0) throw new ArithmeticException("Modulus not positive: " + m); if (!this.gcd(m).equals(ONE)) throw new ArithmeticException(this + " not invertible mod " + m); ... // Do the computation assert this.multiply(result).mod(m).equals(ONE); return result; }In practice, one would not check the second precondition, (this.gcd(m).equals(ONE)) prior to performing the computation, because it is wasteful; this precondition is checked as as a side effect of performing the modular multiplicative inverse computation by standard algorithms. Occasionally, it is necessary to save some data prior to performing a computation in order to check a postcondition after it is complete. This can be done with two assert statements and the help of a simple inner class designed to save the state of one or more variables so they can be checked (or rechecked) after the computation. For example, suppose you have a piece of code that looks like this: void foo(int[] array) { // Manipulate array ... // At this point, array will contain exactly the ints that it did // prior to manipulation, in the same order. }Here is how you could modify the above method to turn the textual assertion into a functional one: void foo(final int[] array) { class DataCopy { private int[] arrayCopy; DataCopy() { arrayCopy = (int[])(array.clone()); } boolean isConsistent() { return Arrays.equals(array, arrayCopy); } } DataCopy copy = null; // Always succeeds; has side effect of saving a copy of array assert (copy = new DataCopy()) != null; ... // Manipulate array assert copy.isConsistent(); }Note that this idiom easily generalizes to save more than one data field, and to test arbitrarily complex assertions concerning pre-computation and post-computation values. This idiom is less than pretty, but it is expedient. The first assert statement (which is executed solely for its side-effect) could be replaced by the more expressive: copy = new DataCopy();but this would copy the array whether or not asserts were enabled, violating the dictum that asserts should have no cost when disabled. Class InvariantsAs noted above, assertions are appropriate for checking internal invariants. The assertion mechanism itself does not enforce any particular style here. It is sometimes convenient to combine many expressions that check required constraints into a single internal method that can then be invoked by assertions. For example, suppose one were to implement a balanced tree data structure of some sort. It might be appropriate to implement a private method that checked that the tree was indeed balanced as per the dictates of the data structure:// Returns true if this tree is properly balanced private boolean balanced() { ... }This method is a class invariant. It should be always be true before and after any method completes. To check that this is indeed the case, each public method and constructor should contain the line: assert balanced();immediately prior to each return. It is overkill to place similar checks at the head of each public method unless the data structure is implemented by native methods. In this case, it is possible that a memory corruption bug could "stomp on" the data structure in between method invocations. A failure of this assertion at the head of a call would indicate that memory corruption had occurred. Removing all Trace of Assertions from Class FilesProgrammers developing for resource-constrained devices may wish to strip assertions out of class files entirely. While this makes it impossible to enable assertions in the field, it also reduces class file size, possibly leading to improved class loading performance. In the absence of a high quality JIT, it could lead to decreased footprint and improved runtime performance. The assertion facility offers no direct support for stripping assertions out of class files. However, the assert statement may be used in conjunction with the "conditional compilation" idiom described in JLS 14.19: static final boolean asserts = ... ; // false to eliminate asserts if (asserts) assert <expr> ;If asserts are used in this fashion, the compiler is free to eliminate all traces of these asserts from the class files that it generates. It is recommended that this be done where appropriate to support generation of code for resource-constrained devices. Requiring that Assertions are EnabledOn a related note, programmers of certain critical systems might wish to ensure that assertions are not disabled in the field. Here is an idiom that prevents a class from being loaded if assertions have been disabled for that class:static { boolean assertsEnabled = false; assert assertsEnabled = true; // Intentional side effect!!! if (!assertsEnabled) throw new RuntimeException("Asserts must be enabled!!!"); } Appendix II. Enabling and Disabling Assertions in the InterpreterSun's java interpreters will disable assertions by default. Two command-line switches will be added to Sun's java interpreters to selectively enable or disable assertions. These command-line switches are not part of the specification for assertions, but are 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.)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 "...", the switch enables assertions in the specified package and any subpackages by default. If the argument is simply "...", the switch enables assertions in the unnamed package in the current working directory. With one argument not ending in "...", the switch enables assertions in the specified 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 (and any subpackages), the following command could be used: java -ea:com.wombat.fruitbat... <Main Class>To run a program with assertions enabled in package com.wombat.fruitbat but disabled in class com.wombat.fruitbat.Brickbat, the following command could be used: java -ea:com.wombat.fruitbat... -da:com.wombat.fruitbat.Brickbat <Main Class> Some care must be exercised by the interpreter in choosing the class loader (or class loaders) on which to set assertion status when processing each -da and -ea flag. If the interpreter cannot positively identify which class loader will be used to load a named class or package, it is prudent to set the assertion status on multiple class loaders to ensure that the assertion status is set on the correct class loader. Finally, it is recommended that a system-specific mechanism, such as a
well-known Properties file, 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.) It is recommended that command line switches take
precedence over persistent specifications at the same granularity. Sun's java
interpreters will support such a mechanism, the details to be determined
later.
-source <release>where <release> can be "1.3" or "1.4". Support for 1.3 source compatibility would likely be phased out over time. Appendix IV. Implementation NotesIt is highly desirable that the execution of disabled assertions have little or no cost at runtime. Further, it is required by this specification that the assertion facility does not require any changes to the class file format or the JVM specification. An implementation that meets these criteria is sketched below. Note that this section describes only one way in which this specification can be implemented; it places no constraints on JVM implementors, who are free to implement the specification in any manner they see fit.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 package default assertion status private Map packageAssertionStatus = 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 top-level class containing the actual assertion status of the class (and any nested classes). private static final boolean $assertionsEnabled = <ClassName>.class.getClassLoader().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. As an optimization, compilers needn't generate this field for classes that contain no reachable (JLS 14.19) assert statements. The assert statement, then, is merely syntactic sugar for this: do { if ($assertionsEnabled && !(Expression1)) throw new AssertionError(String.valueOf(Expression2)); } while(false);The surrounding do-while loop is merely an artifice to transform the if statement to the correct syntactic category (StatementWithoutTrailingSubstatement), and has no semantics associated with it. Note that this implementation works equally well in static and instance methods (and constructors, static initializers, and instance initializers, for that matter). The runtime does not need to treat this code specially in order to "strip out" assertions from classes when they're disabled! Because $assertionsEnabled is final, the JIT can easily determine that the entire if statement has no effect, and eliminate it in the compiled code that it generates. Present day JITs (including HotSpot) do this optimization. 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. This sketch glosses 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. Note that this implementation does not support control over assertions in "low-level" system classes (ClassLoader and any classes that are loaded before ClassLoader). At present, there are no assertions in these low-level systems classes, so this is not viewed as a significant issue. Appendix V. Design FAQThis collection of frequently asked questions concerning the design of the assertion facility is meant to serve as a design rationale.General Questions
Although ad hoc implementations are possible, they are of necessity either ugly (requiring an if statement for each assertion) or inefficient (evaluating the condition even if assertions are disabled). Further, each ad hoc implementation has its own means of enabling and disabling assertions, which lessens the utility of these implementations, especially for debugging in the field. As a result of these shortcomings, assertions have never become a part of the Java culture. Adding assertion support to the platform stands a good chance of rectifying this situation. We recognize that a language change is a serious effort, not to be undertaken lightly. The library approach was considered very seriously. It was, however, deemed essential that the runtime cost of assertions be negligible if they are disabled. In order to achieve this with a library, the programmer is forced to hard-code each assertion as an if statement. Many programmers would not do this. Either they'd omit the if statement and performance would suffer, or they'd ignore the facility entirely. Note also that assertions were contained in James Gosling's original specification for Java, nee Oak. Assertions were removed from the Oak specification because time constraints prevented a satisfactory design and implementation. We seriously considered providing such a facility, but were unable to convince ourselves that it is possible to graft it onto the Java programming language without massive changes to the Java platform libraries, and massive inconsistencies between old and new libraries. Further, we were not convinced that such a facility would preserve the simplicity that is Java's hallmark. On balance, we came to the conclusion that a simple boolean assertion facility was a clear knee-in-the-curve, and far less risky. It's worth noting that adding a boolean assertion facility to the language doesn't preclude adding a full-fledged design-by-contract facility at some time in the future. The simple assertion facility does enable a limited form of design-by-contract style programming. The assert statement is appropriate for postcondition and class invariant checking. Precondition checking should still be performed by checks inside methods that result in particular, documented exceptions, such as IllegalArgumentException and IllegalStateException. Providing such a construct would encourage programmers to put complex assertions inline, when they are better relegated to separate methods. Compatibility
Yes, for source files. (Binaries for classes that use assert as an identifier will continue to work fine.) To ease the transition, we describe a strategy whereby developers can continue using assert as an identifier during a transitional period. Yes. Class files will contain calls to the new ClassLoader methods, such as, desiredAssertionStatus. If a class file containing calls to these methods is run against an older JRE (whose ClassLoader class doesn't define the methods), the program will fail at run time, throwing a NoSuchMethodError. While it would be possible to circumvent this problem by catching the NoSuchMethodError, Java platform implementors are under no obligation to do this. Detailed Syntax and Semantics
There is no compelling reason to restrict the type of this expression. Allowing arbitrary types provides convenience, e.g., for developers who want to associate a unique integer code with each assertion. Further, it makes this expression feel like System.out.print(...), which is desirable. The AssertionError Class
While doing so might improve out-of-the-box usefulness of assertions in some cases, the benefit doesn't justify the cost of adding all these string constants to .class files and runtime images. Access to these objects would encourage programmers to attempt to recover from assertion failures, which defeats the purpose of the facility. This facility is best provided on Throwable, so it may be used for all throwables, and not just just assertion errors. We intend to enhance Throwable to provide this functionality in the same release in which the assertion facility first appears. Enabling and Disabling Assertions
It is a firm requirement that it be possible to enable assertions in the field, for enhanced serviceability. It would have been possible to also permit developers to eliminate assertions from object files at compile time. However, since assertions can contain side effects (though they should not), such a flag could alter the behavior of a program in significant ways. It is viewed as good thing that there is only one semantics associated with each valid Java program. Also, we want to encourage users to leave asserts in object files so they can be enabled in the field. Finally, the standard Java "conditional compilation idiom" described in JLS 14.19 can be used to achieve this effect for developers who really want it. Hierarchical control is useful, as programmers really do use package hierarchies to organize their code. For example, package-tree semantics allow assertions to be enabled or disabled in all of Swing in one fell swoop. No action (other than perhaps a warning message) is necessary or desirable if it's too late to set the assertion status. An exception seems unduly heavyweight. It's not clear that there would be any use for the resulting method The method isn't designed for application programmer use, and it seems inadvisable to make it slower and more complex than necessary. While applets have no reason to call any of the ClassLoader methods for modifying assertion status, allowing them to do so seems harmless. At worst, an applet can mount a weak denial-of-service attack by turning on asserts in classes that have yet to be loaded by its URLClassLoader. Such a construct would encourage people to inline complex assertion code, which we view as a bad thing: if (assertsEnabled()) { ... }Further, it is straightforward to query the assert status atop the current API, if you feel you must: boolean assertsEnabled = false; assert assertsEnabled = true; // Intentional side-effect!!! // Now assertsEnabled is set to the correct value |