Reducing "exception is never thrown in body of corresponding try statement" Error to a Warning

Mike Duigou openjdk at duigou.org
Mon Oct 21 21:20:10 UTC 2019


While the proposed change doesn't directly alter the inability to refine 
(narrow) the exception specification in method/constructor signatures 
because use of narrowing/elimination would break overrides you are 
correct in pointing it out as a a potential issue. I also agree that a 
sub-class throwing a wider set of checked exceptions than the parent 
will always need to be a compilation error.

I do believe that there are cases such as private methods/constructors, 
final classes, and internal APIs where the ability to narrow/remove 
exception specifications will be useful. Addressing the original intent 
of the error, I believe that the "All subtypes of exception that the try 
block can throw have already been caught." warning introduced in Java SE 
7 has proved to be sufficient warning for the similar problem. This 
proposed adjustment would bring the severity of "never-thrown" and 
"already-handled" cases into alignment which seems appropriate.

For -Werror users the effect of reducing the "never-thrown" error to a 
warning would be to allow suppression of the warning which might be 
useful in the case of a need to link against a library which adjusted 
method exception specifications between versions. As an error the 
"never-thrown" can't be surpressed and I can report that when a certain 
Apache library removed exceptions from several methods it was huge 
annoyance to support two versions of the library simultaneously (Maven 
hacks to "generate" (select) appropriate source based on a profile of 
which library version was being used. Yuck!)

Cheers,

Mike

On 2019-10-20 15:55, Brian Goetz wrote:
> I will admit that this strictness is sometimes annoying.
> 
> Your goal here, which is a good one, is to allow narrowing the set of
> checked exceptions to be a source-compatible change.  I seriously
> doubt much consideration was given in 1995 for the
> source-compatibility of _removing_ exceptions from a descriptor, even
> though this should be a compatible change (as it narrows the range of
> accepted effects.)  You correctly argue that this would be a
> compatible change for clients that hadn’t gotten the memo about the
> narrowed exception set.
> 
> Unfortunately, there’s another group we have to worry about —
> subclasses.  Because, if you have a hierarchy:
> 
>     class Foo {
>         void m() throws Exception { };
>     }
> 
>     class Bar extends Foo {
>         @Override
>         void m() throws Exception { };
>     }
> 
> where Foo and Bar are in separate maintenance domains, then reducing
> the set of exceptions in Foo:
> 
>     class Foo {
>         void m() throws IOException { };
>     }
> 
> now renders Bar uncompilable, because it throws a wider set of
> exceptions than the method it overrides (violating LSP) and therefore
> reducing the set of exceptions for overridable methods is not a
> source-compatible change for this group.
> 
> (General observation: It’s really easy to forget the “subclasses” part
> of compatibility — it’s easy to focus only on the clients.  This has
> bitten me more than once.)
> 
> Given that this change still would not allows us to say reducing the
> set of thrown exceptions is a source-compatible change, I’d think that
> the benefit is reduced somewhat?
> 
> 
>> On Oct 20, 2019, at 6:36 PM, Mike Duigou <openjdk at duigou.org> wrote:
>> 
>> Hello all;
>> 
>> As part of my Oracle CodeOne talk "Exceptions 2020" I made two 
>> proposals for improving Java exceptions. One of the proposals was to 
>> remove the compile time error produced when and exception is caught 
>> that is not thrown by the enclosed statements. Specifically I am 
>> proposing the removal of the following section from JLS 11.2.3
>> 
>>    "It is a compile-time error if a catch clause can catch checked 
>> exception class E1
>>     and it is not the case that the try block corresponding to the 
>> catch clause can
>>     throw a checked exception class that is a subclass or superclass 
>> of E1, unless E1
>>     is Exception or a superclass of Exception."
>> 
>> The advantage of removal of this rule is that removal allows API 
>> authors to better document the actual exceptions thrown by 
>> constructors and methods. Vestigial or inaccurate throws 
>> specifications can be eliminated without "breaking" calling code. This 
>> makes it easier to evolve the API by narrowing (including to empty), 
>> the exception specification of method without impacting existing code 
>> which uses the API.
>> 
>> To allow application authors to clean up dead code an equivalent 
>> warning would still be provided and probably should be encouraged by 
>> the JLS specification. The warning would alert them that the catch 
>> clause will not be executed.
>> 
>> As an API author I have been frustrated many times by being unable to 
>> reduce or remove a method's exception specification without causing 
>> compatibility issues for my users and I have also been frustrated when 
>> libraries did choose to alter their method exception specifications 
>> and "broke" my code which caught the no longer thrown exceptions. Does 
>> this seem like a feature/change worth pursuing?
>> 
>> Cheers,
>> 
>> Mike
>> 
>> The changes to javac needed to accomplish this change are quite 
>> simple:
>> 
>> diff --git 
>> a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java 
>> b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java
>> index 0c1fead9a4..f7793d0605 100644
>> --- 
>> a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java
>> +++ 
>> b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java
>> @@ -1271,7 +1277,7 @@ public class Flow {
>>             } else if (!chk.isUnchecked(pos, exc) &&
>>                     !isExceptionOrThrowable(exc) &&
>>                     !chk.intersects(exc, thrownInTry)) {
>> -                log.error(pos, Errors.ExceptNeverThrownInTry(exc));
>> +                log.warning(pos, 
>> Warnings.ExceptNeverThrownInTry(exc));
>>             } else {
>>                 List<Type> catchableThrownTypes = 
>> chk.intersect(List.of(exc), thrownInTry);
>>                 // 'catchableThrownTypes' cannnot possibly be empty - 
>> if 'exc' was an
>> diff --git 
>> a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties 
>> b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties
>> index 1f29df0704..e29b5583e4 100644
>> --- 
>> a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties
>> +++ 
>> b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties
>> @@ -533,10 +533,6 @@ compiler.err.error.reading.file=\
>> compiler.err.except.already.caught=\
>>     exception {0} has already been caught
>> 
>> -# 0: type
>> -compiler.err.except.never.thrown.in.try=\
>> -    exception {0} is never thrown in body of corresponding try 
>> statement
>> -
>> # 0: symbol
>> compiler.err.final.parameter.may.not.be.assigned=\
>>     final parameter {0} may not be assigned
>> @@ -2019,6 +2015,14 @@ 
>> compiler.warn.unchecked.generic.array.creation=\
>> compiler.warn.unchecked.varargs.non.reifiable.type=\
>>     Possible heap pollution from parameterized vararg type {0}
>> 
>> +# 0: type
>> +compiler.warn.except.never.thrown.in.try=\
>> +    exception {0} is never thrown in body of corresponding try 
>> statement
>> +
>> # 0: symbol
>> compiler.warn.varargs.unsafe.use.varargs.param=\
>>     Varargs method could cause heap pollution from non-reifiable 
>> varargs parameter {0}


More information about the jdk-dev mailing list