/* * Copyright (c) 2009, 2013, 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 com.sun.tools.classfile; import java.io.IOException; import java.util.ArrayList; import java.util.List; import com.sun.tools.classfile.TypeAnnotation.Position.TypePathEntry; /** * See JSR 308 specification, Section 3. * * <p><b>This is NOT part of any supported API. * If you write code that depends on this, you do so at your own risk. * This code and its internal interfaces are subject to change or * deletion without notice.</b> */ public class TypeAnnotation { TypeAnnotation(ClassReader cr) throws IOException, Annotation.InvalidAnnotation { constant_pool = cr.getConstantPool(); position = read_position(cr); annotation = new Annotation(cr); } public TypeAnnotation(ConstantPool constant_pool, Annotation annotation, Position position) { this.constant_pool = constant_pool; this.position = position; this.annotation = annotation; } public int length() { int n = annotation.length(); n += position_length(position); return n; } @Override public String toString() { try { return "@" + constant_pool.getUTF8Value(annotation.type_index).toString().substring(1) + " pos: " + position.toString(); } catch (Exception e) { e.printStackTrace(); return e.toString(); } } public final ConstantPool constant_pool; public final Position position; public final Annotation annotation; private static Position read_position(ClassReader cr) throws IOException, Annotation.InvalidAnnotation { // Copied from ClassReader int tag = cr.readUnsignedByte(); // TargetType tag is a byte if (!TargetType.isValidTargetTypeValue(tag)) throw new Annotation.InvalidAnnotation("TypeAnnotation: Invalid type annotation target type value: " + String.format("0x%02X", tag)); TargetType type = TargetType.fromTargetTypeValue(tag); Position position = new Position(); position.type = type; switch (type) { // instanceof case INSTANCEOF: // new expression case NEW: // constructor/method reference receiver case CONSTRUCTOR_REFERENCE: case METHOD_REFERENCE: position.offset = cr.readUnsignedShort(); break; // local variable case LOCAL_VARIABLE: // resource variable case RESOURCE_VARIABLE: int table_length = cr.readUnsignedShort(); position.lvarOffset = new int[table_length]; position.lvarLength = new int[table_length]; position.lvarIndex = new int[table_length]; for (int i = 0; i < table_length; ++i) { position.lvarOffset[i] = cr.readUnsignedShort(); position.lvarLength[i] = cr.readUnsignedShort(); position.lvarIndex[i] = cr.readUnsignedShort(); } break; // exception parameter case EXCEPTION_PARAMETER: position.exception_index = cr.readUnsignedShort(); break; // method receiver case METHOD_RECEIVER: // Do nothing break; // type parameter case CLASS_TYPE_PARAMETER: case METHOD_TYPE_PARAMETER: position.parameter_index = cr.readUnsignedByte(); break; // type parameter bound case CLASS_TYPE_PARAMETER_BOUND: case METHOD_TYPE_PARAMETER_BOUND: position.parameter_index = cr.readUnsignedByte(); position.bound_index = cr.readUnsignedByte(); break; // class extends or implements clause case CLASS_EXTENDS: position.type_index = cr.readUnsignedShort();; break; // throws case THROWS: position.type_index = cr.readUnsignedShort(); break; // method parameter case METHOD_FORMAL_PARAMETER: position.parameter_index = cr.readUnsignedByte(); break; // type cast case CAST: // method/constructor/reference type argument case CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT: case METHOD_INVOCATION_TYPE_ARGUMENT: case CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT: case METHOD_REFERENCE_TYPE_ARGUMENT: position.offset = cr.readUnsignedShort(); position.type_index = cr.readUnsignedByte(); break; // We don't need to worry about these case METHOD_RETURN: case FIELD: break; case UNKNOWN: throw new AssertionError("TypeAnnotation: UNKNOWN target type should never occur!"); default: throw new AssertionError("TypeAnnotation: Unknown target type: " + type); } { // Write type path int len = cr.readUnsignedByte(); List<Integer> loc = new ArrayList<>(len); for (int i = 0; i < len * TypePathEntry.bytesPerEntry; ++i) loc.add(cr.readUnsignedByte()); position.location = Position.getTypePathFromBinary(loc); } return position; } private static int position_length(Position pos) { int n = 0; n += 1; // TargetType tag is a byte switch (pos.type) { // instanceof case INSTANCEOF: // new expression case NEW: // constructor/method reference receiver case CONSTRUCTOR_REFERENCE: case METHOD_REFERENCE: n += 2; // offset break; // local variable case LOCAL_VARIABLE: // resource variable case RESOURCE_VARIABLE: n += 2; // table_length; int table_length = pos.lvarOffset.length; n += 2 * table_length; // offset n += 2 * table_length; // length n += 2 * table_length; // index break; // exception parameter case EXCEPTION_PARAMETER: n += 2; // exception_index break; // method receiver case METHOD_RECEIVER: // Do nothing break; // type parameter case CLASS_TYPE_PARAMETER: case METHOD_TYPE_PARAMETER: n += 1; // parameter_index break; // type parameter bound case CLASS_TYPE_PARAMETER_BOUND: case METHOD_TYPE_PARAMETER_BOUND: n += 1; // parameter_index n += 1; // bound_index break; // class extends or implements clause case CLASS_EXTENDS: n += 2; // type_index break; // throws case THROWS: n += 2; // type_index break; // method parameter case METHOD_FORMAL_PARAMETER: n += 1; // parameter_index break; // type cast case CAST: // method/constructor/reference type argument case CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT: case METHOD_INVOCATION_TYPE_ARGUMENT: case CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT: case METHOD_REFERENCE_TYPE_ARGUMENT: n += 2; // offset n += 1; // type index break; // We don't need to worry about these case METHOD_RETURN: case FIELD: break; case UNKNOWN: throw new AssertionError("TypeAnnotation: UNKNOWN target type should never occur!"); default: throw new AssertionError("TypeAnnotation: Unknown target type: " + pos.type); } { n += 1; // length n += TypePathEntry.bytesPerEntry * pos.location.size(); // bytes for actual array } return n; } // Code duplicated from com.sun.tools.javac.code.TypeAnnotationPosition public static class Position { public enum TypePathEntryKind { ARRAY(0), INNER_TYPE(1), WILDCARD(2), TYPE_ARGUMENT(3); public final int tag; private TypePathEntryKind(int tag) { this.tag = tag; } } public static class TypePathEntry { /** The fixed number of bytes per TypePathEntry. */ public static final int bytesPerEntry = 2; public final TypePathEntryKind tag; public final int arg; public static final TypePathEntry ARRAY = new TypePathEntry(TypePathEntryKind.ARRAY); public static final TypePathEntry INNER_TYPE = new TypePathEntry(TypePathEntryKind.INNER_TYPE); public static final TypePathEntry WILDCARD = new TypePathEntry(TypePathEntryKind.WILDCARD); private TypePathEntry(TypePathEntryKind tag) { if (!(tag == TypePathEntryKind.ARRAY || tag == TypePathEntryKind.INNER_TYPE || tag == TypePathEntryKind.WILDCARD)) { throw new AssertionError("Invalid TypePathEntryKind: " + tag); } this.tag = tag; this.arg = 0; } public TypePathEntry(TypePathEntryKind tag, int arg) { if (tag != TypePathEntryKind.TYPE_ARGUMENT) { throw new AssertionError("Invalid TypePathEntryKind: " + tag); } this.tag = tag; this.arg = arg; } public static TypePathEntry fromBinary(int tag, int arg) { if (arg != 0 && tag != TypePathEntryKind.TYPE_ARGUMENT.tag) { throw new AssertionError("Invalid TypePathEntry tag/arg: " + tag + "/" + arg); } switch (tag) { case 0: return ARRAY; case 1: return INNER_TYPE; case 2: return WILDCARD; case 3: return new TypePathEntry(TypePathEntryKind.TYPE_ARGUMENT, arg); default: throw new AssertionError("Invalid TypePathEntryKind tag: " + tag); } } @Override public String toString() { return tag.toString() + (tag == TypePathEntryKind.TYPE_ARGUMENT ? ("(" + arg + ")") : ""); } @Override public boolean equals(Object other) { if (! (other instanceof TypePathEntry)) { return false; } TypePathEntry tpe = (TypePathEntry) other; return this.tag == tpe.tag && this.arg == tpe.arg; } @Override public int hashCode() { return this.tag.hashCode() * 17 + this.arg; } } public TargetType type = TargetType.UNKNOWN; // For generic/array types. // TODO: or should we use null? Noone will use this object. public List<TypePathEntry> location = new ArrayList<>(0); // Tree position. public int pos = -1; // For typecasts, type tests, new (and locals, as start_pc). public boolean isValidOffset = false; public int offset = -1; // For locals. arrays same length public int[] lvarOffset = null; public int[] lvarLength = null; public int[] lvarIndex = null; // For type parameter bound public int bound_index = Integer.MIN_VALUE; // For type parameter and method parameter public int parameter_index = Integer.MIN_VALUE; // For class extends, implements, and throws clauses public int type_index = Integer.MIN_VALUE; // For exception parameters, index into exception table public int exception_index = Integer.MIN_VALUE; public Position() {} @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append('['); sb.append(type); switch (type) { // instanceof case INSTANCEOF: // new expression case NEW: // constructor/method reference receiver case CONSTRUCTOR_REFERENCE: case METHOD_REFERENCE: sb.append(", offset = "); sb.append(offset); break; // local variable case LOCAL_VARIABLE: // resource variable case RESOURCE_VARIABLE: if (lvarOffset == null) { sb.append(", lvarOffset is null!"); break; } sb.append(", {"); for (int i = 0; i < lvarOffset.length; ++i) { if (i != 0) sb.append("; "); sb.append("start_pc = "); sb.append(lvarOffset[i]); sb.append(", length = "); sb.append(lvarLength[i]); sb.append(", index = "); sb.append(lvarIndex[i]); } sb.append("}"); break; // method receiver case METHOD_RECEIVER: // Do nothing break; // type parameter case CLASS_TYPE_PARAMETER: case METHOD_TYPE_PARAMETER: sb.append(", param_index = "); sb.append(parameter_index); break; // type parameter bound case CLASS_TYPE_PARAMETER_BOUND: case METHOD_TYPE_PARAMETER_BOUND: sb.append(", param_index = "); sb.append(parameter_index); sb.append(", bound_index = "); sb.append(bound_index); break; // class extends or implements clause case CLASS_EXTENDS: sb.append(", type_index = "); sb.append(type_index); break; // throws case THROWS: sb.append(", type_index = "); sb.append(type_index); break; // exception parameter case EXCEPTION_PARAMETER: sb.append(", exception_index = "); sb.append(exception_index); break; // method parameter case METHOD_FORMAL_PARAMETER: sb.append(", param_index = "); sb.append(parameter_index); break; // type cast case CAST: // method/constructor/reference type argument case CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT: case METHOD_INVOCATION_TYPE_ARGUMENT: case CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT: case METHOD_REFERENCE_TYPE_ARGUMENT: sb.append(", offset = "); sb.append(offset); sb.append(", type_index = "); sb.append(type_index); break; // We don't need to worry about these case METHOD_RETURN: case FIELD: break; case UNKNOWN: sb.append(", position UNKNOWN!"); break; default: throw new AssertionError("Unknown target type: " + type); } // Append location data for generics/arrays. if (!location.isEmpty()) { sb.append(", location = ("); sb.append(location); sb.append(")"); } sb.append(", pos = "); sb.append(pos); sb.append(']'); return sb.toString(); } /** * Indicates whether the target tree of the annotation has been optimized * away from classfile or not. * @return true if the target has not been optimized away */ public boolean emitToClassfile() { return !type.isLocal() || isValidOffset; } /** * Decode the binary representation for a type path and set * the {@code location} field. * * @param list The bytecode representation of the type path. */ public static List<TypePathEntry> getTypePathFromBinary(List<Integer> list) { List<TypePathEntry> loc = new ArrayList<>(list.size() / TypePathEntry.bytesPerEntry); int idx = 0; while (idx < list.size()) { if (idx + 1 == list.size()) { throw new AssertionError("Could not decode type path: " + list); } loc.add(TypePathEntry.fromBinary(list.get(idx), list.get(idx + 1))); idx += 2; } return loc; } public static List<Integer> getBinaryFromTypePath(List<TypePathEntry> locs) { List<Integer> loc = new ArrayList<>(locs.size() * TypePathEntry.bytesPerEntry); for (TypePathEntry tpe : locs) { loc.add(tpe.tag.tag); loc.add(tpe.arg); } return loc; } } // Code duplicated from com.sun.tools.javac.code.TargetType // The IsLocal flag could be removed here. public enum TargetType { /** For annotations on a class type parameter declaration. */ CLASS_TYPE_PARAMETER(0x00), /** For annotations on a method type parameter declaration. */ METHOD_TYPE_PARAMETER(0x01), /** For annotations on the type of an "extends" or "implements" clause. */ CLASS_EXTENDS(0x10), /** For annotations on a bound of a type parameter of a class. */ CLASS_TYPE_PARAMETER_BOUND(0x11), /** For annotations on a bound of a type parameter of a method. */ METHOD_TYPE_PARAMETER_BOUND(0x12), /** For annotations on a field. */ FIELD(0x13), /** For annotations on a method return type. */ METHOD_RETURN(0x14), /** For annotations on the method receiver. */ METHOD_RECEIVER(0x15), /** For annotations on a method parameter. */ METHOD_FORMAL_PARAMETER(0x16), /** For annotations on a throws clause in a method declaration. */ THROWS(0x17), /** For annotations on a local variable. */ LOCAL_VARIABLE(0x40, true), /** For annotations on a resource variable. */ RESOURCE_VARIABLE(0x41, true), /** For annotations on an exception parameter. */ EXCEPTION_PARAMETER(0x42, true), /** For annotations on a type test. */ INSTANCEOF(0x43, true), /** For annotations on an object creation expression. */ NEW(0x44, true), /** For annotations on a constructor reference receiver. */ CONSTRUCTOR_REFERENCE(0x45, true), /** For annotations on a method reference receiver. */ METHOD_REFERENCE(0x46, true), /** For annotations on a typecast. */ CAST(0x47, true), /** For annotations on a type argument of an object creation expression. */ CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT(0x48, true), /** For annotations on a type argument of a method call. */ METHOD_INVOCATION_TYPE_ARGUMENT(0x49, true), /** For annotations on a type argument of a constructor reference. */ CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT(0x4A, true), /** For annotations on a type argument of a method reference. */ METHOD_REFERENCE_TYPE_ARGUMENT(0x4B, true), /** For annotations with an unknown target. */ UNKNOWN(0xFF); private static final int MAXIMUM_TARGET_TYPE_VALUE = 0x4B; private final int targetTypeValue; private final boolean isLocal; private TargetType(int targetTypeValue) { this(targetTypeValue, false); } private TargetType(int targetTypeValue, boolean isLocal) { if (targetTypeValue < 0 || targetTypeValue > 255) throw new AssertionError("Attribute type value needs to be an unsigned byte: " + String.format("0x%02X", targetTypeValue)); this.targetTypeValue = targetTypeValue; this.isLocal = isLocal; } /** * Returns whether or not this TargetType represents an annotation whose * target is exclusively a tree in a method body * * Note: wildcard bound targets could target a local tree and a class * member declaration signature tree */ public boolean isLocal() { return isLocal; } public int targetTypeValue() { return this.targetTypeValue; } private static final TargetType[] targets; static { targets = new TargetType[MAXIMUM_TARGET_TYPE_VALUE + 1]; TargetType[] alltargets = values(); for (TargetType target : alltargets) { if (target.targetTypeValue != UNKNOWN.targetTypeValue) targets[target.targetTypeValue] = target; } for (int i = 0; i <= MAXIMUM_TARGET_TYPE_VALUE; ++i) { if (targets[i] == null) targets[i] = UNKNOWN; } } public static boolean isValidTargetTypeValue(int tag) { if (tag == UNKNOWN.targetTypeValue) return true; return (tag >= 0 && tag < targets.length); } public static TargetType fromTargetTypeValue(int tag) { if (tag == UNKNOWN.targetTypeValue) return UNKNOWN; if (tag < 0 || tag >= targets.length) throw new AssertionError("Unknown TargetType: " + tag); return targets[tag]; } } }