How to find potential unchecked exceptions in Java?
Asked Answered
A

6

10

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:

  1. a method's signature indicates that it throws an unchecked exception, and the caller doesn't catch it
  2. a method's body throws explicitly unchecked exceptions, and the caller doesn't catch them
Acker answered 10/11, 2016 at 12:23 Comment(1)
@Carlos When you analyse each and any line of code that goes into your system, you can find all potential sources of runtime exceptions. The question is of course, how useful that would be. Probably not much.Agapanthus
M
6

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.

  1. All exceptions checked or unchecked must extend Throwable. It sounds like you are only interested in "unchecked" throwables so you should consider classes that directly extend or are children of a class that extends Error or RuntimeException.

Throwable Hierarchy

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

  2. 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);
        }
    }

}

Analysis Result Screenshot

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.

Mcgraw answered 10/11, 2016 at 16:6 Comment(1)
Hello Ben, thanks for all the information. It looks like a workable approach.Acker
H
5

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=

Housefly answered 16/11, 2016 at 15:26 Comment(0)
F
2

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.

Follower answered 10/11, 2016 at 13:24 Comment(0)
M
0

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.

Massasoit answered 10/11, 2016 at 12:31 Comment(2)
It is very simple: in a larger application, you catch for Exception at some high level point, simply to avoid exactly that: crashing on something that you never saw coming. And for the record: when you run into "out of memory" an Error is thrown. Errors are not exceptions (or vice versa)!Agapanthus
@Agapanthus - Errors are exceptions. They are just not Exceptions (with a capital E). JLS 11.1.1 states - "An exception is represented by an instance of the class Throwable (a direct subclass of Object) or one of its subclasses."Follower
D
0

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

Doubleacting answered 10/11, 2016 at 12:31 Comment(0)
A
-1

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.

Agapanthus answered 10/11, 2016 at 12:39 Comment(1)
Also I don't really see what you would gain from catching NullPointerExceptions, OutOfBounceExceptions etc. pp. separatly instead of just catching the Exception superclass if you don't want your application to crash at certain points.Disproportionation

© 2022 - 2024 — McMap. All rights reserved.