Assertion support

By Gregor Kaufmann <gregor@complang.tuwien.ac.at>

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:

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:


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):

Detailed explanation of the available options:

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:

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):

See: java.lang.Class, java.lang.ClassLoader, java.lang.VMClassLoader

What had to be done:

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.

See: src/native/vm/sun/jvm.c

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:

Added:

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:

Headers needed to allow construction of Map/HashMap in native code.

src/native/jni.h

Removed:

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:

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).

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).

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).

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.

cacaowiki: assertions (last edited 2008-01-01 18:04:14 by GregorKaufmann)