ERR02-G: Do Not Catch Any Exceptions You Cannot Handle
Introduction
Only catch exceptions from which you can recover, and do not catch any exceptions you cannot handle. Certain types of programming errors, such as java.lang.NullPointerException
, often indicates an underlying issue with a null pointer dereference that needs to be fixed in the application code. It is not appropriate to handle the underlying null pointer dereference by catching the NullPointerException
instead of addressing the problem at its root. This is because multiple expressions within a try block can throw a NullPointerException
, making it difficult or impossible to determine which expression is responsible for the exception. Additionally, programs rarely remain in an expected and usable state after throwing a NullPointerException
. Even if you try to continue execution after catching and logging (or worse, suppressing) the exception, it is unlikely to be successful. Other exceptions in this category that must not be caught include:
java.lang.IllegalMonitorStateException
java.lang.ArrayIndexOutOfBoundsException
java.lang.IndexOutOfBoundsException
java.util.ConcurrentModificationException
Refrain from catching RuntimeException
, Exception
, or Throwable
as these can encompass unforeseen exceptions like NullPointerException
or ArrayIndexOutOfBoundsException
. It's challenging for methods to handle all potential runtime exceptions, so catching these broad exceptions often leads to simply logging or ignoring them, which may not address the underlying issue.
There are exceptions like NoSuchElementException that have specific use cases, such as in the example where it's caught and rethrown with additional context. This approach allows for more targeted exception handling while maintaining code clarity.
public String get(int index) {
ListIterator<String> i = listIterator(index)
try {
return i.next()
} catch(NoSuchElementException e) {
IndexOutOfBoundsException ioobe
= new IndexOutOfBoundsException("Index: " + index)
ioobe.initCause(e) // exception chaining
throw ioobe
}
}
Security Risks
Handling exceptions improperly can introduce several security vulnerabilities. Here are the primary risks:
Logging the full details of exceptions can inadvertently expose sensitive data. For example, an exception stack trace might contain information about the system's structure, data values, and even user inputs that can be exploited by attackers.
Broad exception handling can mask underlying problems that need to be fixed. If exceptions are caught and ignored or only logged, the application might continue running in an unstable state, which can be exploited by attackers.
When logging exceptions, ensure that sensitive information such as user credentials, personal data, or system details are not included. Logs should be sanitized and access to them should be restricted.
Non-Compliant Code Example
The isName function in the below example accepts a string and returns true if the string consists of two substrings separated by white space, each starting with an uppercase letter. The method should also return false if the string is not a name.
// Non-compliant Code Example
public function isName(s : String) : boolean {
try {
var names : String[] = s.split(" ")
if (names.length != 2) {
return false
}
return
(Character.isUpperCase(names[0].codePointAt(0) &&
Character.isUpperCase(names[1].codePointAt(0))
} catch (e : NullPointerException) {
return false
}
}
Unfortunately, this function returns false
if it catches a NullPointerException
. This confounds an error condition with a valid result:
print(isName("Mark Sayewich")) // true
print(isName("Cher")) // false
print(isName(null)) // false
Catching a NullPointerException
is also slower than just adding a null check.
Compliant Solution
This compliant solution explicitly tests s and throws NullPointerException
if it is a null pointer, indicating the error condition:
// Compliant Solution
function isName(s : String) : boolean {
if (s == null) throw new NullPointerException()
var names : String[] = s.split(" ")
if (names.length != 2) return false
return
(Character.isUpperCase(names[0].codePointAt(0) &&
Character.isUpperCase(names[1].codePointAt(0))
}
If the null check were removed, the subsequent call to s.equals("")
would throw a NullPointerException
when s is null. The null check explicitly indicates the programmer's intent and ensures that the exception originates from the publicly-called method and not further down the call stack. It is also good practice to validate arguments at the beginning of each function before any program state is changed.
More complex code may require explicit testing of invariants and appropriate throw statements.
Compliant Solution
This compliant solution simply omits the null pointer check:
// Compliant Solution
function isName(s : String) : boolean {
// NPE check omitted
var names : String[] = s.split(" ")
if (names.length != 2) return false
return
(Character.isUpperCase(names[0].codePointAt(0) &&
Character.isUpperCase(names[1].codePointAt(0))
}
This is acceptable, provided the program fails and no state data is changed before the exception is thrown. It is certainly appropriate for private and package-private functions.
Exceptions
ERR02-G-EX1: Task processing threads, such as worker threads in a thread pool or the Swing event dispatch thread, are permitted to catch RuntimeException when they call untrusted code through an abstraction, such as the Runnable interface [Goetz 2006, p. 161].
ERR02-G-EX2: Systems that require substantial fault tolerance or graceful degradation are permitted to catch and log general exceptions such as Throwable at appropriate levels of abstraction. For example: A real-time control system that catches and logs all exceptions at the outermost layer, followed by warm starting the system so that real-time control can continue. Such approaches are justified when program termination would have safety-critical or mission-critical consequences.
- A system that catches all exceptions that propagate out of each major subsystem logs the exceptions for later debugging and subsequently shuts down the failing subsystem (perhaps replacing it with a much simpler, limited-functionality version) while continuing other services.
Risk Assessment
Catching NullPointerException
may mask an underlying null dereference, degrade application performance, and result in hard-to-understand and maintain code. Likewise, catching RuntimeException
, Exception
, or Throwable
may unintentionally trap other exception types and prevent them from being handled properly
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
ERR02-G | Medium | Highly Likely | Medium | L12 | L1 |
Additional Resources
- Item 74, "Document All Exceptions Thrown by Each Method", Item 77, "Don't Ignore Exceptions"
Was this page helpful?