diff --git a/src/main/java/org/apache/bcel/classfile/AnnotationEntry.java b/src/main/java/org/apache/bcel/classfile/AnnotationEntry.java index afe09d29bd..4bd0e8d34d 100644 --- a/src/main/java/org/apache/bcel/classfile/AnnotationEntry.java +++ b/src/main/java/org/apache/bcel/classfile/AnnotationEntry.java @@ -65,7 +65,7 @@ public static AnnotationEntry read(final DataInput input, final ConstantPool con final int numElementValuePairs = input.readUnsignedShort(); for (int i = 0; i < numElementValuePairs; i++) { annotationEntry.elementValuePairs - .add(new ElementValuePair(input.readUnsignedShort(), ElementValue.readElementValue(input, constantPool), constantPool)); + .add(new ElementValuePair(input.readUnsignedShort(), ElementValue.readElementValue(input, constantPool, isRuntimeVisible, 0), constantPool)); } return annotationEntry; } diff --git a/src/main/java/org/apache/bcel/classfile/ElementValue.java b/src/main/java/org/apache/bcel/classfile/ElementValue.java index 69e3522b99..40e83ea8e9 100644 --- a/src/main/java/org/apache/bcel/classfile/ElementValue.java +++ b/src/main/java/org/apache/bcel/classfile/ElementValue.java @@ -117,7 +117,11 @@ public static ElementValue readElementValue(final DataInput input, final Constan * @throws IOException if an I/O error occurs. * @since 6.7.0 */ - public static ElementValue readElementValue(final DataInput input, final ConstantPool cpool, int arrayNesting) + public static ElementValue readElementValue(final DataInput input, final ConstantPool cpool, final int arrayNesting) throws IOException { + return readElementValue(input, cpool, false, arrayNesting); + } + + static ElementValue readElementValue(final DataInput input, final ConstantPool cpool, final boolean isRuntimeVisible, int arrayNesting) throws IOException { final byte tag = input.readByte(); switch (tag) { @@ -139,8 +143,7 @@ public static ElementValue readElementValue(final DataInput input, final Constan return new ClassElementValue(CLASS, input.readUnsignedShort(), cpool); case ANNOTATION: - // TODO isRuntimeVisible - return new AnnotationElementValue(ANNOTATION, AnnotationEntry.read(input, cpool, false), cpool); + return new AnnotationElementValue(ANNOTATION, AnnotationEntry.read(input, cpool, isRuntimeVisible), cpool); case ARRAY: arrayNesting++; @@ -151,7 +154,7 @@ public static ElementValue readElementValue(final DataInput input, final Constan final int numArrayVals = input.readUnsignedShort(); final ElementValue[] evalues = new ElementValue[numArrayVals]; for (int j = 0; j < numArrayVals; j++) { - evalues[j] = readElementValue(input, cpool, arrayNesting); + evalues[j] = readElementValue(input, cpool, isRuntimeVisible, arrayNesting); } return new ArrayElementValue(ARRAY, evalues, cpool); diff --git a/src/main/java/org/apache/bcel/classfile/ParameterAnnotationEntry.java b/src/main/java/org/apache/bcel/classfile/ParameterAnnotationEntry.java index fce2cc756e..4cfb5bd163 100644 --- a/src/main/java/org/apache/bcel/classfile/ParameterAnnotationEntry.java +++ b/src/main/java/org/apache/bcel/classfile/ParameterAnnotationEntry.java @@ -65,14 +65,14 @@ public static ParameterAnnotationEntry[] createParameterAnnotationEntries(final * * @param input Input stream. * @param constantPool the constant pool. + * @param isRuntimeVisible whether the contained annotations are runtime visible. * @throws IOException if an I/O error occurs. */ - ParameterAnnotationEntry(final DataInput input, final ConstantPool constantPool) throws IOException { + ParameterAnnotationEntry(final DataInput input, final ConstantPool constantPool, final boolean isRuntimeVisible) throws IOException { final int annotationTableLength = input.readUnsignedShort(); annotationTable = new AnnotationEntry[annotationTableLength]; for (int i = 0; i < annotationTableLength; i++) { - // TODO isRuntimeVisible - annotationTable[i] = AnnotationEntry.read(input, constantPool, false); + annotationTable[i] = AnnotationEntry.read(input, constantPool, isRuntimeVisible); } } diff --git a/src/main/java/org/apache/bcel/classfile/ParameterAnnotations.java b/src/main/java/org/apache/bcel/classfile/ParameterAnnotations.java index 48cbd76450..bbbd9f7280 100644 --- a/src/main/java/org/apache/bcel/classfile/ParameterAnnotations.java +++ b/src/main/java/org/apache/bcel/classfile/ParameterAnnotations.java @@ -44,14 +44,15 @@ public abstract class ParameterAnnotations extends Attribute implements Iterable * @param length Content length in bytes. * @param input Input stream. * @param constantPool Array of constants. + * @param isRuntimeVisible whether these parameter annotations are runtime visible. */ - ParameterAnnotations(final byte parameterAnnotationType, final int nameIndex, final int length, final DataInput input, final ConstantPool constantPool) - throws IOException { + ParameterAnnotations(final byte parameterAnnotationType, final int nameIndex, final int length, final DataInput input, final ConstantPool constantPool, + final boolean isRuntimeVisible) throws IOException { this(parameterAnnotationType, nameIndex, length, (ParameterAnnotationEntry[]) null, constantPool); final int numParameters = input.readUnsignedByte(); parameterAnnotationTable = new ParameterAnnotationEntry[numParameters]; for (int i = 0; i < numParameters; i++) { - parameterAnnotationTable[i] = new ParameterAnnotationEntry(input, constantPool); + parameterAnnotationTable[i] = new ParameterAnnotationEntry(input, constantPool, isRuntimeVisible); } } diff --git a/src/main/java/org/apache/bcel/classfile/RuntimeInvisibleParameterAnnotations.java b/src/main/java/org/apache/bcel/classfile/RuntimeInvisibleParameterAnnotations.java index 2a8518a297..27a18f5900 100644 --- a/src/main/java/org/apache/bcel/classfile/RuntimeInvisibleParameterAnnotations.java +++ b/src/main/java/org/apache/bcel/classfile/RuntimeInvisibleParameterAnnotations.java @@ -41,6 +41,6 @@ public class RuntimeInvisibleParameterAnnotations extends ParameterAnnotations { */ public RuntimeInvisibleParameterAnnotations(final int nameIndex, final int length, final DataInput input, final ConstantPool constantPool) throws IOException { - super(Const.ATTR_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS, nameIndex, length, input, constantPool); + super(Const.ATTR_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS, nameIndex, length, input, constantPool, false); } } diff --git a/src/main/java/org/apache/bcel/classfile/RuntimeVisibleParameterAnnotations.java b/src/main/java/org/apache/bcel/classfile/RuntimeVisibleParameterAnnotations.java index 167ee53c71..51d8491d24 100644 --- a/src/main/java/org/apache/bcel/classfile/RuntimeVisibleParameterAnnotations.java +++ b/src/main/java/org/apache/bcel/classfile/RuntimeVisibleParameterAnnotations.java @@ -41,6 +41,6 @@ public class RuntimeVisibleParameterAnnotations extends ParameterAnnotations { */ public RuntimeVisibleParameterAnnotations(final int nameIndex, final int length, final DataInput input, final ConstantPool constantPool) throws IOException { - super(Const.ATTR_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS, nameIndex, length, input, constantPool); + super(Const.ATTR_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS, nameIndex, length, input, constantPool, true); } } diff --git a/src/test/java/org/apache/bcel/generic/ParameterAnnotationVisibilityTest.java b/src/test/java/org/apache/bcel/generic/ParameterAnnotationVisibilityTest.java new file mode 100644 index 0000000000..f4ddce12e0 --- /dev/null +++ b/src/test/java/org/apache/bcel/generic/ParameterAnnotationVisibilityTest.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://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.apache.bcel.generic; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.apache.bcel.AbstractTest; +import org.apache.bcel.Const; +import org.apache.bcel.classfile.AnnotationElementValue; +import org.apache.bcel.classfile.AnnotationEntry; +import org.apache.bcel.classfile.ArrayElementValue; +import org.apache.bcel.classfile.ElementValue; +import org.apache.bcel.classfile.ElementValuePair; +import org.apache.bcel.classfile.JavaClass; +import org.apache.bcel.classfile.Method; +import org.apache.bcel.classfile.ParameterAnnotationEntry; +import org.junit.jupiter.api.Test; + +/** + * Checks that the runtime-visible flag is propagated when parameter annotations and nested annotation values are parsed + * from a class file. + */ +class ParameterAnnotationVisibilityTest extends AbstractTest { + + private JavaClass loadTestClass() throws ClassNotFoundException { + return getTestJavaClass(PACKAGE_BASE_NAME + ".data.AnnotatedWithCombinedAnnotation"); + } + + private Method getConstructor(final JavaClass jc) { + for (final Method method : jc.getMethods()) { + if (Const.CONSTRUCTOR_NAME.equals(method.getName())) { + return method; + } + } + return null; + } + + @Test + void testNestedAnnotationValueIsRuntimeVisible() throws ClassNotFoundException { + final JavaClass jc = loadTestClass(); + boolean found = false; + for (final AnnotationEntry outer : jc.getAnnotationEntries()) { + for (final ElementValuePair pair : outer.getElementValuePairs()) { + final ElementValue value = pair.getValue(); + if (value instanceof ArrayElementValue) { + for (final ElementValue inner : ((ArrayElementValue) value).getElementValuesArray()) { + if (inner instanceof AnnotationElementValue) { + found = true; + assertTrue(((AnnotationElementValue) inner).getAnnotationEntry().isRuntimeVisible(), + "nested annotation value parsed from a runtime-visible annotation reported as invisible"); + } + } + } + } + } + assertTrue(found, "no nested annotation values found in test data"); + } + + @Test + void testParsedParameterAnnotationIsRuntimeVisible() throws ClassNotFoundException { + final JavaClass jc = loadTestClass(); + final Method init = getConstructor(jc); + assertNotNull(init); + boolean found = false; + for (final ParameterAnnotationEntry pae : init.getParameterAnnotationEntries()) { + for (final AnnotationEntry ae : pae.getAnnotationEntries()) { + found = true; + assertTrue(ae.isRuntimeVisible(), "runtime-visible parameter annotation parsed as invisible"); + } + } + assertTrue(found, "no parameter annotations found in test data"); + } + + @Test + void testParameterAnnotationVisibilitySurvivesMethodGen() throws ClassNotFoundException { + final JavaClass jc = loadTestClass(); + final Method init = getConstructor(jc); + final ConstantPoolGen cp = new ConstantPoolGen(jc.getConstantPool()); + final MethodGen mg = new MethodGen(init, jc.getClassName(), cp); + mg.getAnnotationsOnParameter(1); // unpack the existing parameter annotations before rebuilding + final Method out = mg.getMethod(); + assertNotNull(out.getAttribute(Const.ATTR_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS), + "runtime-visible parameter annotation dropped on MethodGen round-trip"); + assertNull(out.getAttribute(Const.ATTR_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS), + "runtime-visible parameter annotation downgraded to invisible on MethodGen round-trip"); + } +}