Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to find potential unchecked exceptions in Java?

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
like image 245
ocroquette Avatar asked Nov 10 '16 12:11

ocroquette


People also ask

What are all unchecked exceptions in Java?

Unchecked exceptions in Java are those exceptions that are checked by JVM, not by java compiler. They occur during the runtime of a program. All exceptions under the runtime exception class are called unchecked exceptions or runtime exceptions in Java. We can write a Java program and compile it.

Which exceptions are unchecked exceptions?

An unchecked exception is the one which occurs at the time of execution. These are also called as Runtime Exceptions. These include programming bugs, such as logic errors or improper use of an API. Runtime exceptions are ignored at the time of compilation.

What are unchecked exceptions give an example?

Unchecked exception example Unchecked exceptions result from faulty logic that can occur anywhere in a software program. For example, if a developer invokes a method on a null object, an unchecked NullPointerException occurs.

How do you fix unchecked exceptions in Java?

Handling ArrayIndexoutOfBoundException: Try-catch Block we can handle this exception try statement allows you to define a block of code to be tested for errors and catch block captures the given exception object and perform required operations. The program will not terminate.


2 Answers

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.

like image 96
Ben Holland Avatar answered Oct 06 '22 16:10

Ben Holland


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=

like image 29
Thogor Avatar answered Oct 06 '22 14:10

Thogor