It is mandatory to handle checked exceptions in Java. The compiler enforces this rule by making sure that every possible checked exception is either declared using the throws
clause or handled within a try-catch
block. Unfortunately, this guarantee does not carry over to the JVM runtime environment, preventing the caller from determining which exceptions the callee can throw.
Checked exceptions provide a checklist of unusual events that can be handled during program execution whereas unchecked exceptions involve programming mistakes that should be caught early, without expecting the Java runtime to stage a recovery. Undeclared checked exceptions resemble the latter in behavior as the compiler does not force the programmer to handle them.
A common argument favoring undeclared checked exceptions (even unchecked exceptions) is that the caller does not have to define innumerable catch
blocks for each specific checked exception that the callee can throw. Likewise, handling each exception within the callee is believed to clutter-up the code. One way to deal with this issue is to wrap the specific exceptions into a new exception, appropriate for the abstraction level. For example, a method may throw an IOException
instead of specific exceptions such as FileNotFoundException
. While exception handling should be as specific as possible, a compromise can be struck to increase the code readability in complex systems [[Venners 03]]. However, wrapping checked exceptions into a broader exception class may not provide enough context for a recovery at the top level. Unchecked exceptions and undeclared checked exceptions, on the other hand, help reduce clutter in code but are unsuitable when a client is expected to recover from an exceptional condition.
Clients or callers are expected to know the exceptions that the underlying code can throw. For this reason, developers must sufficiently document all possible checked exceptions. Undeclared checked exceptions are a special class of exceptions that need diligent documentation. Security critical software must almost always make this contract explicit. Yet another difficulty in dealing with them is that sensitive exceptions cannot be sanitized before delivery, in the absence of a dedicated exception reporter. Ideally, undeclared checked exceptions should be avoided.
Noncompliant Code Example
This noncompliant code example uses the sun.misc.Unsafe
class. All sun.*
classes are undocumented because using them can cause portability and backward compatibility issues. This noncompliant code example is risky from this standpoint and by its capacity to throw undeclared checked exceptions.
A class that is loaded by the bootstrap class loader has the authority to call the static
factory method Unsafe.getUnsafe()
. An average developer may be unable to fulfill this requirement unless the sun.boot.class.path
system property is modified. One alternative is to change the accessibility of the field that holds an instance of Unsafe
using reflection. This is only possible if the current security manager allows it (by violating ENV04-J. Do not grant ReflectPermission with target suppressAccessChecks). To throw an undeclared checked exception, the caller just needs to use the Unsafe.throwException()
method.
import java.io.IOException; import java.lang.reflect.Field; import sun.misc.Unsafe; public class UnsafeCode { public static void main( String[] args ) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { Field f = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); Unsafe u = (Unsafe) field.get(null); u.throwException(new IOException("No need to declare this checked exception")); } }
Noncompliant Code Example
Any checked exception thrown by the default constructor of Class.newInstance()
is propagated even if it is not declared explicitly. On the contrary, the java.lang.reflect.Constructor.newInstance()
method wraps any exceptions thrown from within the constructor into a checked exception called InvocationTargetException
.
public class BadNewInstance { private static Throwable throwable; private BadNewInstance() throws Throwable { throw throwable; } public static synchronized void undeclaredThrow(Throwable throwable) { // These two should not be passed if (throwable instanceof IllegalAccessException || throwable instanceof InstantiationException) { throw new IllegalArgumentException(); // Unchecked, no declaration required } BadNewInstance.throwable = throwable; try { BadNewInstance.class.newInstance(); } catch (InstantiationException e) { /* dead code */ } catch (IllegalAccessException e) { /* dead code */ } finally { BadNewInstance.throwable = null; } // Avoid memory leak } } public class UndeclaredException { public static void main(String[] args) { // No declared checked exceptions BadNewInstance.undeclaredThrow(new Exception("Any checked exception")); } }
Even if the programmer wishes to catch and handle the possible checked exceptions, the compiler refuses to believe that any can be thrown in the particular context. One way to deal with this difficulty is to catch Exception
and check whether the possible checked exception is an instance of it else re-throw the exception. This is shown below. The most obvious pitfall is that this technique is easy to bypass whenever an unanticipated checked exception is thrown.
public static void main(String[] args) { try { BadNewInstance.undeclaredThrow(new IOException("Any checked exception")); } catch(Exception e) { if (e instanceof IOException) { System.out.println("IOException occurred"); } else if (e instanceof RuntimeException) { throw (RuntimeException) e; } else { //some other unknown checked exception } } }
Compliant Solution
Prefer the method Constructor.newInstance()
over Class.newInstance()
. An alternative is to use the builder interface recommended by Bloch [[Bloch 08]].
// Generic type for a builder used to build any object of type T public interface Builder<T> { public T build(); }
A client can pass a builder to a method and request the creation of an object. A bounded wildcard type should be used to constrain the builder's type parameter. In the code snippet that follows, a US Dollar (USD) is built from coins of different denomination.
USD buildCurrency(Builder<? extends denomination> currencyBuilder) { /* ... */ }
For further details on implementing the builder pattern, refer to OBJ04-J. Do not allow partially initialized objects to be accessed. In the example described in that rule, the Currency.Builder
class must implement the Builder interface highlighted in this recommendation.
Noncompliant Code Example
An unchecked cast of a generic type with parameterized exception declaration can also result in unexpected checked exceptions. The compiler complains unless the warnings are suppressed.
interface Thr<EXC extends Exception> { void fn() throws EXC; } public class UndeclaredGen { static void undeclaredThrow() throws RuntimeException { @SuppressWarnings("unchecked") // Suppresses warnings Thr<RuntimeException> thr = (Thr<RuntimeException>)(Thr) new Thr<IOException>() { public void fn() throws IOException { throw new IOException(); } }; thr.fn(); } public static void main(String[] args) { undeclaredThrow(); } }
Noncompliant Code Example
According to the Java API [[API 06]], class Thread
documentation,
[
Thread.stop()
] may be used to generate exceptions that its target thread is unprepared to handle (including checked exceptions that the thread could not possibly throw, were it not for this method). For example, the following method is behaviorally identical to Java's throw operation, but circumvents the compiler's attempts to guarantee that the calling method has declared all of the checked exceptions that it may throw.
static void sneakyThrow(Throwable t) { Thread.currentThread().stop(t); }
It is also possible to disassemble a class, remove any declared checked exceptions and reassemble the class so that checked exceptions are thrown at runtime when this class is used [[Roubtsov 03]]. Simply, compiling against a class that declares the checked exception and supplying one at runtime that doesn't, also suffices. Similarly, a different compiler than javac
might handle checked exceptions differently. Yet another way is to furtively use the sun.corba.Bridge
class. All these methods are strongly discouraged.
Compliant Solution
Refrain from employing code (legitimate or hostile) that is capable of throwing undeclared checked exceptions. If the source code can throw them, explicitly document the behavior. Finally, do not use deprecated methods such as Thread.stop()
.
Risk Assessment
Failure to document undeclared checked exceptions can result in checked exceptions that the caller is unprepared to handle. This violates the safety property.
Rule |
Severity |
Likelihood |
Remediation Cost |
Priority |
Level |
---|---|---|---|---|---|
EXC06- J |
low |
unlikely |
high |
P1 |
L3 |
Automated Detection
TODO
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
References
[[JLS 05]] Chapter 11: Exceptions
[[Venners 03]] "Scalability of Checked Exceptions"
[[Roubtsov 03]]
[[Schwarz 04]]
[[Goetz 04b]]
[[Bloch 08]] Item 2: "Consider a builder when faced with many constructor parameters"
[[MITRE 09]] CWE ID 703 "Failure to Handle Exceptional Conditions", CWE ID 248 "Uncaught Exception"
EXC01-J. Use a class dedicated to reporting exceptions 13. Exceptional Behavior (EXC) EXC07-J. Restore prior object state on method failure