Reputation: 3259
In accordance with the Java specification, The Java compiler verifies automatically that all checked exceptions are caught, based on "throw" statements and method signatures, and ignores unchecked exceptions.
However, sometimes it would be useful for the developer to find out what unchecked exceptions can be thrown, for instance some 3rd party code might throw unchecked exceptions in situations where the developer would tend to expect a checked exception (like Long.parseLong). Or a developer might throw an unchecked exception as a placeholder for a future checked exception and forget to replace it.
In these examples, it is theoretically possible to find these uncaught unchecked exception. In the first case, the signature of Long.parseLong indicates that it throws NumberFormatException, and in the second case, the source code is available, so the compiler knows what unchecked exceptions are thrown.
My question is: is there a tool that can report these cases? Or maybe a way to let the Java compiler treat temporarily unchecked exceptions are checked exceptions? That would be very useful to verify manually and fix potential bugs that would otherwise cause a crash of the whole thread or application at runtime.
EDIT: after some answers, I have to underline that my goal is not to find the exhaustive list of unchecked exceptions possible in the system, but potential bugs due to unchecked exceptions. I think it boils down to the two cases:
Upvotes: 10
Views: 4305
Reputation: 2329
Yes you can write a static analysis to do this. I've done something similar myself and wrote mine in a program analysis tool called Atlas. Here: https://github.com/EnSoftCorp/java-toolbox-commons/.../ThrowableAnalysis.java is some code that might be helpful for what you need, it statically computes matches for throw sites and potential catch sites in a piece of software (conservatively in that it does not consider path feasibility). For your case you are interested in throw sites that do not have a corresponding catch block.
Here are the important bits of the analysis.
In the Atlas Shell you could write the following queries to find all the unchecked throwables.
var supertypeEdges = Common.universe().edgesTaggedWithAny(XCSG.Supertype)
var errors = supertypeEdges.reverse(Common.typeSelect("java.lang", "Error"))
var uncheckedExceptions = supertypeEdges.reverse(Common.typeSelect("java.lang", "RuntimeException"))
show(errors.union(uncheckedExceptions))
Any exception that can be caught at runtime (checked or unchecked) must have a corresponding "throw" site. While a thrown checked exception must be declared in a method signature, it is not required to be declared for a thrown unchecked exception. However this isn't really that important since we can detect all thrown unchecked exceptions simply by looking at the type hierarchy as we discussed in step 1.
To match the throw site with the corresponding catch block we must remember that a thrown exception propogates back up the call stack until it is caught (or crashes the program when it is not caught by the main method or thread entry point). To do this analysis you need a call graph (the more precise the call graph is the more accurate your analysis will be here). For each throw of an unchecked exception type step backwards along the call graph to the callsite of the method that could throw the unchecked exception. Check if the callsite is contained within a try block (or has a trap region if you are analyzing bytecode). If it is you must check the compatibility of the catch blocks/trap regions and determine if the exception will be caught. If the exception is not caught repeat the process stepping backwards along the call graph to each callsite until the exception is caught or there is no possible catch block.
Using the ThrowableAnalysis code I shared earlier you could bring it all together to find each uncaught thrown unchecked throwable types.
public class Analysis {
// execute show(Analysis.run()) on the Atlas shell
public static Q run(){
Q supertypeEdges = Common.universe().edgesTaggedWithAny(XCSG.Supertype);
Q errors = supertypeEdges.reverse(Common.typeSelect("java.lang", "Error"));
Q uncheckedExceptions = supertypeEdges.reverse(Common.typeSelect("java.lang", "RuntimeException"));
Q typeOfEdges = Common.universe().edgesTaggedWithAny(XCSG.TypeOf);
Q thrownUncheckedThrowables = typeOfEdges.predecessors(errors.union(uncheckedExceptions)).nodesTaggedWithAny(XCSG.ThrownValue);
AtlasSet<Node> uncaughtThrownUncheckedThrowables = new AtlasHashSet<Node>();
for(Node thrownUncheckedThrowable : thrownUncheckedThrowables.eval().nodes()){
if(ThrowableAnalysis.findCatchForThrows(Common.toQ(thrownUncheckedThrowable)).eval().nodes().isEmpty()){
uncaughtThrownUncheckedThrowables.add(thrownUncheckedThrowable);
}
}
Q uncaughtThrownUncheckedThrowableMethods = Common.toQ(uncaughtThrownUncheckedThrowables).containers().nodesTaggedWithAny(XCSG.Method);
Q callEdges = Common.universe().edgesTaggedWithAny(XCSG.Call);
Q rootMethods = callEdges.reverse(uncaughtThrownUncheckedThrowableMethods).roots();
Q callChainToUncaughtThrowables = callEdges.between(rootMethods, uncaughtThrownUncheckedThrowableMethods);
return callChainToUncaughtThrowables.union(Common.toQ(uncaughtThrownUncheckedThrowables));
}
}
Here's a screenshot of the result of running this code on the following test case.
public class Test {
public static void main(String[] args) {
foo();
}
private static void foo(){
throw new Pig("Pigs can fly!");
}
public static class Pig extends RuntimeException {
public Pig(String message){
super(message);
}
}
}
Important Caveats: You must consider whether or not to do whole program analysis here. If you only analyze your code and not the full JDK (several million lines of code) then you will only detect uncaught runtime exceptions which originate inside your application. For example you would not catch "test".substring(0,10) which throws an out of bounds exception inside the substring method declared in the String class in the JDK. While Atlas supports partial or whole program analysis using Java source or bytecode and can scale up to the full JDK, you would need to allocate about an hour of pre-processing time and 20 gigabytes more memory if you plan to include the full JDK.
Upvotes: 6
Reputation: 321
There is an Intellij plugin that can help you discover unchecked exceptions. You can customize the search process to include/exclude libraries when searching for them.
https://plugins.jetbrains.com/plugin/8157?pr=
Upvotes: 5
Reputation: 718708
My question is: is there a tool that can report these cases?
AFAIK, no.
Or maybe a way to let the Java compiler treat temporarily unchecked exceptions are checked exceptions?
AFAIK, no.
While tools like this are theoretically possible (with some caveats1), they would be close to useless in practice. If you rely solely on local analysis of methods, most ordinary Java would be flagged as potentially throwing a wide range of unchecked exceptions. Some of these could be excluded with some simple non-local analysis, but that would be nowhere like enough to avoid excessive "false positives".
IMO, there is no practical way to eliminate all runtime exceptions.
What you should be doing is to combining the following practice in order to reduce the number of bugs (including unexpected runtime exceptions) that make it into production code
Thorough and methodical testing; e.g. by developing automated unit and system test-suites, and using coverage tools to help you identify codepaths that have not been tested.
Use of static analysis tools like PMD and FindBugs that can detect certain classes of problem. You can help these and similar tools by using annotations like @NotNull
.
Code reviews.
Following good coding practice, especially when developing multi-threaded code.
But note that these practices are expensive, and they don't eliminate all bugs.
Some of the other answers seem to be suggestion that you should catch all exceptions (e.g. Exception
, RuntimeException
or even Throwable
) as a way of avoiding crashes.
This is misguided. Yes, you can "catch them all" (this is called Pokemon exception handling!) but you can't safely recover from an arbitrary unexpected exception. In general, the only entirely safe thing to do when you get an unexpected exception is to bail out.
1 - These caveats are: 1) the analyser needs to be aware of the Java language constructs that can throw unchecked exceptions implicitly; e.g. instance method call can throw a NPE, a new
can throw an OOOME, etc. 2) you need to analyse all library methods used by your code, including 3rd-party libraries, 3) Java exceptions could be thrown from native code, and 4) things involving static and "bytecode engineering" need to be considered.
Upvotes: 2
Reputation: 140417
Image a simple method like
public Foo bar(String input) {
return new Foo(input.charAt(0));
}
Alone that method could throw at least a NullPointerException or some OutOfBounds thing.
And the catch is: in order to see "all" potential exceptions, your tool would have to check each and any line of code (compiled or source) that goes into your applications.
I don't see how this could in any way be "manageable".
And it gets worse, what if
public Foo(char whatever) {
String klazz = ... god knows
throw (RuntimeException) Class.forName(klazz).newInstance();
Of course, that would need some try/catch for the checked exceptions that the reflection code has; but the point is: understand the whole universe of potential exceptions might end up in some "pretty huge map" drawn for you. With so many paths/branches in it that you never find the 0.001% of interesting paths in there.
Upvotes: -1
Reputation: 19776
It's not possible to find unchecked exceptions in many cases, or you might as well end up with a very long list of possible exceptions that could occur.
When is it not possible to find unchecked exceptions? Assume you want to call a method of an interface. One implementation might throw certain unchecked exceptions, others won't. The compiler can't know because it's only known at runtime.
Why could you end up with a very long list of possible exceptions? Well, pretty much every method might throw NullPointerException
, in case you provide null
parameters which are not explicitly checked. If they are checked, there's probably an IllegalArgumentException
instead. Furthermore, every single method which this method calls might also throw different unchecked exceptions, which would have to be added to the list. You could run into a ClassNotFoundError
or OutOfMemoryError
anytime, which would also have to be added to that list...
Upvotes: 0
Reputation: 608
There is no way to get a list of possible unchecked exceptions. Since they are not declared anywhere (they are created on the fly) it's just not possible without a pretty specific code analyzer tool--and even then it might not catch some from compiled library classes.
For examples of tricky things to predict consider anything that allocates memory can throw an out of memory exception and you could even instantiate an exception reflectively which would be pretty much impossible to find with ANY static analysis tools.
If you are really really paranoid you could catch RuntimeException
, that should get all unchecked exceptions that you would want to deal with--it's not a recommended tactic but can prevent your program from failing from some unknown/unseen future bug.
Upvotes: 0