How to find potential unchecked exceptions in Java?

According to the Java specification, the Java compiler automatically verifies that all checked exceptions are caught based on throwing statements and method signatures, and ignores unchecked exceptions.

However, sometimes it would be useful for the developer to find out what exceptions can be thrown, for example, some third-party code can cause uncontrolled exceptions in situations where the developer will seek to check the exception (for example, Long.parseLong). Or, the developer may throw the excluded exception as a placeholder for the future checked exception and forget to replace it.

In these examples, it is theoretically possible to find this undeclared excluded exception. In the first case, the signature Long.parseLong indicates that it throws a NumberFormatException, and in the second case, the source code is available, so the compiler knows which throws are excluded.

My question is: is there a tool that can report these cases? Or maybe a way to allow the Java compiler to temporarily throw exceptions, are the exceptions checked? It would be very useful to manually check and fix possible errors that could otherwise lead to the failure of the entire thread or application at runtime.

EDIT : after some answers, I must emphasize that my goal is not to find an exhaustive list of unchecked exceptions that are possible in the system, but potential errors due to unchecked exceptions. I think it comes down to two cases:

  • the signature of the method indicates that it throws an exception that does not cause an error, and the caller does not detect it
  • the body of the method throws explicitly unchecked exceptions, and the caller does not detect them
+5
source share
6 answers

Yes, you can write a static analysis to do this. I did something similar to myself and wrote mine in a software analytic tool called Atlas . Here: https://github.com/EnSoftCorp/java-toolbox-commons/.../ThrowableAnalysis.java is code that can be useful for what you need, it statically calculates matches for throwing sites and potential catch sites in terms of software (conservative in the sense that it does not consider the possibility of implementation). For your case, you are interested in throwing sites that do not have a matching catch block.

Here are the important bits of analysis.

  • All marked or unchecked exceptions should extend Throwable . It seems that you are only interested in "unverified" metadata, so you should consider classes that are directly distributed or are children of a class that extends Error or RuntimeException .

Throwable hierarchy

In Atlas Shell, you can write the following queries to find all unverified objects.

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 detected at runtime (checked or not checked) must have a corresponding “throw” to the site. Although the thrown checked exception must be declared in the method signature, it does not need to be declared for the thrown unchecked exception. However, this is not so important, because we can detect all the selected excluded exceptions by simply looking at the type hierarchy, as we discussed in step 1.

  2. In order to correlate the throwing site with the corresponding catch block, we must remember that the excluded exception maintains a backup of the call stack until it is detected (or the program crashes if it is not caught by the main method or by writing a dot stream). For this analysis, you need a call graph (the more accurate the call graph, the more accurate your analysis will be). For each throw of an uncontrolled type of exception, step backward along the schedule of calls to the calling element of the method, which can cause an uncontrolled exception. Check if the call site is contained in the try block (or has a trap area if you are analyzing bytecode). If this is the case, you should check the compatibility of the catch / trap blocks and determine if an exception will be detected. If the exception is not caught, repeat the process, stepping back along the call schedule to each calling site until the exception is caught or there is no possible catch block.

Using the ThrowableAnalysis code I shared earlier, you could put it all together to find all non-displayable abandoned unchecked 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 is a screenshot of the result of running this code in the following test example.

 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 warnings:. You must decide whether you want to do all the analysis of the program here. If you analyze your code, and not the full JDK (several million lines of code), then you will find only runtime exceptions that occur inside your application. For example, you will not catch "test" .substring (0.10), which throws an exception from the limits inside the substring method declared in the String class in the JDK. Although Atlas supports partial or full program analysis using a Java source or bytecode and can scale to a full JDK, you will need to allocate about an hour of preprocessing time and 20 gigabytes of memory if you plan to enable full JDK.

+4
source

There is a built-in Intellij plugin that can help you detect unchecked exceptions. You can configure the search process to include / exclude libraries when they are searched.

https://plugins.jetbrains.com/plugin/8157?pr=

+4
source

My question is: is there a tool that can report these cases?

AFAIK, no.

Or maybe a way to allow the Java compiler to temporarily throw exceptions - are the exceptions checked?

AFAIK, no.

Although such tools are theoretically possible (with some reservations 1 ), they would be practically useless in practice. If you rely solely on local method analysis, most common Java will be flagged as potentially throwing a wide range of exceptions. Some of them can be excluded with a simple nonlocal analysis, but this would not be enough to avoid excessive “false positives”.


IMO, there is no practical way to eliminate all exceptions at runtime.

What you should do is combine the following practice to reduce the number of errors (including unexpected runtime exceptions) that turn them into production code

  • Thorough and methodical testing; for example, by developing automated modules and system test suites and using coverage tools to help you identify coded pages that have not been tested.

  • Using static analysis tools such as PMD and FindBugs that can identify specific classes of problems. You can help these and similar tools using annotations like @NotNull .

  • Code reviews.

  • Following good coding practice, especially when developing multi-threaded code.

But note that these methods are expensive, and they do not eliminate all errors.


Some of the other answers seem to suggest that you should catch all exceptions (like Exception , RuntimeException or even Throwable ) as a way to avoid crashes.

This is a mistake. Yes, you can “catch them all” (this is called Pokemon exception handling!), But you cannot safely recover from an arbitrary unexpected exception. In general, the only absolutely safe thing to do when you get an unexpected exception is to help out.


1 - These caveats: 1) the parser should be aware of Java language constructs that can discreetly exclude unchecked exceptions; for example, calling a method of a method can call NPE, new can call OOOME, etc. 2) you need to analyze all the library methods used by your code, including third-party libraries, 3) Java exceptions can be selected from your own code, and 4) you need to consider things related to statics and the "bytecode technique".

+3
source

Unable to get a list of possible exceptions thrown. Since they are not declared anywhere (they were created on the fly), this is simply not possible without a rather specific code analyzer tool - and even then it might not catch some of the compiled library classes.

For examples of the complex things that you need to predict, think about what allocates memory might throw an exception in memory, and you could even create an illustration of an exception that would be nearly impossible to search using any static analysis tools.

If you are really paranoid, you can catch a RuntimeException that should receive all unhandled exceptions that you would like to handle - this is not a recommended tactic, but it can prevent your program from crashing from an unknown / invisible future error.

0
source

In many cases, it is not possible to find excluded exceptions, or you can also get a very long list of possible exceptions that may occur.

When is it impossible to find excluded exceptions? Suppose you want to call an interface method. One implementation may throw some unchecked exceptions, others may not. The compiler cannot know, because it is known only at runtime.

Why can you have a very long list of possible exceptions? Well, almost every method can throw a NullPointerException if you provide null parameters that are not explicitly checked. If they are checked, perhaps an IllegalArgumentException instead. In addition, each individual method that calls this method can also raise different unchecked exceptions that must be added to the list. You could work in ClassNotFoundError or OutOfMemoryError at any time, which should also be added to this list ...

0
source

What is a simple way, for example

 public Foo bar(String input) { return new Foo(input.charAt(0)); } 

Alone, this method can throw at least a NullPointerException or some OutOfBounds thing.

And the catch: in order to see “all” potential exceptions, your tool will need to check each and every line of code (compiled or source) that goes into your applications.

I do not see how this can be "controllable."

And all the worse, what if

 public Foo(char whatever) { String klazz = ... god knows throw (RuntimeException) Class.forName(klazz).newInstance(); 

Of course, to test the exceptions that the reflection code has, some try / catch is required; but the fact is that: understanding the whole universe of potential exceptions may turn out to be in some kind of "pretty huge map" drawn for you. With so many paths / branches in it that you never find 0.001% of interesting paths there.

0
source

Source: https://habr.com/ru/post/1259542/


All Articles