We envision a world where no exceptions are raised; instead, language semantics are changed
so that operations are total functions. Either an operation executes normally or tailored
recovery code is applied where exceptions would have been raised. As an initial step and
evaluation of this idea, we propose to transform programs so that null pointer dereferences
are handled automatically without a large runtime overhead. We increase robustness by replacing
code that raises null pointer exceptions with error handling code, allowing the program to
continue execution. Our technique first finds potential null pointer dereferences and then
automatically transforms programs to insert null checks and error-handling code. These
transformations are guided by composable, context sensitive recovery policies. Error-handling
code may, for example, create default objects of the appropriate types, or restore data
structure invariants. If no null pointers would be dereferenced, the transformed program behaves
just as the original. We applied our transformation in experiments involving multiple benchmarks,
the Java Standard Library, and externally reported null pointer exceptions. Our technique is
able to handle the reported exceptions and allow the programs to continue to do useful work, with
an average execution time overhead of less than 1% and an average byte code space overhead of 22%.
Null pointer exception management is a logical starting point for changing Java’s semantics for
exception handling, because of the simplicity and regularity and null pointer exceptions. Null pointer
exceptions, while conceptually simple, remain prevalent in practice. Null pointer dereferences are not
only frequent, but also catastrophic and are “a very serious threat to the safety of programs”. Many
classes of null pointer exceptions can be found automatically by static analyses, and they have been
reported as one of the top ten causes of common web application security risks. Addressing such risks
with fault-tolerance techniques is a promising avenue. For example, techniques that mask memory errors
have successfully eliminated security vulnerabilities in servers.
Though Java already provides an infrastructure for exceptions, the current state of the language is
only a partial solution. Java makes a clear distinction between checked and unchecked exceptions.
The former are included in method type signatures and must be addressed by the programmer; the latter
may be ignored without compiler complaint. Unchecked exceptions should also be documented and properly
handled by the language, in a systematic and universal manner. Java treats null pointer exceptions as
unchecked by default, while APPEND’s approach to null pointer prevention is similar to the way Java
treats checked exceptions: an undesirable situation or behavior is identified by the programmer, and
some error handling code is generated. One reason null pointer exceptions are not treated as checked by
Java is that there are many potential sources of null pointer dereferences and different recovery
situations would have to be embedded in multiple local catch blocks explicitly: a time-consuming and
error-prone task. First, it would be difficult to identify, for each null pointer exception that
propagated upwards, what kind of recovery code could be applied, without knowing context information.
Secondly, Java’s current exception handling mechanisms also open up the possibility of a breakdown of
encapsulation and information hiding, as implementation details from lower levels of scope are raised
to the top level of the program. A solution to null pointer exceptions that is able to prevent or mask
them in a way that is both reasonable and accessible to the programmer has yet to be implemented.
APPEND is a program transformation that changes Java’s null pointer exception handling by automatically
inserting null checks and error-handling code. No program annotations are required, and developers need
not wade through defect reports. Programs are modified according to composable recovery policies.
Recovery policies are executed at compile-time and, depending on the context, recovery code is inserted
that is then executed at run-time if the null checks fail. Recovery policies are conceptually related to
theorem prover tactics and tacticals or to certain classes of aspect-oriented programming. If no null
values are dereferenced at run-time, the transformed program behaves just as the original program. If the
original program would dereference a null value, the transformed program instead executes the policy
dictated error-handling code, such as creating a default value on the fly or not calculating that
expression. Previous research has suggested that programs might successfully continue even with discarded
instructions; we present and measure a concrete, low-level, annotation-free version of such a system, and
extend it to allow for user-specified actions.
The idea behind this approach is that null pointer dereferences are undesirable, especially in circumstances
where they are involved in non-critical computations where the program is forced to crash if one is
encountered. If we had a way of preventing the program from ceasing execution, while logging and performing
some sort of recovery code instead of raising an exception, we hypothesize that there are many applications
where this behavior would be preferred. Therefore, it becomes possible to check every pointer dereference
in the code for nullness, and to include recovery code for every such instance. We argue that this is a
practical and preferable way to deal with null pointer exceptions in Java.Because we intend to check every
pointer dereference for nullness, we could have chosen to take advantage of the existing null checking of
the Java virtual machine. Given the low overhead of our tool, we chose to work on the application level
instead, to remain portable and not have to rely on a single modified JVM instantiation.The transformation
can be implemented directly atop existing program transformation frameworks and dovetails easily with
standard development processes.