/* * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package jdk.internal.classfile.impl; import java.lang.classfile.Annotation; import java.lang.classfile.AnnotationElement; import java.lang.classfile.AnnotationValue; import java.lang.classfile.BufWriter; import java.lang.classfile.ClassReader; import java.lang.classfile.constantpool.*; import java.lang.classfile.TypeAnnotation; import static java.lang.classfile.ClassFile.*; import static java.lang.classfile.TypeAnnotation.TargetInfo.*; import java.util.List; import java.lang.classfile.Label; import java.lang.classfile.constantpool.Utf8Entry; import jdk.internal.access.SharedSecrets; public final class AnnotationReader { private AnnotationReader() { } public static List readAnnotations(ClassReader classReader, int p) { int pos = p; int numAnnotations = classReader.readU2(pos); var annos = new Object[numAnnotations]; pos += 2; for (int i = 0; i < numAnnotations; ++i) { annos[i] = readAnnotation(classReader, pos); pos = skipAnnotation(classReader, pos); } return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArray(annos); } public static AnnotationValue readElementValue(ClassReader classReader, int p) { char tag = (char) classReader.readU1(p); ++p; return switch (tag) { case AEV_BYTE -> new AnnotationImpl.OfByteImpl(classReader.readEntry(p, IntegerEntry.class)); case AEV_CHAR -> new AnnotationImpl.OfCharImpl(classReader.readEntry(p, IntegerEntry.class)); case AEV_DOUBLE -> new AnnotationImpl.OfDoubleImpl(classReader.readEntry(p, DoubleEntry.class)); case AEV_FLOAT -> new AnnotationImpl.OfFloatImpl(classReader.readEntry(p, FloatEntry.class)); case AEV_INT -> new AnnotationImpl.OfIntImpl(classReader.readEntry(p, IntegerEntry.class)); case AEV_LONG -> new AnnotationImpl.OfLongImpl(classReader.readEntry(p, LongEntry.class)); case AEV_SHORT -> new AnnotationImpl.OfShortImpl(classReader.readEntry(p, IntegerEntry.class)); case AEV_BOOLEAN -> new AnnotationImpl.OfBooleanImpl(classReader.readEntry(p, IntegerEntry.class)); case AEV_STRING -> new AnnotationImpl.OfStringImpl(classReader.readEntry(p, Utf8Entry.class)); case AEV_ENUM -> new AnnotationImpl.OfEnumImpl(classReader.readEntry(p, Utf8Entry.class), classReader.readEntry(p + 2, Utf8Entry.class)); case AEV_CLASS -> new AnnotationImpl.OfClassImpl(classReader.readEntry(p, Utf8Entry.class)); case AEV_ANNOTATION -> new AnnotationImpl.OfAnnotationImpl(readAnnotation(classReader, p)); case AEV_ARRAY -> { int numValues = classReader.readU2(p); p += 2; var values = new Object[numValues]; for (int i = 0; i < numValues; ++i) { values[i] = readElementValue(classReader, p); p = skipElementValue(classReader, p); } yield new AnnotationImpl.OfArrayImpl(SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArray(values)); } default -> throw new IllegalArgumentException( "Unexpected tag '%s' in AnnotationValue, pos = %d".formatted(tag, p - 1)); }; } public static List readTypeAnnotations(ClassReader classReader, int p, LabelContext lc) { int numTypeAnnotations = classReader.readU2(p); p += 2; var annotations = new Object[numTypeAnnotations]; for (int i = 0; i < numTypeAnnotations; ++i) { annotations[i] = readTypeAnnotation(classReader, p, lc); p = skipTypeAnnotation(classReader, p); } return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArray(annotations); } public static List> readParameterAnnotations(ClassReader classReader, int p) { int cnt = classReader.readU1(p++); var pas = new Object[cnt]; for (int i = 0; i < cnt; ++i) { pas[i] = readAnnotations(classReader, p); p = skipAnnotations(classReader, p); } return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArray(pas); } private static int skipElementValue(ClassReader classReader, int p) { char tag = (char) classReader.readU1(p); ++p; return switch (tag) { case 'B', 'C', 'D', 'F', 'I', 'J', 'S', 'Z', 's', 'c' -> p + 2; case 'e' -> p + 4; case '@' -> skipAnnotation(classReader, p); case '[' -> { int numValues = classReader.readU2(p); p += 2; for (int i = 0; i < numValues; ++i) { p = skipElementValue(classReader, p); } yield p; } default -> throw new IllegalArgumentException( "Unexpected tag '%s' in AnnotationValue, pos = %d".formatted(tag, p - 1)); }; } private static Annotation readAnnotation(ClassReader classReader, int p) { Utf8Entry annotationClass = classReader.readEntry(p, Utf8Entry.class); p += 2; List elems = readAnnotationElementValuePairs(classReader, p); return new AnnotationImpl(annotationClass, elems); } private static int skipAnnotations(ClassReader classReader, int p) { int numAnnotations = classReader.readU2(p); p += 2; for (int i = 0; i < numAnnotations; ++i) p = skipAnnotation(classReader, p); return p; } private static int skipAnnotation(ClassReader classReader, int p) { return skipElementValuePairs(classReader, p + 2); } private static List readAnnotationElementValuePairs(ClassReader classReader, int p) { int numElementValuePairs = classReader.readU2(p); p += 2; var annotationElements = new Object[numElementValuePairs]; for (int i = 0; i < numElementValuePairs; ++i) { Utf8Entry elementName = classReader.readEntry(p, Utf8Entry.class); p += 2; AnnotationValue value = readElementValue(classReader, p); annotationElements[i] = new AnnotationImpl.AnnotationElementImpl(elementName, value); p = skipElementValue(classReader, p); } return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArray(annotationElements); } private static int skipElementValuePairs(ClassReader classReader, int p) { int numElementValuePairs = classReader.readU2(p); p += 2; for (int i = 0; i < numElementValuePairs; ++i) { p = skipElementValue(classReader, p + 2); } return p; } private static Label getLabel(LabelContext lc, int bciOffset, int targetType, int p) { //helper method to avoid NPE if (lc == null) throw new IllegalArgumentException("Unexpected targetType '%d' in TypeAnnotation outside of Code attribute, pos = %d".formatted(targetType, p - 1)); return lc.getLabel(bciOffset); } private static TypeAnnotation readTypeAnnotation(ClassReader classReader, int p, LabelContext lc) { int targetType = classReader.readU1(p++); var targetInfo = switch (targetType) { case TAT_CLASS_TYPE_PARAMETER -> ofClassTypeParameter(classReader.readU1(p)); case TAT_METHOD_TYPE_PARAMETER -> ofMethodTypeParameter(classReader.readU1(p)); case TAT_CLASS_EXTENDS -> ofClassExtends(classReader.readU2(p)); case TAT_CLASS_TYPE_PARAMETER_BOUND -> ofClassTypeParameterBound(classReader.readU1(p), classReader.readU1(p + 1)); case TAT_METHOD_TYPE_PARAMETER_BOUND -> ofMethodTypeParameterBound(classReader.readU1(p), classReader.readU1(p + 1)); case TAT_FIELD -> ofField(); case TAT_METHOD_RETURN -> ofMethodReturn(); case TAT_METHOD_RECEIVER -> ofMethodReceiver(); case TAT_METHOD_FORMAL_PARAMETER -> ofMethodFormalParameter(classReader.readU1(p)); case TAT_THROWS -> ofThrows(classReader.readU2(p)); case TAT_LOCAL_VARIABLE -> ofLocalVariable(readLocalVarEntries(classReader, p, lc, targetType)); case TAT_RESOURCE_VARIABLE -> ofResourceVariable(readLocalVarEntries(classReader, p, lc, targetType)); case TAT_EXCEPTION_PARAMETER -> ofExceptionParameter(classReader.readU2(p)); case TAT_INSTANCEOF -> ofInstanceofExpr(getLabel(lc, classReader.readU2(p), targetType, p)); case TAT_NEW -> ofNewExpr(getLabel(lc, classReader.readU2(p), targetType, p)); case TAT_CONSTRUCTOR_REFERENCE -> ofConstructorReference(getLabel(lc, classReader.readU2(p), targetType, p)); case TAT_METHOD_REFERENCE -> ofMethodReference(getLabel(lc, classReader.readU2(p), targetType, p)); case TAT_CAST -> ofCastExpr(getLabel(lc, classReader.readU2(p), targetType, p), classReader.readU1(p + 2)); case TAT_CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT -> ofConstructorInvocationTypeArgument(getLabel(lc, classReader.readU2(p), targetType, p), classReader.readU1(p + 2)); case TAT_METHOD_INVOCATION_TYPE_ARGUMENT -> ofMethodInvocationTypeArgument(getLabel(lc, classReader.readU2(p), targetType, p), classReader.readU1(p + 2)); case TAT_CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT -> ofConstructorReferenceTypeArgument(getLabel(lc, classReader.readU2(p), targetType, p), classReader.readU1(p + 2)); case TAT_METHOD_REFERENCE_TYPE_ARGUMENT -> ofMethodReferenceTypeArgument(getLabel(lc, classReader.readU2(p), targetType, p), classReader.readU1(p + 2)); default -> throw new IllegalArgumentException("Unexpected targetType '%d' in TypeAnnotation, pos = %d".formatted(targetType, p - 1)); }; p += targetInfo.size(); int pathLength = classReader.readU1(p++); TypeAnnotation.TypePathComponent[] typePath = new TypeAnnotation.TypePathComponent[pathLength]; for (int i = 0; i < pathLength; ++i) { int typePathKindTag = classReader.readU1(p++); int typeArgumentIndex = classReader.readU1(p++); typePath[i] = switch (typePathKindTag) { case 0 -> TypeAnnotation.TypePathComponent.ARRAY; case 1 -> TypeAnnotation.TypePathComponent.INNER_TYPE; case 2 -> TypeAnnotation.TypePathComponent.WILDCARD; case 3 -> new UnboundAttribute.TypePathComponentImpl(TypeAnnotation.TypePathComponent.Kind.TYPE_ARGUMENT, typeArgumentIndex); default -> throw new IllegalArgumentException("Unknown type annotation path component kind: " + typePathKindTag); }; } // the annotation info for this annotation Utf8Entry type = classReader.readEntry(p, Utf8Entry.class); p += 2; return TypeAnnotation.of(targetInfo, List.of(typePath), type, readAnnotationElementValuePairs(classReader, p)); } private static List readLocalVarEntries(ClassReader classReader, int p, LabelContext lc, int targetType) { int tableLength = classReader.readU2(p); p += 2; var entries = new Object[tableLength]; for (int i = 0; i < tableLength; ++i) { int startPc = classReader.readU2(p); entries[i] = TypeAnnotation.LocalVarTargetInfo.of( getLabel(lc, startPc, targetType, p), getLabel(lc, startPc + classReader.readU2(p + 2), targetType, p - 2), classReader.readU2(p + 4)); p += 6; } return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArray(entries); } private static int skipTypeAnnotation(ClassReader classReader, int p) { int targetType = classReader.readU1(p++); p += switch (targetType) { case 0x13, 0x14, 0x15 -> 0; case 0x00, 0x01, 0x16 -> 1; case 0x10, 0x11, 0x12, 0x17, 0x42, 0x43, 0x44, 0x45, 0x46 -> 2; case 0x47, 0x48, 0x49, 0x4A, 0x4B -> 3; case 0x40, 0x41 -> 2 + classReader.readU2(p) * 6; default -> throw new IllegalArgumentException( "Unexpected targetType '%d' in TypeAnnotation, pos = %d".formatted(targetType, p - 1)); }; int pathLength = classReader.readU1(p++); p += pathLength * 2; // the annotation info for this annotation p += 2; p = skipElementValuePairs(classReader, p); return p; } public static void writeAnnotation(BufWriterImpl buf, Annotation annotation) { // handles annotations and type annotations // TODO annotation cleanup later ((Util.Writable) annotation).writeTo(buf); } public static void writeAnnotations(BufWriter buf, List list) { // handles annotations and type annotations var internalBuf = (BufWriterImpl) buf; internalBuf.writeU2(list.size()); for (var e : list) { writeAnnotation(internalBuf, e); } } public static void writeAnnotationValue(BufWriterImpl buf, AnnotationValue value) { // TODO annotation cleanup later ((Util.Writable) value).writeTo(buf); } }