Assertion support
By Gregor Kaufmann <gregor@complang.tuwien.ac.at>
Contents
Introduction to assertions in Java
The assertion keyword in Java allows to assert the correctness of assumptions made in a program. It was first introdcuded in JDK 1.2 (see JSR41). An assertion works on an expression, evaluating to a boolean type, that must be true during the execution of a program (or else the execution halts and an exception gets thrown). Short example: a function that calcualtes from celsius to kelvin. This function might use an assertion to assure that the calculated value is not below 0.
An assertion statement comes in two forms:
assert BooleanExpression ;
assert BooleanExpression : ValueExpression ;
The "BooleanExpression" can be any java expression resulting in a boolean value. If this "BoolenExpression" evalutes to false an (unnamed) AssertionError gets thrown. The second form of the assertion statement is used to generate detailed error messages: the value of "ValueExpression" gets passed to the constructor of the thrown AssertionError exception, building a more detailed error message. An assertion statement is equal to: if (BooleanExpression == false) throw new AssertionError(ValueExpression); (without the possibility to easily turn on/off this code at runtime).
Examples:
assert val < 10;
assert val > 99: val;
- assert isValid(val): val;
- assert val.isEnabled(): val.getStatus();
Assertions can be turned on or off at runtime (turned off by default). The following options for the interpreter are available (>JDK1.2 and >CACAO-0.98):
-ea[:<packagename>...|:<classname>]
-enableassertions[:<packagename>...|:<classname>]
-da[:<packagename>...|:<classname>]
-disableassertions[:<packagename>...|:<classname>]
- -esa | -enablesystemassertions
- -dsa | -disablesystemassertions
Detailed explanation of the available options:
- -enableassertions/-ea -- Turns on assertions for all non-system/user classes
- -disableassertions/-da -- Turns off assertions for all non-system/user classes
- -enablesystemassertions/-esa -- Turns on assertions for all system/non-user classes
- -disablesystemassertions/-dsa -- Turns off assertions for all system/non-user classes
- -enableassertions/-ea:my.package... -- Turns on assertions for all classes in the "my.package" package (and all subpackages)
- -disableassertions/-da:my.package... -- Turns off assertions for all classes in the "my.package" package (and all subpackages)
- -enableassertions/-ea:Myclass -- Turns on assertions a class named "Myclass"
- -disableassertions/-da:Myclass -- Turns off assertions a class named "Myclass"
Note #1: Specifing multiple class/package names is possible.
Note #2: The assertion switches -ea/-da/-esa/-dsa are currently not implemented correctly in cacao+classpath (use cacao+openjdk or this classpath patch).
The following example shows how an assertion statement is translated into bytecode.
I've compiled the following class with JDK-6.0 (other compilers produce slightly different bytecode).
1 public class Test {
2 public static void main(String[] args) {
3 int x = 1;
4 assert x == 2 : x;
5 }
6 }
The following bytecode gets produced:
A static block is used to iniatilizes the boolean variable describing the assertion status of the class "Test". The assertion status, as set by the user/vm for, gets loaded (lines 3,4), and saved into constant_pool[2] (line 9).
1 static {};
2 Code:
3 0: ldc_w #5; //class Test
4 3: invokevirtual #6; //Method java/lang/Class.desiredAssertionStatus:()Z
5 6: ifne 13
6 9: iconst_1
7 10: goto 14
8 13: iconst_0
9 14: putstatic #2; //Field $assertionsDisabled:Z
10 17: return
The assertion status gets loaded from constant_pool[2] (line 5), if assertions are disabled the function returns immediatly (lines 6,15). Otherwise, the assertion statement gets evaluated and an AssertionError exception gets thrown (lines 6-15).
1 public static void main(java.lang.String[]);
2 Code:
3 0: iconst_1
4 1: istore_1
5 2: getstatic #2; //Field $assertionsDisabled:Z
6 5: ifne 22
7 8: iload_1
8 9: iconst_2
9 10: if_icmpeq 22
10 13: new #3; //class java/lang/AssertionError
11 16: dup
12 17: iload_1
13 18: invokespecial #4; //Method java/lang/AssertionError."<init>":(I)V
14 21: athrow
15 22: return
Implementation of assertion support in CACAO
When I started working on the assertion support for cacao, a basic functionality to toggle assertions on and off was already implemented. It was possible to toggle assertions on and off at a systemwide level, but this only worked when cacao was used together with the GNU classpath classes. Because at least something was already implemented, I decided to start my work on cacao+classpath.
Each class implements a method called desiredAssertionStatus that returns the desired assertion status of a class, see here on how this is used by an assertion statement.
The desiredAssertionStatus method in java.lang.Class of classpath looks like this:
1216 public boolean desiredAssertionStatus()
1217 {
1218 ClassLoader c = getClassLoader();
1219 Object status;
1220 if (c == null)
1221 return VMClassLoader.defaultAssertionStatus();
1222 if (c.classAssertionStatus != null)
1223 synchronized (c)
1224 {
1225 status = c.classAssertionStatus.get(getName());
1226 if (status != null)
1227 return status.equals(Boolean.TRUE);
1228 }
1229 else
1230 {
1231 status = ClassLoader.StaticData.
1232 systemClassAssertionStatus.get(getName());
1233 if (status != null)
1234 return status.equals(Boolean.TRUE);
1235 }
1236 if (c.packageAssertionStatus != null)
1237 synchronized (c)
1238 {
1239 String name = getPackagePortion(getName());
1240 if ("".equals(name))
1241 status = c.packageAssertionStatus.get(null);
1242 else
1243 do
1244 {
1245 status = c.packageAssertionStatus.get(name);
1246 name = getPackagePortion(name);
1247 }
1248 while (! "".equals(name) && status == null);
1249 if (status != null)
1250 return status.equals(Boolean.TRUE);
1251 }
1252 else
1253 {
1254 String name = getPackagePortion(getName());
1255 if ("".equals(name))
1256 status = ClassLoader.StaticData.
1257 systemPackageAssertionStatus.get(null);
1258 else
1259 do
1260 {
1261 status = ClassLoader.StaticData.
1262 systemPackageAssertionStatus.get(name);
1263 name = getPackagePortion(name);
1264 }
1265 while (! "".equals(name) && status == null);
1266 if (status != null)
1267 return status.equals(Boolean.TRUE);
1268 }
1269 return c.defaultAssertionStatus;
1270 }
The ClassLoader class stores the global assertion status for user classes and the individual status for classes and packages:
- boolean defaultAssertionStatus
Map<String, Boolean> systemPackageAssertionStatus
Map<String, Boolean> systemClassAssertionStatus
The VMClassLoader class is a special class that needs to implemented by vm's that use the classpath classes. The following methods are used by the ClassLoader to initialize the variables above (in the same order):
- boolean defaultUserAssertionStatus()
Map<String, Boolean> packageAssertionStatus()
Map<String, Boolean> classAssertionStatus()
See: java.lang.Class, java.lang.ClassLoader, java.lang.VMClassLoader
What had to be done:
- Implement the methods: defaultUserAssertionStatus, packageAssertionStatus, classAssertionStatus
- Write a function to parse the commandline options
See: src/lib/gnu/java/lang/VMClassLoader.java, src/native/vm/gnu/java_lang_VMClassLoader.c, src/vm/assertion.c and src/vm/assertion.c
For cacao+openjdk I could reuse most of the code I wrote for cacao+classpath:
The desiredAssertionStatus method in java.lang.Class of openjdk looks like this:
2849 public boolean desiredAssertionStatus() {
2850 ClassLoader loader = getClassLoader();
2851 // If the loader is null this is a system class, so ask the VM
2852 if (loader == null)
2853 return desiredAssertionStatus0(this);
2854
2855 synchronized(loader) {
2856 // If the classloader has been initialized with
2857 // the assertion directives, ask it. Otherwise,
2858 // ask the VM.
2859 return (loader.classAssertionStatus == null ?
2860 desiredAssertionStatus0(this) :
2861 loader.desiredAssertionStatus(getName()));
2862 }
2863 }
2864
2865 // Retrieves the desired assertion status of this class from the VM
2866 private static native boolean desiredAssertionStatus0(Class clazz);
The native function called by desiredAssertionStatus0 is JVM_DesiredAssertionStatus, and that's the only function that had to be implemented by me to make assertions work with cacao+openjdk. I've also corrected the implementation of the JVM_AssertionStatusDirectives function, which is used by java.lang.ClassLoader.
Patch overview
Changed/New files:
configure.ac
Added configure option "--enable-assertion" (turned on by default).
Most of the assertion code will be turned off if this switch is disabled.
Actual configure logic is in m4/assertion.m4.
m4/assertion.m4
Added autoconf logic to enable/disable building of assertion support.
src/lib/gnu/java/lang/VMClassLoader.java
Replaced the dummy implementations of:
- defaultAssertionStatus
- packageAssertionStatus
- classAssertionStatus
Added:
- defaultUserAssertionStatus
This function returns the user assertion status. Due to incorrect handling of user/system assertion status in GNU classpath, enabling (default) system assertions will also enable assertions in all user classes (a patch to fix this behaviour was submitted in August 07).
Actual implementations now call into native code to get status.
src/native/include/Makefile.am
Added:
java_util_HashMap.h
- java_util_Map.h
Headers needed to allow construction of Map/HashMap in native code.
src/native/jni.h
Removed:
_Jv_JavaVM->Java_java_lang_VMClassLoader_defaultAssertionStatus
This variable was used to hold the system's assertion status and was replaced by assertion_user_enabled and assertion_system_enabled.
src/native/vm/gnu/java_lang_VMClassLoader.c
This file holds native implementations of the VMClassLoader for GNU classpath.
The following functions were added/replaced:
- Java_java_lang_VMClassLoader_defaultUserAssertionStatus
Native implementation of VMClassLoader.defaultUserAssertionStatus. This function returns the default user assertion status of the system (user_assertion_status). Returns false if ENABLE_ASSERTION is not defined (--enable-assertions=no).
- Java_java_lang_VMClassLoader_defaultAssertionStatus
Previous implemention was replaced. Native implementation of VMClassLoader.defaultAssertionStatus. This function returns the default assertion status of the system (system_assertion_status). Returns false if ENABLE_ASSERTION is not defined (--enable-assertions=no).
- Java_java_lang_VMClassLoader_packageAssertionStatus0
Native implementation of VMClassLoader.packageAssertionStatus. Builds and returns a HashMap containing key and value pairs of packagenames and their assertion status (as expected by the ClassLoader). Returns an empty HashMap if ENABLE_ASSERTION is not defined (--enable-assertions=no).
- Java_java_lang_VMClassLoader_classAssertionStatus0
Native implementation of VMClassLoader.classAssertionStatus. Builds and returns a HashMap containing key and value pairs of classnames and their assertion status (as expected by the ClassLoader). Returns an empty HashMap if ENABLE_ASSERTION is not defined (--enable-assertions=no).
src/native/vm/sun/jvm.c
This file holds various native implementations needed by OpenJDK.
The following functions were added/replaced:
Dummy implementation was replaced. Returns the desired assertion status for a given class. Returns false if ENABLE_ASSERTION is not defined (--enable-assertions=no).
Previous implementation was incomplete. Builds and returns an AssertionStatusDirectives object. This object contains the names of all packages and classes and their assertion status.
src/vm/Makefile.am
Added (optional) building of the assertion module (assertion.c/assertion.h). Will only be built if ENABLE_ASSERTION is defined (--enable-assertions=yes).
src/vm/assertion.c
This file handles the various assertion commandline options (-ea/-da/-esa/-dsa).
src/vm/assertion.h
Defines the following global variables:
46 extern list_t *list_assertion_names;
This variable stores class/package names and their assertion status.
47 extern int32_t assertion_class_count;
This variable stores the amount of classnames specified on the commandline.
48 extern int32_t assertion_package_count;
This variable stores the amount of packagenames specified on the commandline.
49 extern bool assertion_user_enabled;
This variable stores the systemwide user default assertion status.
50 extern bool assertion_system_enabled;
This variable stores the systemwide default assertion status.
Defines the following functions:
54 void assertion_ea_da(const char *name, bool enabled);
This function is used to initialize the variables described aboved.
src/vm/vm.c
Handling of assertion commandline options was added/changed. Package and classname parsing is handled by src/vm/assertion.c (assertion_ea_da function).
src/vmcore/class.c
Added class_java_util_HashMap
src/vmcore/class.h
Added class_java_util_HashMap.
src/vmcore/linker.c
Added linking of class_java_util_HashMap.
src/vmcore/loader.c
Added loading of class_java_util_HashMap.