diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/asm/GenericSigBase.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/asm/GenericSigBase.java index 33f321273a..5d740041eb 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/asm/GenericSigBase.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/asm/GenericSigBase.java @@ -5,6 +5,7 @@ package net.sourceforge.pmd.lang.java.symbols.internal.asm; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -201,26 +202,51 @@ abstract class GenericSigBase { private List exceptionTypes; private JTypeMirror returnType; private TypeAnnotationSetWithReferences typeAnnots; + private @Nullable String[] rawExceptions; /** Used for constructors of inner non-static classes. */ private final boolean skipFirstParam; - LazyMethodType(ExecutableStub ctx, @NonNull String descriptor, @Nullable String genericSig, @SuppressWarnings("PMD.UnusedFormalParameter") @Nullable String[] exceptions, boolean skipFirstParam) { + // TODO exceptions. Couple of notes: + // - the descriptor never contains thrown exceptions + // - the signature might not contain the thrown exception types (if they do not depend on type variables) + // - the exceptions array also contains unchecked exceptions + // + // See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.9.1 + // TODO test cases + // void foo() throws E; // descriptor "()V" signature "()V^TE;" exceptions: ??? + // void foo(E e) throws Exception; // descriptor "(Ljava.lang.Object;)V" signature "(TE;)V" exceptions: [ "java/lang/Exception" ] + // void foo() throws Exception; // descriptor "()V" signature null exceptions: [ "java/lang/Exception" ] + LazyMethodType(ExecutableStub ctx, + @NonNull String descriptor, + @Nullable String genericSig, + @Nullable String[] exceptions, + boolean skipFirstParam) { super(ctx); this.signature = genericSig != null ? genericSig : descriptor; // generic signatures already omit the synthetic param this.skipFirstParam = skipFirstParam && genericSig == null; + this.rawExceptions = exceptions; } @Override protected void doParse() { ctx.sigParser().parseMethodType(this, signature); + if (rawExceptions != null && this.exceptionTypes.isEmpty()) { + // the descriptor did not contain exceptions. They're in this string array. + this.exceptionTypes = Arrays.stream(rawExceptions) + .map(ctx.getResolver()::resolveFromInternalNameCannotFail) + .map(ctx.getTypeSystem()::rawType) + .collect(CollectionUtil.toUnmodifiableList()); + } if (typeAnnots != null) { // apply type annotations here typeAnnots.forEach(this::acceptAnnotationAfterParse); } - typeAnnots = null; // null this out. + // null this transient data out + this.rawExceptions = null; + this.typeAnnots = null; } @Override @@ -293,13 +319,20 @@ abstract class GenericSigBase { parameterTypes = TypeAnnotationHelper.replaceAtIndex(parameterTypes, idx, annotatedFormal); return; } + case TypeReference.THROWS: { + assert exceptionTypes != null : "Exception types are not set"; + int idx = tyRef.getExceptionIndex(); + JTypeMirror annotatedFormal = TypeAnnotationHelper.applySinglePath(exceptionTypes.get(idx), path, annot); + exceptionTypes = TypeAnnotationHelper.replaceAtIndex(exceptionTypes, idx, annotatedFormal); + return; + } case TypeReference.METHOD_TYPE_PARAMETER_BOUND: case TypeReference.METHOD_TYPE_PARAMETER: case TypeReference.METHOD_RECEIVER: - case TypeReference.THROWS: throw new NotImplementedException("Not yet implemented: type ref " + tyRef.getSort()); default: - throw new IllegalArgumentException("Invalid type reference for method or ctor type annotation: " + tyRef.getSort()); + throw new IllegalArgumentException( + "Invalid type reference for method or ctor type annotation: " + tyRef.getSort()); } } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symbols/TypeAnnotReflectionOnMethodsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symbols/TypeAnnotReflectionOnMethodsTest.java index a336eac19a..b1f8ff6dbd 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symbols/TypeAnnotReflectionOnMethodsTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symbols/TypeAnnotReflectionOnMethodsTest.java @@ -71,6 +71,20 @@ public class TypeAnnotReflectionOnMethodsTest { } } + @Test + public void testTypeAnnotOnThrows() { + + /* + abstract void aOnThrows() throws @A RuntimeException; + */ + + { + JMethodSig t = getMethodType(sym, "aOnThrows"); + assertHasTypeAnnots(t.getReturnType(), emptyList()); + assertHasTypeAnnots(t.getThrownExceptions().get(0), aAnnot); + } + } + private static JMethodSig getMethodType(JClassType sym, String fieldName) { return sym.streamMethods(it -> it.nameEquals(fieldName)).findFirst().get(); } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symbols/testdata/ClassWithTypeAnnotationsOnMethods.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symbols/testdata/ClassWithTypeAnnotationsOnMethods.java index 83d04b8b4c..a629dbff0a 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symbols/testdata/ClassWithTypeAnnotationsOnMethods.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symbols/testdata/ClassWithTypeAnnotationsOnMethods.java @@ -23,4 +23,12 @@ public abstract class ClassWithTypeAnnotationsOnMethods { abstract void aOnThrows() throws @A RuntimeException; + + static class CtorOwner { + + CtorOwner(@A @B int i) { } + @A CtorOwner() { } + CtorOwner(String i) throws @A RuntimeException {} + } + }