All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jboss.jandex.Indexer Maven / Gradle / Ivy

There is a newer version: 1.3.3
Show newest version
/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2013 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;

import static org.jboss.jandex.ClassInfo.EnclosingMethodInfo;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Modifier;
import java.nio.charset.Charset;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;

/**
 * Analyzes and indexes the annotation and key structural information of a set
 * of classes. The indexer will purposefully skip any class that is not Java 5
 * or later. It will also do a basic/quick structural scan on any class it
 * determines does not have annotations.
 *
 * 

* The Indexer operates on input streams that point to class file data. Input * streams do not need to be buffered, as the indexer already does this. There * is also no limit to the number of class file streams the indexer can process, * other than available memory. * *

* The Indexer attempts to minimize the final memory state of the index, but to * do this it must maintain additional in-process state (intern tables etc) * until the index is complete. * *

* Numerous optimizations are taken during indexing to attempt to minimize the * CPU and I/O cost, however, the Java class file format was not designed for * partial searching, which ultimately limits the efficiency of processing them. * *

* Thread-Safety This class is not thread-safe can not be * shared between threads. The index it produces however is thread-safe. * * @author Jason T. Greene * */ public final class Indexer { private final static int CONSTANT_CLASS = 7; private final static int CONSTANT_FIELDREF = 9; private final static int CONSTANT_METHODREF = 10; private final static int CONSTANT_INTERFACEMETHODREF = 11; private final static int CONSTANT_STRING = 8; private final static int CONSTANT_INTEGER = 3; private final static int CONSTANT_FLOAT = 4; private final static int CONSTANT_LONG = 5; private final static int CONSTANT_DOUBLE = 6; private final static int CONSTANT_NAMEANDTYPE = 12; private final static int CONSTANT_UTF8 = 1; private final static int CONSTANT_INVOKEDYNAMIC = 18; private final static int CONSTANT_METHODHANDLE = 15; private final static int CONSTANT_METHODTYPE = 16; // "RuntimeVisibleAnnotations" private final static byte[] RUNTIME_ANNOTATIONS = new byte[] { 0x52, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x56, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73 }; // "RuntimeVisibleParameterAnnotations" private final static byte[] RUNTIME_PARAM_ANNOTATIONS = new byte[] { 0x52, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x56, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73 }; // "RuntimeTypeVisibleAnnotations" private final static byte[] RUNTIME_TYPE_ANNOTATIONS = new byte[] { 0x52, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x56, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73 }; // "Signature" private final static byte[] SIGNATURE = new byte[] { 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65 }; // "Exceptions" private final static byte[] EXCEPTIONS = new byte[] { 0x45, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73 }; // "InnerClasses" private final static byte[] INNER_CLASSES = new byte[] { 0x49, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73 }; // "EnclosingMethod" private final static byte[] ENCLOSING_METHOD = new byte[] { 0x45, 0x6e, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64 }; private final static int RUNTIME_ANNOTATIONS_LEN = RUNTIME_ANNOTATIONS.length; private final static int RUNTIME_PARAM_ANNOTATIONS_LEN = RUNTIME_PARAM_ANNOTATIONS.length; private final static int RUNTIME_TYPE_ANNOTATIONS_LEN = RUNTIME_TYPE_ANNOTATIONS.length; private final static int SIGNATURE_LEN = SIGNATURE.length; private final static int EXCEPTIONS_LEN = EXCEPTIONS.length; private final static int INNER_CLASSES_LEN = INNER_CLASSES.length; private final static int ENCLOSING_METHOD_LEN = ENCLOSING_METHOD.length; private final static int HAS_RUNTIME_ANNOTATION = 1; private final static int HAS_RUNTIME_PARAM_ANNOTATION = 2; private final static int HAS_RUNTIME_TYPE_ANNOTATION = 3; private final static int HAS_SIGNATURE = 4; private final static int HAS_EXCEPTIONS = 5; private final static int HAS_INNER_CLASSES = 6; private final static int HAS_ENCLOSING_METHOD = 7; private final static byte[] INIT_METHOD_NAME = Utils.toUTF8(""); private IdentityHashMap signaturePresent; private static class InnerClassInfo { private InnerClassInfo(DotName innerClass, DotName enclosingClass, String simpleName, int flags) { this.innnerClass = innerClass; this.enclosingClass = enclosingClass; this.simpleName = simpleName; this.flags = flags; } private final DotName innnerClass; private DotName enclosingClass; private String simpleName; private int flags; } private static boolean match(byte[] target, int offset, byte[] expected) { if (target.length - offset < expected.length) return false; for (int i = 0; i < expected.length; i++) if (target[offset + i] != expected[i]) return false; return true; } private static byte[] sizeToFit(byte[] buf, int needed, int offset, int remainingEntries) { if (offset + needed > buf.length) { buf = Arrays.copyOf(buf, buf.length + Math.max(needed, (remainingEntries + 1) * 20)); } return buf; } private static void skipFully(InputStream s, long n) throws IOException { long skipped; long total = 0; while (total < n) { skipped = s.skip(n - total); if (skipped < 0) throw new EOFException(); total += skipped; // Skip is not guaranteed to distinguish between EOF and nothing-read if (skipped == 0) { if (s.read() < 0) { throw new EOFException(); } total++; } } } // Class lifespan fields private byte[] constantPool; private int[] constantPoolOffsets; private byte[] constantPoolAnnoAttrributes; private ClassInfo currentClass; private HashMap> classAnnotations; private ArrayList elementAnnotations; private List signatures; private Map innerClasses; private IdentityHashMap> typeAnnotations; private List methods; private List fields; // Index lifespan fields private Map> masterAnnotations; private Map> subclasses; private Map> implementors; private Map classes; private NameTable names; private GenericSignatureParser signatureParser; private void initIndexMaps() { if (masterAnnotations == null) masterAnnotations = new HashMap>(); if (subclasses == null) subclasses = new HashMap>(); if (implementors == null) implementors = new HashMap>(); if (classes == null) classes = new HashMap(); if (names == null) names = new NameTable(); if (signatureParser == null) { signatureParser = new GenericSignatureParser(names); } } private void initClassFields() { elementAnnotations = new ArrayList(); signaturePresent = new IdentityHashMap(); signatures = new ArrayList(); typeAnnotations = new IdentityHashMap>(); } private void processMethodInfo(DataInputStream data) throws IOException { int numMethods = data.readUnsignedShort(); List methods = numMethods > 0 ? new ArrayList(numMethods) : Collections.emptyList(); for (int i = 0; i < numMethods; i++) { short flags = (short) data.readUnsignedShort(); byte[] name = intern(decodeUtf8EntryAsBytes(data.readUnsignedShort())); String descriptor = decodeUtf8Entry(data.readUnsignedShort()); IntegerHolder pos = new IntegerHolder(); Type[] parameters = intern(parseMethodArgs(descriptor, pos)); Type returnType = parseType(descriptor, pos); MethodInfo method = new MethodInfo(currentClass, name, parameters, returnType, flags); if (parameters.length == 0 && Arrays.equals(INIT_METHOD_NAME, name)) { currentClass.setHasNoArgsConstructor(true); } processAttributes(data, method); method.setAnnotations(elementAnnotations); elementAnnotations.clear(); methods.add(method); } this.methods = methods; } private void processFieldInfo(DataInputStream data) throws IOException { int numFields = data.readUnsignedShort(); List fields = numFields > 0 ? new ArrayList(numFields) : Collections.emptyList(); for (int i = 0; i < numFields; i++) { short flags = (short) data.readUnsignedShort(); byte[] name = intern(decodeUtf8EntryAsBytes(data.readUnsignedShort())); Type type = parseType(decodeUtf8Entry(data.readUnsignedShort())); FieldInfo field = new FieldInfo(currentClass, name, type, flags); processAttributes(data, field); field.setAnnotations(elementAnnotations); elementAnnotations.clear(); fields.add(field); } this.fields = fields; } private void processAttributes(DataInputStream data, AnnotationTarget target) throws IOException { int numAttrs = data.readUnsignedShort(); for (int a = 0; a < numAttrs; a++) { int index = data.readUnsignedShort(); long attributeLen = data.readInt() & 0xFFFFFFFFL; byte annotationAttribute = constantPoolAnnoAttrributes[index - 1]; if (annotationAttribute == HAS_RUNTIME_ANNOTATION) { processAnnotations(data, target); } else if (annotationAttribute == HAS_RUNTIME_PARAM_ANNOTATION) { if (!(target instanceof MethodInfo)) throw new IllegalStateException("RuntimeVisibleParameterAnnotations appeared on a non-method"); int numParameters = data.readUnsignedByte(); for (short p = 0; p < numParameters; p++) { processAnnotations(data, new MethodParameterInfo((MethodInfo) target, p)); } } else if (annotationAttribute == HAS_RUNTIME_TYPE_ANNOTATION) { processTypeAnnotations(data, target); } else if (annotationAttribute == HAS_SIGNATURE) { processSignature(data, target); } else if (annotationAttribute == HAS_EXCEPTIONS && target instanceof MethodInfo) { processExceptions(data, (MethodInfo) target); } else if (annotationAttribute == HAS_INNER_CLASSES && target instanceof ClassInfo) { processInnerClasses(data, (ClassInfo) target); } else if (annotationAttribute == HAS_ENCLOSING_METHOD && target instanceof ClassInfo) { processEnclosingMethod(data, (ClassInfo) target); } else { skipFully(data, attributeLen); } } } private void processAnnotations(DataInputStream data, AnnotationTarget target) throws IOException { int numAnnotations = data.readUnsignedShort(); while (numAnnotations-- > 0) processAnnotation(data, target); } private void processInnerClasses(DataInputStream data, ClassInfo target) throws IOException { int numClasses = data.readUnsignedShort(); innerClasses = numClasses > 0 ? new HashMap(numClasses) : Collections.emptyMap(); for (int i = 0; i < numClasses; i++) { DotName innerClass = decodeClassEntry(data.readUnsignedShort()); int outerIndex = data.readUnsignedShort(); DotName outerClass = outerIndex == 0 ? null : decodeClassEntry(outerIndex); int simpleIndex = data.readUnsignedShort(); String simpleName = simpleIndex == 0 ? null : decodeUtf8Entry(simpleIndex); int flags = data.readUnsignedShort(); if (innerClass.equals(target.name())) { target.setInnerClassInfo(outerClass, simpleName); } innerClasses.put(innerClass, new InnerClassInfo(innerClass, outerClass, simpleName, flags)); } } private void processEnclosingMethod(DataInputStream data, ClassInfo target) throws IOException { int classIndex = data.readUnsignedShort(); int index = data.readUnsignedShort(); if (index == 0) { return; // Enclosed in a static or an instance variable } DotName enclosingClass = decodeClassEntry(classIndex); NameAndType nameAndType = decodeNameAndTypeEntry(index); IntegerHolder pos = new IntegerHolder(); Type[] parameters = intern(parseMethodArgs(nameAndType.descriptor, pos)); Type returnType = parseType(nameAndType.descriptor, pos); EnclosingMethodInfo method = new EnclosingMethodInfo(nameAndType.name, returnType, parameters, enclosingClass); target.setEnclosingMethod(method); } private void processTypeAnnotations(DataInputStream data, AnnotationTarget target) throws IOException { int numAnnotations = data.readUnsignedShort(); List annotations = new ArrayList(numAnnotations); for (int i = 0; i < numAnnotations; i++) { TypeAnnotationState annotation = processTypeAnnotation(data, target); if (annotation != null) { annotations.add(annotation); } } typeAnnotations.put(target, annotations); } private TypeAnnotationState processTypeAnnotation(DataInputStream data, AnnotationTarget target) throws IOException { int targetType = data.readUnsignedByte(); TypeTarget typeTarget = null; switch (targetType) { case 0x00: // CLASS_TYPE_PARAMETER case 0x01: // METHOD_TYPE_PARAMETER { typeTarget = new TypeParameterTypeTarget(target, data.readUnsignedByte()); break; } case 0x10: // CLASS_EXTENDS { if (!(target instanceof ClassInfo)) { throw new IllegalStateException("Class extends type annotation appeared on a non class target"); } typeTarget = new ClassExtendsTypeTarget((ClassInfo)target, data.readUnsignedShort()); break; } case 0x11: // CLASS_TYPE_PARAMETER_BOUND case 0x12: // METHOD_TYPE_PARAMETER_BOUND { typeTarget = new TypeParameterBoundTypeTarget(target, data.readUnsignedByte(), data.readUnsignedByte()); break; } case 0x13: // FIELD case 0x14: // METHOD_RETURN case 0x15: // METHOD_RECEIVER typeTarget = new EmptyTypeTarget(target, targetType == 0x15); break; case 0x16: // METHOD_FORMAL_PARAMETER { if (!(target instanceof MethodInfo)) { throw new IllegalStateException("Method parameter type annotation appeared on a non-method target"); } typeTarget = new MethodParameterTypeTarget((MethodInfo)target, data.readUnsignedByte()); break; } case 0x17: // THROWS { if (!(target instanceof MethodInfo)) { throw new IllegalStateException("Throws type annotation appeared on a non-method target"); } typeTarget = new ThrowsTypeTarget((MethodInfo) target, data.readUnsignedShort()); break; } // Skip code attribute values, which shouldn't be present case 0x40: // LOCAL_VARIABLE case 0x41: // RESOURCE_VARIABLE skipFully(data, data.readUnsignedShort() * 6); break; case 0x42: // EXCEPTION_PARAMETER skipFully(data, 2); break; case 0x43: // INSTANCEOF case 0x44: // NEW case 0x45: // CONSTRUCTOR_REFERENCE case 0x46: // METHOD_REFERENCE skipFully(data, 2); break; case 0x47: // CAST case 0x48: // CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT case 0x49: // METHOD_INVOCATION_TYPE_ARGUMENT case 0x4A: // CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT case 0x4B: // METHOD_REFERENCE_TYPE_ARGUMENT skipFully(data, 3); break; default: throw new IllegalStateException("Invalid type annotation target type"); } if (typeTarget == null) { return null; } BooleanHolder genericsRequired = new BooleanHolder(); ArrayList pathElements = processTargetPath(data, genericsRequired); AnnotationInstance annotation = processAnnotation(data, typeTarget); return new TypeAnnotationState(typeTarget, annotation, pathElements, genericsRequired.bool); } private void resolveTypeAnnotations() { for (Map.Entry> entry : typeAnnotations.entrySet()) { AnnotationTarget key = entry.getKey(); List annotations = entry.getValue(); for (TypeAnnotationState annotation : annotations) { resolveTypeAnnotation(key, annotation); } } } private void updateTypeTargets() { for (Map.Entry> entry : typeAnnotations.entrySet()) { AnnotationTarget key = entry.getKey(); List annotations = entry.getValue(); for (TypeAnnotationState annotation : annotations) { updateTypeTarget(key, annotation); } } } private static Type[] getTypeParameters(AnnotationTarget target) { if (target instanceof ClassInfo) { return ((ClassInfo)target).typeParameterArray(); } else if (target instanceof MethodInfo) { return ((MethodInfo)target).typeParameterArray(); } throw new IllegalStateException("Type annotation referred to type parameters on an invalid target: " + target); } private static Type[] copyTypeParameters(AnnotationTarget target) { if (target instanceof ClassInfo) { return ((ClassInfo)target).typeParameterArray().clone(); } else if (target instanceof MethodInfo) { return ((MethodInfo)target).typeParameterArray().clone(); } throw new IllegalStateException("Type annotation referred to type parameters on an invalid target: " + target); } private void setTypeParameters(AnnotationTarget target, Type[] typeParameters) { if (target instanceof ClassInfo) { ((ClassInfo)target).setTypeParameters(typeParameters); return; } else if (target instanceof MethodInfo) { ((MethodInfo)target).setTypeParameters(typeParameters); return; } throw new IllegalStateException("Type annotation referred to type parameters on an invalid target: " + target); } private void resolveTypeAnnotation(AnnotationTarget target, TypeAnnotationState typeAnnotationState) { // Signature is erroneously omitted from bridge methods with generic type annotations if (typeAnnotationState.genericsRequired && !signaturePresent.containsKey(target)) { typeAnnotationState.target.setTarget(VoidType.VOID); return; } TypeTarget typeTarget = typeAnnotationState.target; if (typeTarget.usage() == TypeTarget.Usage.TYPE_PARAMETER_BOUND) { TypeParameterBoundTypeTarget bound = (TypeParameterBoundTypeTarget) typeTarget; Type[] types = copyTypeParameters(target); int index = bound.position(); if (index >= types.length) { return; } TypeVariable type = types[index].asTypeVariable(); int boundIndex = bound.boundPosition(); if (boundIndex >= type.boundArray().length) { return; } type = type.copyType(boundIndex, resolveTypePath(type.boundArray()[boundIndex], typeAnnotationState)); types[index] = intern(type); setTypeParameters(target, intern(types)); } else if (typeTarget.usage() == TypeTarget.Usage.TYPE_PARAMETER) { TypeParameterTypeTarget parameter = (TypeParameterTypeTarget) typeTarget; Type[] types = copyTypeParameters(target); int index = parameter.position(); if (index >= types.length) { return; } types[index] = resolveTypePath(types[index], typeAnnotationState); setTypeParameters(target, intern(types)); } else if (typeTarget.usage() == TypeTarget.Usage.CLASS_EXTENDS) { ClassInfo clazz = (ClassInfo) target; ClassExtendsTypeTarget extendsTarget = (ClassExtendsTypeTarget) typeTarget; int index = extendsTarget.position(); if (index == 65535) { clazz.setSuperClassType(resolveTypePath(clazz.superClassType(), typeAnnotationState)); } else if (index < clazz.interfaceTypes().size()) { Type[] types = clazz.copyInterfaceTypes(); types[index] = resolveTypePath(types[index], typeAnnotationState); clazz.setInterfaceTypes(intern(types)); } } else if (typeTarget.usage() == TypeTarget.Usage.METHOD_PARAMETER) { MethodInfo method = (MethodInfo) target; MethodParameterTypeTarget parameter = (MethodParameterTypeTarget) typeTarget; int index = parameter.position(); Type[] types = method.copyParameters(); if (index >= types.length) { return; } types[index] = resolveTypePath(types[index], typeAnnotationState); method.setParameters(intern(types)); } else if (typeTarget.usage() == TypeTarget.Usage.EMPTY && target instanceof FieldInfo) { FieldInfo field = (FieldInfo) target; field.setType(resolveTypePath(field.type(), typeAnnotationState)); } else if (typeTarget.usage() == TypeTarget.Usage.EMPTY && target instanceof MethodInfo) { MethodInfo method = (MethodInfo) target; if (((EmptyTypeTarget) typeTarget).isReceiver()) { method.setReceiverType(resolveTypePath(method.receiverType(), typeAnnotationState)); } else { method.setReturnType(resolveTypePath(method.returnType(), typeAnnotationState)); } } else if (typeTarget.usage() == TypeTarget.Usage.THROWS && target instanceof MethodInfo) { MethodInfo method = (MethodInfo) target; int position = ((ThrowsTypeTarget)typeTarget).position(); Type[] exceptions = method.copyExceptions(); if (position >= exceptions.length) { return; } exceptions[position] = resolveTypePath(exceptions[position], typeAnnotationState); method.setExceptions(intern(exceptions)); } } private Type resolveTypePath(Type type, TypeAnnotationState typeAnnotationState) { PathElementStack elements = typeAnnotationState.pathElements; PathElement element = elements.pop(); if (element == null) { type = intern(type.addAnnotation(new AnnotationInstance(typeAnnotationState.annotation, null))); typeAnnotationState.target.setTarget(type); // FIXME // Clone the instance with a null target so that it can be interned return type; } switch (element.kind) { case ARRAY: { ArrayType arrayType = type.asArrayType(); int dimensions = arrayType.dimensions(); while (--dimensions > 0 && elements.size() > 0 && elements.peek().kind == PathElement.Kind.ARRAY) { elements.pop(); } Type nested = dimensions > 0 ? new ArrayType(arrayType.component(), dimensions) : arrayType.component(); nested = resolveTypePath(nested, typeAnnotationState); return intern(arrayType.copyType(nested, arrayType.dimensions() - dimensions)); } case PARAMETERIZED: { ParameterizedType parameterizedType = type.asParameterizedType(); Type[] arguments = parameterizedType.argumentsArray().clone(); int pos = element.pos; if (pos >= arguments.length) { throw new IllegalStateException("Type annotation referred to a type argument that does not exist"); } arguments[pos] = resolveTypePath(arguments[pos], typeAnnotationState); return intern(parameterizedType.copyType(arguments)); } case WILDCARD_BOUND: { WildcardType wildcardType = type.asWildcardType(); Type bound = resolveTypePath(wildcardType.bound(), typeAnnotationState); return intern(wildcardType.copyType(bound)); } case NESTED: { int depth = popNestedDepth(elements); return rebuildNestedType(type, depth, typeAnnotationState); } } throw new IllegalStateException("Unknown path element"); } private int popNestedDepth(PathElementStack elements) { int depth = 1; while (elements.size() > 0 && elements.peek().kind == PathElement.Kind.NESTED) { elements.pop(); depth++; } return depth; } private void updateTypeTarget(AnnotationTarget enclosingTarget, TypeAnnotationState typeAnnotationState) { // Signature is erroneously omitted from bridge methods with generic type annotations if (typeAnnotationState.genericsRequired && !signaturePresent.containsKey(enclosingTarget)) { return; } typeAnnotationState.pathElements.reset(); TypeTarget target = typeAnnotationState.target; Type type; switch (target.usage()) { case EMPTY: { if (enclosingTarget instanceof FieldInfo) { type = ((FieldInfo)enclosingTarget).type(); } else { MethodInfo method = (MethodInfo) enclosingTarget; type = target.asEmpty().isReceiver() ? method.receiverType() : method.returnType(); } break; } case CLASS_EXTENDS: { ClassInfo clazz = (ClassInfo) enclosingTarget; int position = target.asClassExtends().position(); type = position == 65535 ? clazz.superClassType() : clazz.interfaceTypeArray()[position]; break; } case METHOD_PARAMETER: { MethodInfo method = (MethodInfo) enclosingTarget; type = method.methodInternal().parameterArray()[target.asMethodParameterType().position()]; break; } case TYPE_PARAMETER: { type = getTypeParameters(enclosingTarget)[target.asTypeParameter().position()]; break; } case TYPE_PARAMETER_BOUND: { TypeParameterBoundTypeTarget boundTarget = target.asTypeParameterBound(); type = getTypeParameters(enclosingTarget)[boundTarget.position()] .asTypeVariable().boundArray()[boundTarget.boundPosition()]; break; } case THROWS: { type = ((MethodInfo)enclosingTarget).methodInternal().exceptionArray()[target.asThrows().position()]; break; } default: throw new IllegalStateException("Unknown type target: " + target.usage()); } type = searchTypePath(type, typeAnnotationState); target.setTarget(type); } private Type searchTypePath(Type type, TypeAnnotationState typeAnnotationState) { PathElementStack elements = typeAnnotationState.pathElements; PathElement element = elements.pop(); if (element == null) { return type; } switch (element.kind) { case ARRAY: { ArrayType arrayType = type.asArrayType(); int dimensions = arrayType.dimensions(); while (--dimensions > 0 && elements.size() > 0 && elements.peek().kind == PathElement.Kind.ARRAY) { elements.pop(); } assert dimensions == 0; return searchTypePath(arrayType.component(), typeAnnotationState); } case PARAMETERIZED: { ParameterizedType parameterizedType = type.asParameterizedType(); return searchTypePath(parameterizedType.argumentsArray()[element.pos], typeAnnotationState); } case WILDCARD_BOUND: { return searchTypePath(type.asWildcardType().bound(), typeAnnotationState); } case NESTED: { int depth = popNestedDepth(elements); return searchNestedType(type, depth, typeAnnotationState); } } throw new IllegalStateException("Unknown path element"); } private Type rebuildNestedType(Type type, int depth, TypeAnnotationState typeAnnotationState) { DotName name = type.name(); Map ownerMap = buildOwnerMap(type); ArrayDeque classes = buildClassesQueue(name); Type last = null; for (InnerClassInfo current : classes) { DotName currentName = current.innnerClass; Type oType = ownerMap.get(currentName); // Static classes do not count for NESTED path elements if (depth > 0 && !Modifier.isStatic(current.flags)) { --depth; } if (last != null) { last = intern(oType != null ? convertParameterized(oType).copyType(last) : new ParameterizedType(currentName, null, last)); } else if (oType != null) { last = oType; } if (depth == 0) { if (last == null) { last = intern(new ClassType(currentName)); } last = resolveTypePath(last, typeAnnotationState); // Assignment to -1 messes up IDEA data-flow, use -- instead depth--; } } if (last == null) { throw new IllegalStateException("Required class information is missing"); } return last; } private ParameterizedType convertParameterized(Type oType) { return oType instanceof ClassType ? oType.asClassType().toParameterizedType() : oType.asParameterizedType(); } private Type searchNestedType(Type type, int depth, TypeAnnotationState typeAnnotationState) { DotName name = type.name(); Map ownerMap = buildOwnerMap(type); ArrayDeque classes = buildClassesQueue(name); for (InnerClassInfo current : classes) { DotName currentName = current.innnerClass; // Static classes do not count for NESTED path elements if (depth > 0 && !Modifier.isStatic(current.flags)) { --depth; } if (depth == 0) { Type owner = ownerMap.get(currentName); return searchTypePath(owner == null ? type : owner, typeAnnotationState); } } throw new IllegalStateException("Required class information is missing"); } private ArrayDeque buildClassesQueue(DotName name) { ArrayDeque classes = new ArrayDeque(); InnerClassInfo info = innerClasses.get(name); while (info != null) { classes.addFirst(info); name = info.enclosingClass; info = name != null ? innerClasses.get(name) : null; } return classes; } private Map buildOwnerMap(Type type) { Map pTypeTree = new HashMap(); Type nextType = type; do { pTypeTree.put(nextType.name(), nextType); nextType = nextType instanceof ParameterizedType ? nextType.asParameterizedType().owner() : null; } while (nextType != null); return pTypeTree; } private static class PathElement { private static enum Kind {ARRAY, NESTED, WILDCARD_BOUND, PARAMETERIZED} private static Kind[] KINDS = Kind.values(); private Kind kind; private int pos; private PathElement(Kind kind, int pos) { this.kind = kind; this.pos = pos; } } private static class PathElementStack { private int elementPos; private final ArrayList pathElements; PathElementStack(ArrayList pathElements) { this.pathElements = pathElements; } PathElement pop() { if (elementPos >= pathElements.size()) { return null; } return pathElements.get(elementPos++); } PathElement peek() { return pathElements.get(elementPos); } int size() { return pathElements.size() - elementPos; } void reset() { elementPos = 0; } } private static class TypeAnnotationState { private final TypeTarget target; private final AnnotationInstance annotation; private final boolean genericsRequired; private final PathElementStack pathElements; public TypeAnnotationState(TypeTarget target, AnnotationInstance annotation, ArrayList pathElements, boolean genericsRequired) { this.target = target; this.annotation = annotation; this.pathElements = new PathElementStack(pathElements); this.genericsRequired = genericsRequired; } } private static class BooleanHolder { boolean bool; } private ArrayList processTargetPath(DataInputStream data, BooleanHolder genericsRequired) throws IOException { int numElements = data.readUnsignedByte(); ArrayList elements = new ArrayList(numElements); for (int i = 0; i < numElements; i++) { int kindIndex = data.readUnsignedByte(); int pos = data.readUnsignedByte(); PathElement.Kind kind = PathElement.KINDS[kindIndex]; if (kind == PathElement.Kind.WILDCARD_BOUND || kind == PathElement.Kind.PARAMETERIZED) { genericsRequired.bool = true; } elements.add(new PathElement(kind, pos)); } return elements; } private void processExceptions(DataInputStream data, MethodInfo target) throws IOException { int numExceptions = data.readUnsignedShort(); Type[] exceptions = numExceptions <= 0 ? Type.EMPTY_ARRAY : new Type[numExceptions]; for (int i = 0; i < numExceptions; i++) { exceptions[i] = intern(new ClassType(decodeClassEntry(data.readUnsignedShort()))); } // Do not overwrite a signature exception if (numExceptions > 0 && target.exceptions().size() == 0) { target.setExceptions(exceptions); } } private void processSignature(DataInputStream data, AnnotationTarget target) throws IOException { String signature = decodeUtf8Entry(data.readUnsignedShort()); signatures.add(signature); signatures.add(target); signaturePresent.put(target, null); } private void parseClassSignature(String signature, ClassInfo clazz) { GenericSignatureParser.ClassSignature classSignature = signatureParser.parseClassSignature(signature); clazz.setInterfaceTypes(classSignature.interfaces()); clazz.setSuperClassType(classSignature.superClass()); clazz.setTypeParameters(classSignature.parameters()); } private void applySignatures() { for (int i = 0; i < signatures.size(); i += 2) { String elementSignature = (String) signatures.get(i); Object element = signatures.get(i + 1); if (element instanceof FieldInfo) { parseFieldSignature(elementSignature, (FieldInfo)element); } else if (element instanceof MethodInfo) { parseMethodSignature(elementSignature, (MethodInfo) element); } else if (element instanceof ClassInfo) { parseClassSignature(elementSignature, (ClassInfo) element); } } } private void parseFieldSignature(String signature, FieldInfo field) { Type type = signatureParser.parseFieldSignature(signature); field.setType(type); } private void parseMethodSignature(String signature, MethodInfo method) { GenericSignatureParser.MethodSignature methodSignature = signatureParser.parseMethodSignature(signature); method.setParameters(methodSignature.methodParameters()); method.setReturnType(methodSignature.returnType()); method.setTypeParameters(methodSignature.typeParameters()); if (methodSignature.throwables().length > 0) { method.setExceptions(methodSignature.throwables()); } } private AnnotationInstance processAnnotation(DataInputStream data, AnnotationTarget target) throws IOException { String annotation = convertClassFieldDescriptor(decodeUtf8Entry(data.readUnsignedShort())); int valuePairs = data.readUnsignedShort(); AnnotationValue[] values = new AnnotationValue[valuePairs]; for (int v = 0; v < valuePairs; v++) { String name = intern(decodeUtf8Entry(data.readUnsignedShort())); values[v] = processAnnotationElementValue(name, data); } // Sort entries so they can be binary searched Arrays.sort(values, new Comparator() { public int compare(AnnotationValue o1, AnnotationValue o2) { return o1.name().compareTo(o2.name()); } }); DotName annotationName = names.convertToName(annotation); AnnotationInstance instance = new AnnotationInstance(annotationName, target, values); // Don't record nested annotations in index if (target != null) { recordAnnotation(classAnnotations, annotationName, instance); recordAnnotation(masterAnnotations, annotationName, instance); if (target instanceof FieldInfo || target instanceof MethodInfo || target instanceof MethodParameterInfo) { elementAnnotations.add(instance); } } return instance; } private void recordAnnotation(Map> classAnnotations, DotName annotation, AnnotationInstance instance) { List list = classAnnotations.get(annotation); if (list == null) { list = new ArrayList(); classAnnotations.put(annotation, list); } list.add(instance); } private String intern(String string) { return names.intern(string); } private byte[] intern(byte[] bytes) { return names.intern(bytes); } private Type intern(Type type) { return names.intern(type); } private Type[] intern(Type[] type) { return names.intern(type); } private AnnotationValue processAnnotationElementValue(String name, DataInputStream data) throws IOException { int tag = data.readUnsignedByte(); switch (tag) { case 'B': return new AnnotationValue.ByteValue(name, (byte)decodeIntegerEntry(data.readUnsignedShort())); case 'C': return new AnnotationValue.CharacterValue(name, (char)decodeIntegerEntry(data.readUnsignedShort())); case 'I': return new AnnotationValue.IntegerValue(name, decodeIntegerEntry(data.readUnsignedShort())); case 'S': return new AnnotationValue.ShortValue(name, (short)decodeIntegerEntry(data.readUnsignedShort())); case 'Z': return new AnnotationValue.BooleanValue(name, decodeIntegerEntry(data.readUnsignedShort()) > 0); case 'F': return new AnnotationValue.FloatValue(name, decodeFloatEntry(data.readUnsignedShort())); case 'D': return new AnnotationValue.DoubleValue(name, decodeDoubleEntry(data.readUnsignedShort())); case 'J': return new AnnotationValue.LongValue(name, decodeLongEntry(data.readUnsignedShort())); case 's': return new AnnotationValue.StringValue(name, decodeUtf8Entry(data.readUnsignedShort())); case 'c': return new AnnotationValue.ClassValue(name, parseType(decodeUtf8Entry(data.readUnsignedShort()))); case 'e': { DotName type = parseType(decodeUtf8Entry(data.readUnsignedShort())).name(); String value = decodeUtf8Entry(data.readUnsignedShort()); return new AnnotationValue.EnumValue(name, type, value); } case '@': return new AnnotationValue.NestedAnnotation(name, processAnnotation(data, null)); case '[': { int numValues = data.readUnsignedShort(); AnnotationValue values[] = new AnnotationValue[numValues]; for (int i = 0; i < numValues; i++) values[i] = processAnnotationElementValue("", data); return new AnnotationValue.ArrayValue(name, values); } default: throw new IllegalStateException("Invalid tag value: " + tag); } } private void processClassInfo(DataInputStream data) throws IOException { short flags = (short) data.readUnsignedShort(); DotName thisName = decodeClassEntry(data.readUnsignedShort()); int superIndex = data.readUnsignedShort(); DotName superName = (superIndex != 0) ? decodeClassEntry(superIndex) : null; int numInterfaces = data.readUnsignedShort(); List interfaces = new ArrayList(numInterfaces); for (int i = 0; i < numInterfaces; i++) { interfaces.add(intern(new ClassType(decodeClassEntry(data.readUnsignedShort())))); } Type[] interfaceTypes = intern(interfaces.toArray(new Type[interfaces.size()])); Type superClassType = superName == null ? null : intern(new ClassType(superName)); this.classAnnotations = new HashMap>(); this.currentClass = new ClassInfo(thisName, superClassType, flags, interfaceTypes, classAnnotations); if (superName != null) addSubclass(superName, currentClass); for (int i = 0; i < numInterfaces; i++) { addImplementor(interfaces.get(i).name(), currentClass); } classes.put(currentClass.name(), currentClass); } private void addSubclass(DotName superName, ClassInfo currentClass) { List list = subclasses.get(superName); if (list == null) { list = new ArrayList(); subclasses.put(superName, list); } list.add(currentClass); } private void addImplementor(DotName interfaceName, ClassInfo currentClass) { List list = implementors.get(interfaceName); if (list == null) { list = new ArrayList(); implementors.put(interfaceName, list); } list.add(currentClass); } private boolean isJDK11OrNewer(DataInputStream stream) throws IOException { int minor = stream.readUnsignedShort(); int major = stream.readUnsignedShort(); return major > 45 || (major == 45 && minor >= 3); } private void verifyMagic(DataInputStream stream) throws IOException { byte[] buf = new byte[4]; stream.readFully(buf); if (buf[0] != (byte)0xCA || buf[1] != (byte)0xFE || buf[2] != (byte)0xBA || buf[3] != (byte)0xBE) throw new IOException("Invalid Magic"); } private DotName decodeClassEntry(int classInfoIndex) { byte[] pool = constantPool; int[] offsets = constantPoolOffsets; int pos = offsets[classInfoIndex - 1]; if (pool[pos] != CONSTANT_CLASS) throw new IllegalStateException("Constant pool entry is not a class info type: " + classInfoIndex + ":" + pos); int nameIndex = (pool[++pos] & 0xFF) << 8 | (pool[++pos] & 0xFF); return names.convertToName(decodeUtf8Entry(nameIndex), '/'); } private String decodeUtf8Entry(int index) { byte[] pool = constantPool; int[] offsets = constantPoolOffsets; int pos = offsets[index - 1]; if (pool[pos] != CONSTANT_UTF8) throw new IllegalStateException("Constant pool entry is not a utf8 info type: " + index + ":" + pos); int len = (pool[++pos] & 0xFF) << 8 | (pool[++pos] & 0xFF); return new String(pool, ++pos, len, Charset.forName("UTF-8")); } private byte[] decodeUtf8EntryAsBytes(int index) { byte[] pool = constantPool; int[] offsets = constantPoolOffsets; int pos = offsets[index - 1]; if (pool[pos] != CONSTANT_UTF8) throw new IllegalStateException("Constant pool entry is not a utf8 info type: " + index + ":" + pos); int len = (pool[++pos] & 0xFF) << 8 | (pool[++pos] & 0xFF); return Arrays.copyOfRange(pool, ++pos, len + pos); } private static class NameAndType { private String name; private String descriptor; private NameAndType(String name, String descriptor) { this.name = name; this.descriptor = descriptor; } } private NameAndType decodeNameAndTypeEntry(int index) { byte[] pool = constantPool; int[] offsets = constantPoolOffsets; int pos = offsets[index - 1]; if (pool[pos] != CONSTANT_NAMEANDTYPE) throw new IllegalStateException("Constant pool entry is not a name and type type: " + index + ":" + pos); int nameIndex = (pool[++pos] & 0xFF) << 8 | (pool[++pos] & 0xFF); int descriptorIndex = (pool[++pos] & 0xFF) << 8 | (pool[++pos] & 0xFF); return new NameAndType(intern(decodeUtf8Entry(nameIndex)), decodeUtf8Entry(descriptorIndex)); } private int bitsToInt(byte[] pool, int pos) { return (pool[++pos] & 0xFF) << 24 | (pool[++pos] & 0xFF) << 16 | (pool[++pos] & 0xFF) << 8 | (pool[++pos] & 0xFF); } private long bitsToLong(byte[] pool, int pos) { return ((long)pool[++pos] & 0xFF) << 56 | ((long)pool[++pos] & 0xFF) << 48 | ((long)pool[++pos] & 0xFF) << 40 | ((long)pool[++pos] & 0xFF) << 32 | (pool[++pos] & 0xFF) << 24 | (pool[++pos] & 0xFF) << 16 | (pool[++pos] & 0xFF) << 8 | (pool[++pos] & 0xFF); } private int decodeIntegerEntry(int index) { byte[] pool = constantPool; int[] offsets = constantPoolOffsets; int pos = offsets[index - 1]; if (pool[pos] != CONSTANT_INTEGER) throw new IllegalStateException("Constant pool entry is not an integer info type: " + index + ":" + pos); return bitsToInt(pool, pos); } private long decodeLongEntry(int index) { byte[] pool = constantPool; int[] offsets = constantPoolOffsets; int pos = offsets[index - 1]; if (pool[pos] != CONSTANT_LONG) throw new IllegalStateException("Constant pool entry is not an long info type: " + index + ":" + pos); return bitsToLong(pool, pos); } private float decodeFloatEntry(int index) { byte[] pool = constantPool; int[] offsets = constantPoolOffsets; int pos = offsets[index - 1]; if (pool[pos] != CONSTANT_FLOAT) throw new IllegalStateException("Constant pool entry is not an float info type: " + index + ":" + pos); return Float.intBitsToFloat(bitsToInt(pool, pos)); } private double decodeDoubleEntry(int index) { byte[] pool = constantPool; int[] offsets = constantPoolOffsets; int pos = offsets[index - 1]; if (pool[pos] != CONSTANT_DOUBLE) throw new IllegalStateException("Constant pool entry is not an double info type: " + index + ":" + pos); return Double.longBitsToDouble(bitsToLong(pool, pos)); } private static String convertClassFieldDescriptor(String descriptor) { if (descriptor.charAt(0) != 'L') throw new IllegalArgumentException("Non class descriptor: " + descriptor); return descriptor.substring(1, descriptor.length() -1 ).replace('/', '.'); } private static class IntegerHolder { private int i; }; private Type[] parseMethodArgs(String descriptor, IntegerHolder pos) { if (descriptor.charAt(pos.i) != '(') throw new IllegalArgumentException("Invalid descriptor: " + descriptor); ArrayList types = new ArrayList(); while (descriptor.charAt(++pos.i) != ')') { types.add(parseType(descriptor, pos)); } pos.i++; return types.toArray(new Type[types.size()]); } private Type parseType(String descriptor) { return parseType(descriptor, new IntegerHolder()); } private Type parseType(String descriptor, IntegerHolder pos) { int start = pos.i; char c = descriptor.charAt(start); Type type = PrimitiveType.decode(c); if (type != null) { return type; } DotName name; switch (c) { case 'V': return VoidType.VOID; case 'L': { int end = start; while (descriptor.charAt(++end) != ';'); name = names.convertToName(descriptor.substring(start + 1, end), '/'); pos.i = end; return names.intern(new ClassType(name)); } case '[': { int end = start; while (descriptor.charAt(++end) == '['); int depth = end - start; pos.i = end; type = parseType(descriptor, pos); return names.intern(new ArrayType(type, depth)); } default: throw new IllegalArgumentException("Invalid descriptor: " + descriptor + " pos " + start); } } private boolean processConstantPool(DataInputStream stream) throws IOException { int poolCount = stream.readUnsignedShort() - 1; byte[] buf = new byte[20 * poolCount]; // Guess byte[] annoAttributes = new byte[poolCount]; int[] offsets = new int[poolCount]; boolean hasAnnotations = false; for (int pos = 0, offset = 0; pos < poolCount; pos++) { int tag = stream.readUnsignedByte(); offsets[pos] = offset; switch (tag) { case CONSTANT_CLASS: case CONSTANT_STRING: buf = sizeToFit(buf, 3, offset, poolCount - pos); buf[offset++] = (byte) tag; stream.readFully(buf, offset, 2); offset += 2; break; case CONSTANT_FIELDREF: case CONSTANT_METHODREF: case CONSTANT_INTERFACEMETHODREF: case CONSTANT_INTEGER: case CONSTANT_INVOKEDYNAMIC: case CONSTANT_FLOAT: case CONSTANT_NAMEANDTYPE: buf = sizeToFit(buf, 5, offset, poolCount - pos); buf[offset++] = (byte) tag; stream.readFully(buf, offset, 4); offset += 4; break; case CONSTANT_LONG: case CONSTANT_DOUBLE: buf = sizeToFit(buf, 9, offset, poolCount - pos); buf[offset++] = (byte) tag; stream.readFully(buf, offset, 8); offset += 8; pos++; // 8 byte constant pool entries take two "virtual" slots for some reason break; case CONSTANT_METHODHANDLE: buf = sizeToFit(buf, 4, offset, poolCount - pos); buf[offset++] = (byte) tag; stream.readFully(buf, offset, 3); offset += 3; break; case CONSTANT_METHODTYPE: buf = sizeToFit(buf, 3, offset, poolCount - pos); buf[offset++] = (byte) tag; stream.readFully(buf, offset, 2); offset += 2; break; case CONSTANT_UTF8: int len = stream.readUnsignedShort(); buf = sizeToFit(buf, len + 3, offset, poolCount - pos); buf[offset++] = (byte) tag; buf[offset++] = (byte) (len >>> 8); buf[offset++] = (byte) len; stream.readFully(buf, offset, len); if (len == RUNTIME_ANNOTATIONS_LEN && match(buf, offset, RUNTIME_ANNOTATIONS)) { annoAttributes[pos] = HAS_RUNTIME_ANNOTATION; hasAnnotations = true; } else if (len == RUNTIME_PARAM_ANNOTATIONS_LEN && match(buf, offset, RUNTIME_PARAM_ANNOTATIONS)) { annoAttributes[pos] = HAS_RUNTIME_PARAM_ANNOTATION; hasAnnotations = true; } else if (len == RUNTIME_TYPE_ANNOTATIONS_LEN && match(buf, offset, RUNTIME_TYPE_ANNOTATIONS)) { annoAttributes[pos] = HAS_RUNTIME_TYPE_ANNOTATION; } else if (len == SIGNATURE_LEN && match(buf, offset, SIGNATURE)) { annoAttributes[pos] = HAS_SIGNATURE; } else if (len == EXCEPTIONS_LEN && match(buf, offset, EXCEPTIONS)) { annoAttributes[pos] = HAS_EXCEPTIONS; } else if (len == INNER_CLASSES_LEN && match(buf, offset, INNER_CLASSES)) { annoAttributes[pos] = HAS_INNER_CLASSES; } else if (len == ENCLOSING_METHOD_LEN && match(buf, offset, ENCLOSING_METHOD)) { annoAttributes[pos] = HAS_ENCLOSING_METHOD; } offset += len; break; default: throw new IllegalStateException("Unknown tag! pos=" + pos + " poolCount = " + poolCount); } } constantPool = buf; constantPoolOffsets = offsets; constantPoolAnnoAttrributes = annoAttributes; return hasAnnotations; } /** * Analyze and index the class file data present in the passed input stream. * Each call adds information to the final complete index; however, to aid in * processing a per-class index (ClassInfo) is returned on each call. * * @param stream a stream pointing to class file data * @return a class index containing all annotations on the passed class stream * @throws IOException if the class file data is corrupt or the underlying stream fails */ public ClassInfo index(InputStream stream) throws IOException { try { DataInputStream data = new DataInputStream(new BufferedInputStream(stream)); verifyMagic(data); // Retroweaved classes may contain annotations // Also, hierarchy info is needed regardless if (!isJDK11OrNewer(data)) return null; initIndexMaps(); initClassFields(); processConstantPool(data); processClassInfo(data); processFieldInfo(data); processMethodInfo(data); processAttributes(data, currentClass); applySignatures(); resolveTypeAnnotations(); updateTypeTargets(); currentClass.setMethods(methods, names); currentClass.setFields(fields, names); return currentClass; } finally { constantPool = null; constantPoolOffsets = null; constantPoolAnnoAttrributes = null; currentClass = null; classAnnotations = null; elementAnnotations = null; innerClasses = null; signatures = null; signaturePresent = null; } } /** * Completes, finalizes, and returns the index after zero or more calls to * index. Future calls to index will result in a new index. * * @return the master index for all scanned class streams */ public Index complete() { initIndexMaps(); try { return Index.create(masterAnnotations, subclasses, implementors, classes); } finally { masterAnnotations = null; subclasses = null; classes = null; signatureParser = null; names = null; } } }