/* * Copyright (C) 2010-2018 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package lombok.bytecode; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Utility to read the constant pool, header, and inheritance information of any class file. */ public class ClassFileMetaData { private static final byte UTF8 = 1; private static final byte INTEGER = 3; private static final byte FLOAT = 4; private static final byte LONG = 5; private static final byte DOUBLE = 6; private static final byte CLASS = 7; private static final byte STRING = 8; private static final byte FIELD = 9; private static final byte METHOD = 10; private static final byte INTERFACE_METHOD = 11; private static final byte NAME_TYPE = 12; // New in java7: support for methodhandles and invokedynamic private static final byte METHOD_HANDLE = 15; private static final byte METHOD_TYPE = 16; private static final byte INVOKE_DYNAMIC = 18; // New in java9: support for modules private static final byte MODULE = 19; private static final byte PACKAGE = 20; private static final int NOT_FOUND = -1; private static final int START_OF_CONSTANT_POOL = 8; private final byte[] byteCode; private final int maxPoolSize; private final int[] offsets; private final byte[] types; private final String[] utf8s; private final int endOfPool; public ClassFileMetaData(byte[] byteCode) { this.byteCode = byteCode; maxPoolSize = readValue(START_OF_CONSTANT_POOL); offsets = new int[maxPoolSize]; types = new byte[maxPoolSize]; utf8s = new String[maxPoolSize]; int position = 10; for (int i = 1; i < maxPoolSize; i++) { byte type = byteCode[position]; types[i] = type; position++; offsets[i] = position; switch (type) { case UTF8: int length = readValue(position); position += 2; utf8s[i] = decodeString(position, length); position += length; break; case CLASS: case STRING: case METHOD_TYPE: case MODULE: case PACKAGE: position += 2; break; case METHOD_HANDLE: position += 3; break; case INTEGER: case FLOAT: case FIELD: case METHOD: case INTERFACE_METHOD: case NAME_TYPE: case INVOKE_DYNAMIC: position += 4; break; case LONG: case DOUBLE: position += 8; i++; break; case 0: break; default: throw new AssertionError("Unknown constant pool type " + type); } } endOfPool = position; } private String decodeString(int pos, int size) { int end = pos + size; // the resulting string might be smaller StringBuilder result = new StringBuilder(size); while (pos < end) { int first = (byteCode[pos++] & 0xFF); if (first < 0x80) { result.append((char)first); } else if ((first & 0xE0) == 0xC0) { int x = (first & 0x1F) << 6; int y = (byteCode[pos++] & 0x3F); result.append((char)(x | y)); } else { int x = (first & 0x0F) << 12; int y = (byteCode[pos++] & 0x3F) << 6; int z = (byteCode[pos++] & 0x3F); result.append((char)(x | y | z)); } } return result.toString(); } /** * Checks if the constant pool contains the provided 'raw' string. These are used as source material for further JVM types, such as string constants, type references, etcetera. */ public boolean containsUtf8(String value) { return findUtf8(value) != NOT_FOUND; } /** * Checks if the constant pool contains a reference to the provided class. * * NB: Most uses of a type do NOT show up as a class in the constant pool. * For example, the parameter types and return type of any method you invoke or declare, are stored as signatures and not as type references, * but the type to which any method you invoke belongs, is. Read the JVM Specification for more information. * * @param className must be provided JVM-style, such as {@code java/lang/String} */ public boolean usesClass(String className) { return findClass(className) != NOT_FOUND; } /** * Checks if the constant pool contains a reference to a given field, either for writing or reading. * * @param className must be provided JVM-style, such as {@code java/lang/String} */ public boolean usesField(String className, String fieldName) { int classIndex = findClass(className); if (classIndex == NOT_FOUND) return false; int fieldNameIndex = findUtf8(fieldName); if (fieldNameIndex == NOT_FOUND) return false; for (int i = 1; i < maxPoolSize; i++) { if (types[i] == FIELD && readValue(offsets[i]) == classIndex) { int nameAndTypeIndex = readValue(offsets[i] + 2); if (readValue(offsets[nameAndTypeIndex]) == fieldNameIndex) return true; } } return false; } /** * Checks if the constant pool contains a reference to a given method, with any signature (return type and parameter types). * * @param className must be provided JVM-style, such as {@code java/lang/String} */ public boolean usesMethod(String className, String methodName) { int classIndex = findClass(className); if (classIndex == NOT_FOUND) return false; int methodNameIndex = findUtf8(methodName); if (methodNameIndex == NOT_FOUND) return false; for (int i = 1; i < maxPoolSize; i++) { if (isMethod(i) && readValue(offsets[i]) == classIndex) { int nameAndTypeIndex = readValue(offsets[i] + 2); if (readValue(offsets[nameAndTypeIndex]) == methodNameIndex) return true; } } return false; } /** * Checks if the constant pool contains a reference to a given method. * * @param className must be provided JVM-style, such as {@code java/lang/String} * @param descriptor must be provided JVM-style, such as {@code (IZ)Ljava/lang/String;} */ public boolean usesMethod(String className, String methodName, String descriptor) { int classIndex = findClass(className); if (classIndex == NOT_FOUND) return false; int nameAndTypeIndex = findNameAndType(methodName, descriptor); if (nameAndTypeIndex == NOT_FOUND) return false; for (int i = 1; i < maxPoolSize; i++) { if (isMethod(i) && readValue(offsets[i]) == classIndex && readValue(offsets[i] + 2) == nameAndTypeIndex) return true; } return false; } /** * Checks if the constant pool contains the provided string constant, which implies the constant is used somewhere in the code. * * NB: String literals get concatenated by the compiler. * NB2: This method does NOT do any kind of normalization. */ public boolean containsStringConstant(String value) { int index = findUtf8(value); if (index == NOT_FOUND) return false; for (int i = 1; i < maxPoolSize; i++) { if (types[i] == STRING && readValue(offsets[i]) == index) return true; } return false; } /** * Checks if the constant pool contains the provided long constant, which implies the constant is used somewhere in the code. * * NB: compile-time constant expressions are evaluated at compile time. */ public boolean containsLong(long value) { for (int i = 1; i < maxPoolSize; i++) { if (types[i] == LONG && readLong(i) == value) return true; } return false; } /** * Checks if the constant pool contains the provided double constant, which implies the constant is used somewhere in the code. * * NB: compile-time constant expressions are evaluated at compile time. */ public boolean containsDouble(double value) { boolean isNan = Double.isNaN(value); for (int i = 1; i < maxPoolSize; i++) { if (types[i] == DOUBLE) { double d = readDouble(i); if (d == value || (isNan && Double.isNaN(d))) return true; } } return false; } /** * Checks if the constant pool contains the provided int constant, which implies the constant is used somewhere in the code. * * NB: compile-time constant expressions are evaluated at compile time. */ public boolean containsInteger(int value) { for (int i = 1; i < maxPoolSize; i++) { if (types[i] == INTEGER && readInteger(i) == value) return true; } return false; } /** * Checks if the constant pool contains the provided float constant, which implies the constant is used somewhere in the code. * * NB: compile-time constant expressions are evaluated at compile time. */ public boolean containsFloat(float value) { boolean isNan = Float.isNaN(value); for (int i = 1; i < maxPoolSize; i++) { if (types[i] == FLOAT) { float f = readFloat(i); if (f == value || (isNan && Float.isNaN(f))) return true; } } return false; } private long readLong(int index) { int pos = offsets[index]; return ((long)read32(pos)) << 32 | (read32(pos + 4) & 0x00000000FFFFFFFFL); } private double readDouble(int index) { return Double.longBitsToDouble(readLong(index)); } private int readInteger(int index) { return read32(offsets[index]); } private float readFloat(int index) { return Float.intBitsToFloat(readInteger(index)); } private int read32(int pos) { return (byteCode[pos] & 0xFF) << 24 | (byteCode[pos + 1] & 0xFF) << 16 | (byteCode[pos + 2] & 0xFF) << 8 | (byteCode[pos + 3] & 0xFF); } /** * Returns the name of the class in JVM format, such as {@code java/lang/String} */ public String getClassName() { return getClassName(readValue(endOfPool + 2)); } /** * Returns the name of the superclass in JVM format, such as {@code java/lang/Object} * * NB: If you try this on Object itself, you'll get {@code null}.
* NB2: For interfaces and annotation interfaces, you'll always get {@code java/lang/Object} */ public String getSuperClassName() { return getClassName(readValue(endOfPool + 4)); } /** * Returns the name of all implemented interfaces. */ public List getInterfaces() { int size = readValue(endOfPool + 6); if (size == 0) return Collections.emptyList(); List result = new ArrayList(); for (int i = 0; i < size; i++) { result.add(getClassName(readValue(endOfPool + 8 + (i * 2)))); } return result; } /** * A {@code toString()} like utility to dump all contents of the constant pool into a string. * * NB: No guarantees are made about the exact layout of this string. It is for informational purposes only, don't try to parse it.
* NB2: After a double or long, there's a JVM spec-mandated gap, which is listed as {@code (cont.)} in the returned string. */ public String poolContent() { StringBuilder result = new StringBuilder(); for (int i = 1; i < maxPoolSize; i++) { result.append(String.format("#%02x: ", i)); int pos = offsets[i]; switch(types[i]) { case UTF8: result.append("Utf8 ").append(utf8s[i]); break; case CLASS: result.append("Class ").append(getClassName(i)); break; case STRING: result.append("String \"").append(utf8s[readValue(pos)]).append("\""); break; case INTEGER: result.append("int ").append(readInteger(i)); break; case FLOAT: result.append("float ").append(readFloat(i)); break; case FIELD: appendAccess(result.append("Field "), i); break; case METHOD: case INTERFACE_METHOD: appendAccess(result.append("Method "), i); break; case NAME_TYPE: appendNameAndType(result.append("Name&Type "), i); break; case LONG: result.append("long ").append(readLong(i)); break; case DOUBLE: result.append("double ").append(readDouble(i)); break; case METHOD_HANDLE: result.append("MethodHandle..."); break; case METHOD_TYPE: result.append("MethodType..."); break; case INVOKE_DYNAMIC: result.append("InvokeDynamic..."); break; case 0: result.append("(cont.)"); break; } result.append("\n"); } return result.toString(); } private void appendAccess(StringBuilder result, int index) { int pos = offsets[index]; result.append(getClassName(readValue(pos))).append("."); appendNameAndType(result, readValue(pos + 2)); } private void appendNameAndType(StringBuilder result, int index) { int pos = offsets[index]; result.append(utf8s[readValue(pos)]).append(":").append(utf8s[readValue(pos + 2)]); } private String getClassName(int classIndex) { if (classIndex < 1) return null; return utf8s[readValue(offsets[classIndex])]; } private boolean isMethod(int i) { byte type = types[i]; return type == METHOD || type == INTERFACE_METHOD; } private int findNameAndType(String name, String descriptor) { int nameIndex = findUtf8(name); if (nameIndex == NOT_FOUND) return NOT_FOUND; int descriptorIndex = findUtf8(descriptor); if (descriptorIndex == NOT_FOUND) return NOT_FOUND; for (int i = 1; i < maxPoolSize; i++) { if (types[i] == NAME_TYPE && readValue(offsets[i]) == nameIndex && readValue(offsets[i] + 2) == descriptorIndex) return i; } return NOT_FOUND; } private int findUtf8(String value) { for (int i = 1; i < maxPoolSize; i++) { if (value.equals(utf8s[i])) { return i; } } return NOT_FOUND; } private int findClass(String className) { int index = findUtf8(className); if (index == -1) return NOT_FOUND; for (int i = 1; i < maxPoolSize; i++) { if (types[i] == CLASS && readValue(offsets[i]) == index) return i; } return NOT_FOUND; } private int readValue(int position) { return ((byteCode[position] & 0xFF) << 8) | (byteCode[position + 1] & 0xFF); } }