From 079d9d9a639b69b103dde096002fb7f9909d2d97 Mon Sep 17 00:00:00 2001 From: Vladimir Sitnikov Date: Sat, 16 Jan 2021 23:51:13 +0300 Subject: [PATCH] Fix IllegalArgumentException Not an array type for bridge methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit javac copies annotations to bridge methods (which is good), however the annotations might become invalid. For instance, the bridge signature for @Nullable Object[] is Object (non-array), so usage=ARRAY signature is invalid We ignore those annotations for the bridge methods. See https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6695379 fixes #102 --- java8-tests/pom.xml | 67 +++++++ .../jandex/test/bridge/BridgeMethodTest.java | 165 ++++++++++++++++++ pom.xml | 8 + src/main/java/org/jboss/jandex/Indexer.java | 40 ++++- 4 files changed, 279 insertions(+), 1 deletion(-) create mode 100644 java8-tests/pom.xml create mode 100644 java8-tests/src/test/java/org/jboss/jandex/test/bridge/BridgeMethodTest.java diff --git a/java8-tests/pom.xml b/java8-tests/pom.xml new file mode 100644 index 00000000..86cd20e9 --- /dev/null +++ b/java8-tests/pom.xml @@ -0,0 +1,67 @@ + + 4.0.0 + + 1.8 + 1.8 + 1.8 + + + + org.jboss + jboss-parent + 12 + + + jandex-java8-tests + 2.2.3.Final-SNAPSHOT + Java Annotation Indexer tests: Java8 + jar + + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.1 + + true + ${maven.compiler.source} + ${maven.compiler.target} + true + true + + + + + + + + org.jboss + jandex + ${project.version} + test + + + org.checkerframework + checker-qual + 2.10.0 + test + + + junit + junit + 4.13.1 + jar + test + + + diff --git a/java8-tests/src/test/java/org/jboss/jandex/test/bridge/BridgeMethodTest.java b/java8-tests/src/test/java/org/jboss/jandex/test/bridge/BridgeMethodTest.java new file mode 100644 index 00000000..deacf728 --- /dev/null +++ b/java8-tests/src/test/java/org/jboss/jandex/test/bridge/BridgeMethodTest.java @@ -0,0 +1,165 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2021 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jboss.jandex.test.bridge; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.Indexer; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type; +import org.jboss.jandex.TypeTarget; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class BridgeMethodTest { + public static class ArrayWithNullableElementsConsumer + implements Consumer<@Nullable Object[]> { + @Override + public void accept(@Nullable Object[] objects) { + } + } + + @Test + public void arrayWithNullableElements() throws IOException { + verifyMethodSignature( + ArrayWithNullableElementsConsumer.class, + "accept", + TypeTarget.Usage.METHOD_PARAMETER, "java.lang.Object", + "@Nullable java.lang.Object[]"); + } + + public static class NullableArrayConsumer + implements Consumer { + @Override + public void accept(Object @Nullable [] objects) { + } + } + + @Test + public void nullableArray() throws IOException { + verifyMethodSignature( + NullableArrayConsumer.class, + "accept", + TypeTarget.Usage.METHOD_PARAMETER, "@Nullable java.lang.Object", + "java.lang.Object @Nullable []"); + } + + public static class NullableArrayWithNullableElementsConsumer + implements Consumer<@Nullable Object @Nullable []> { + @Override + public void accept(@Nullable Object @Nullable [] objects) { + } + } + + @Test + public void nullableArrayWithNullableElementsConsumer() throws IOException { + verifyMethodSignature( + NullableArrayWithNullableElementsConsumer.class, + "accept", + TypeTarget.Usage.METHOD_PARAMETER, "@Nullable java.lang.Object", + "@Nullable java.lang.Object @Nullable []"); + } + + public static class ArrayWithNullableElementsSupplier + implements Supplier<@Nullable Object[]> { + @Override + public @Nullable Object[] get() { + return new Object[0]; + } + } + + @Test + public void arrayWithNullableElementsSupplier() throws IOException { + verifyMethodSignature( + ArrayWithNullableElementsSupplier.class, + "get", + TypeTarget.Usage.EMPTY, + "java.lang.Object", + "@Nullable java.lang.Object[]"); + } + + public static class NullableArraySupplier + implements Supplier { + @Override + public Object @Nullable [] get() { + return null; + } + } + + @Test + public void nullableArraySupplier() throws IOException { + verifyMethodSignature( + NullableArraySupplier.class, + "get", + TypeTarget.Usage.EMPTY, + "@Nullable java.lang.Object", + "java.lang.Object @Nullable []"); + } + + private boolean isBridge(MethodInfo methodInfo) { + int bridgeModifiers = 0x1000 /* SYNTHETIC */ | 0x40 /* BRIDGE */; + return (methodInfo.flags() & bridgeModifiers) == bridgeModifiers; + } + + private InputStream getClassBytes(Class klass) { + String fileName = klass.getName(); + fileName = fileName.substring(fileName.lastIndexOf('.') + 1); + return klass.getResourceAsStream(fileName + ".class"); + } + + private void verifyMethodSignature( + Class klass, + String methodName, + TypeTarget.Usage usage, + String expectedBridgeType, + String expectedNonBridgeType) throws IOException { + Indexer indexer = new Indexer(); + ClassInfo info = indexer.index(getClassBytes(klass)); + int methods = 0; + for (MethodInfo method : info.methods()) { + if (!methodName.equals(method.name())) { + continue; + } + String expectedType = isBridge(method) ? expectedBridgeType : expectedNonBridgeType; + Type type; + switch (usage) { + case METHOD_PARAMETER: + type = method.parameters().get(0); + break; + case EMPTY: + type = method.returnType(); + break; + default: + throw new IllegalArgumentException("Expected METHOD_PARAMETER or EMPTY, got " + usage); + } + Assert.assertEquals(type + " signature for " + + (isBridge(method) ? "" : "non-") + "bridge method " + method, + expectedType, type.toString()); + methods++; + } + if (methods == 0) { + Assert.fail("At least one '" + methodName + "' method is expected in " + klass); + } + } +} diff --git a/pom.xml b/pom.xml index fc9ba0d1..a1294b59 100644 --- a/pom.xml +++ b/pom.xml @@ -149,6 +149,14 @@ test + + + java8-tests + + java8-tests + + + jboss-public-repository diff --git a/src/main/java/org/jboss/jandex/Indexer.java b/src/main/java/org/jboss/jandex/Indexer.java index e46461dc..cfe8ec04 100644 --- a/src/main/java/org/jboss/jandex/Indexer.java +++ b/src/main/java/org/jboss/jandex/Indexer.java @@ -721,6 +721,10 @@ private void resolveTypeAnnotation(AnnotationTarget target, TypeAnnotationState return; } + if (targetsArrayInBridgeMethod(typeAnnotationState, types[index], method)) { + return; + } + types[index] = resolveTypePath(types[index], typeAnnotationState); method.setParameters(intern(types)); } else if (typeTarget.usage() == TypeTarget.Usage.EMPTY && target instanceof FieldInfo) { @@ -731,7 +735,11 @@ private void resolveTypeAnnotation(AnnotationTarget target, TypeAnnotationState if (((EmptyTypeTarget) typeTarget).isReceiver()) { method.setReceiverType(resolveTypePath(method.receiverType(), typeAnnotationState)); } else { - method.setReturnType(resolveTypePath(method.returnType(), typeAnnotationState)); + Type returnType = method.returnType(); + if (targetsArrayInBridgeMethod(typeAnnotationState, returnType, method)) { + return; + } + method.setReturnType(resolveTypePath(returnType, typeAnnotationState)); } } else if (typeTarget.usage() == TypeTarget.Usage.THROWS && target instanceof MethodInfo) { MethodInfo method = (MethodInfo) target; @@ -747,6 +755,30 @@ private void resolveTypeAnnotation(AnnotationTarget target, TypeAnnotationState } } + private boolean targetsArrayInBridgeMethod( + TypeAnnotationState typeAnnotationState, Type type, MethodInfo method) { + // javac copies annotations to bridge methods (which is good), however the annotations + // might become invalid. For instance, the bridge signature for @Nullable Object[] is + // Object (non-array), so usage=ARRAY signature is invalid + // We ignore those annotations for the bridge methods. + // See https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6695379 + return type.kind() != Type.Kind.ARRAY && isBridge(method) && + targetsArray(typeAnnotationState); + } + + private boolean isBridge(MethodInfo methodInfo) { + int bridgeModifiers = Modifiers.SYNTHETIC | 0x40; + return (methodInfo.flags() & bridgeModifiers) == bridgeModifiers; + } + + private boolean targetsArray(TypeAnnotationState typeAnnotationState) { + if (typeAnnotationState.pathElements.size() == 0) { + return false; + } + PathElement pathElement = typeAnnotationState.pathElements.peek(); + return pathElement != null && pathElement.kind == PathElement.Kind.ARRAY; + } + private Type resolveTypePath(Type type, TypeAnnotationState typeAnnotationState) { PathElementStack elements = typeAnnotationState.pathElements; PathElement element = elements.pop(); @@ -823,6 +855,9 @@ private void updateTypeTarget(AnnotationTarget enclosingTarget, TypeAnnotationSt } else { MethodInfo method = (MethodInfo) enclosingTarget; type = target.asEmpty().isReceiver() ? method.receiverType() : method.returnType(); + if (targetsArrayInBridgeMethod(typeAnnotationState, type, method)) { + return; + } } break; } @@ -835,6 +870,9 @@ private void updateTypeTarget(AnnotationTarget enclosingTarget, TypeAnnotationSt case METHOD_PARAMETER: { MethodInfo method = (MethodInfo) enclosingTarget; type = method.methodInternal().parameterArray()[target.asMethodParameterType().position()]; + if (targetsArrayInBridgeMethod(typeAnnotationState, type, method)) { + return; + } break; } case TYPE_PARAMETER: {