mirror of
https://github.com/tiennm99/lombok.git
synced 2026-10-15 10:15:19 +00:00
[loader] Fix an issue when using lombok as a module while using ObjectWeb ASM as dep.
Evidently jigsaw's damage to java extends to our way of shading ObjectWeb ASM dep - it's a split package violation even though lombok loads its stuff in a separate loader. We fix it by using some on-the-fly extremely fast constant pool rewriting.
This commit is contained in:
4 files changed
+620
-2
No files matched your search
@@ -22,11 +22,15 @@
|
||||
package lombok.bytecode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utility to read the constant pool, header, and inheritance information of any class file.
|
||||
*
|
||||
* THIS IS A COPY OF lombok.launch.ClassFileMetaData - it is used by the shadowclassloader which can't rely on anything from outside the lombok.launch package, but everything in the launch package has to be package private.
|
||||
* If there is a bug or an update is needed here - make sure to also update it in the lombok.bytecode package.
|
||||
*/
|
||||
public class ClassFileMetaData {
|
||||
private static final byte UTF8 = 1;
|
||||
@@ -54,7 +58,7 @@ public class ClassFileMetaData {
|
||||
|
||||
private final byte[] byteCode;
|
||||
|
||||
private final int maxPoolSize;
|
||||
private final int maxPoolSize;
|
||||
private final int[] offsets;
|
||||
private final byte[] types;
|
||||
private final String[] utf8s;
|
||||
@@ -138,6 +142,18 @@ public class ClassFileMetaData {
|
||||
return new String(result, 0, length);
|
||||
}
|
||||
|
||||
public int[] getOffsets(byte type) {
|
||||
int[] out = new int[types.length];
|
||||
int ptr = 0;
|
||||
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
if (types[i] != type) continue;
|
||||
out[ptr++] = offsets[i];
|
||||
}
|
||||
|
||||
return Arrays.copyOf(out, ptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@@ -457,6 +473,9 @@ public class ClassFileMetaData {
|
||||
return NOT_FOUND;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a 16-bit value at {@code position}
|
||||
*/
|
||||
private int readValue(int position) {
|
||||
return ((byteCode[position] & 0xFF) << 8) | (byteCode[position + 1] & 0xFF);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,482 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2019 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.launch;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utility to read the constant pool, header, and inheritance information of any class file.
|
||||
*
|
||||
* THIS IS A COPY OF lombok.bytecode.ClassFileMetaData - it is used by the shadowclassloader which can't rely on anything from outside the lombok.launch package, but everything in the launch package has to be package private.
|
||||
* If there is a bug or an update is needed here - make sure to also update it in the lombok.bytecode package.
|
||||
*/
|
||||
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 DYNAMIC = 17;
|
||||
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:
|
||||
case 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
|
||||
char[] result = new char[size];
|
||||
int length = 0;
|
||||
while (pos < end) {
|
||||
int first = (byteCode[pos++] & 0xFF);
|
||||
if (first < 0x80) {
|
||||
result[length++] = (char)first;
|
||||
} else if ((first & 0xE0) == 0xC0) {
|
||||
int x = (first & 0x1F) << 6;
|
||||
int y = (byteCode[pos++] & 0x3F);
|
||||
result[length++] = (char)(x | y);
|
||||
} else {
|
||||
int x = (first & 0x0F) << 12;
|
||||
int y = (byteCode[pos++] & 0x3F) << 6;
|
||||
int z = (byteCode[pos++] & 0x3F);
|
||||
result[length++] = (char)(x | y | z);
|
||||
}
|
||||
}
|
||||
return new String(result, 0, length);
|
||||
}
|
||||
|
||||
public int[] getOffsets(byte type) {
|
||||
int[] out = new int[types.length];
|
||||
int ptr = 0;
|
||||
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
if (types[i] != type) continue;
|
||||
out[ptr++] = offsets[i];
|
||||
}
|
||||
|
||||
return Arrays.copyOf(out, ptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 <em>NOT</em> 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}.<br />
|
||||
* 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<String> getInterfaces() {
|
||||
int size = readValue(endOfPool + 6);
|
||||
if (size == 0) return Collections.emptyList();
|
||||
|
||||
List<String> result = new ArrayList<String>();
|
||||
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.<br />
|
||||
* 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 DYNAMIC:
|
||||
result.append("Dynamic...");
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a 16-bit value at {@code position}
|
||||
*/
|
||||
private int readValue(int position) {
|
||||
return ((byteCode[position] & 0xFF) << 8) | (byteCode[position + 1] & 0xFF);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package lombok.launch;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* Low-level shader tool, that inline 'rewrites' bytecode so that a given package name acts like it has a different name.
|
||||
*
|
||||
* This is useful particularly in light of the java module system which disallows 'split packages' - different loader systems / class file sources
|
||||
* that both have the same package. Generally you'd take one of your dependency libraries (such as lombok's dependency on ObjectWeb's ASM bytecode parser) and
|
||||
* rename that dependency to avoid conflicts with other code that also wants to use ASM.
|
||||
*
|
||||
* This shader doesn't use ASM or any other bytecode manip library; it just scans constant pools.
|
||||
*
|
||||
* It's limited in its capabilities: A shading op (rename 'this' package to 'that') requires same-length strings so that the replacement can be done without moving things around.
|
||||
*/
|
||||
class PackageShader {
|
||||
private final byte[][] froms; // contains package prefixes that need shading.
|
||||
private final byte[][] tos; // contains the post-shading package prefixes. froms.length == tos.length.
|
||||
|
||||
/**
|
||||
* @param shadeOps provide pairs of strings; each pair is a from,to tuple listing binary package prefixes, e.g. "Lorg/objectweb/asm", "Lorg/lombokweb/asm".
|
||||
*/
|
||||
public PackageShader(String... shadeOps) {
|
||||
if (shadeOps.length % 2 != 0) throw new IllegalArgumentException("Provide pairs: real package name to shaded package name (you provided an odd number of strings; even number required)");
|
||||
Charset ascii = Charset.forName("US-ASCII");
|
||||
int len = shadeOps.length / 2;
|
||||
froms = new byte[len][];
|
||||
tos = new byte[len][];
|
||||
for (int i = 0; i < len; i ++) {
|
||||
String in = shadeOps[i << 1];
|
||||
String out = shadeOps[(i << 1) | 1];
|
||||
if (in.contains(".") || out.contains(".")) throw new IllegalArgumentException("Binary name prefixes are required (use slashes and dollars instead of dots to separate type name elements); they look like e.g. 'java/util/'. Violating entry: " + in + " -> " + out);
|
||||
froms[i] = in.getBytes(ascii);
|
||||
tos[i] = out.getBytes(ascii);
|
||||
if (froms[i].length != tos[i].length) throw new IllegalArgumentException("Pair [" + in + " -> " + out + "] is invalid: Both strings must be the same length");
|
||||
}
|
||||
}
|
||||
|
||||
private static final byte CONSTANTPOOLTYPE_UTF8 = 1;
|
||||
/**
|
||||
* Fuxxes the constant pool of the provided bytecode, rewriting any constant pool entries that represent types that start with any of the registered package shade requests to the shaded variant.
|
||||
*
|
||||
* The byte array is modified in-line.
|
||||
*
|
||||
* @param b Bytecode
|
||||
* @return {@code true} if at least one shading modification has been applied.
|
||||
*/
|
||||
public boolean apply(byte[] b) {
|
||||
ClassFileMetaData md = new ClassFileMetaData(b);
|
||||
boolean changes = false;
|
||||
|
||||
// We want to replace the package name but it can occur in the middle of a 'signature' string, e.g. ([I[Ljava/util/List;)Ljava/util/List; - so replace from start and _after_ every capital-L.
|
||||
// 'startPoints' tracks every location inside the constant pool UTF8 value that _could_ contain a fully qualified type name: Position 0 plus after _every_ capital-L in the UTF-8.
|
||||
int[] startPoints = new int[260];
|
||||
int maxStartPoints;
|
||||
|
||||
for (int offset : md.getOffsets(CONSTANTPOOLTYPE_UTF8)) {
|
||||
int len = readValue(b, offset);
|
||||
offset += 2;
|
||||
startPoints[0] = offset;
|
||||
maxStartPoints = 1;
|
||||
|
||||
for (int i = offset; i < offset + len; i++) {
|
||||
if (b[i] == 'L') startPoints[maxStartPoints++] = offset + 1;
|
||||
}
|
||||
|
||||
outer: for (int i = 0; i < froms.length; i++) {
|
||||
for (int startPointIdx = 0; startPointIdx < maxStartPoints; startPointIdx++) {
|
||||
int indexIntoSignature = startPoints[startPointIdx];
|
||||
if (len - indexIntoSignature < froms[i].length) continue;
|
||||
for (int p = 0; p < froms[i].length; p++) {
|
||||
if (b[offset + indexIntoSignature + p] != froms[i][p]) continue outer;
|
||||
}
|
||||
System.arraycopy(tos[i], 0, b, offset + indexIntoSignature, tos[i].length);
|
||||
changes = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a 16-bit value at {@code position}
|
||||
*/
|
||||
private static int readValue(byte[] b, int position) {
|
||||
return ((b[position] & 0xFF) << 8) | (b[position + 1] & 0xFF);
|
||||
}
|
||||
|
||||
public String reverseResourceName(String name) {
|
||||
outer: for (int i = 0; i < tos.length; i++) {
|
||||
int len = tos[i].length;
|
||||
if (name.length() < len) continue;
|
||||
for (int p = 0; p < len; p++) {
|
||||
if (name.charAt(p) != tos[i][p]) continue outer;
|
||||
}
|
||||
try {
|
||||
String out = new String(froms[i], 0, froms[i].length, "US-ASCII") + name.substring(len);
|
||||
return out;
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// Can't happen - US-ASCII is guaranteed by the JVM spec
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
@@ -105,6 +106,9 @@ class ShadowClassLoader extends ClassLoader {
|
||||
|
||||
private final Set<ClassLoader> prependedParentLoaders = Collections.newSetFromMap(new IdentityHashMap<ClassLoader, Boolean>());
|
||||
|
||||
private final PackageShader shader = new PackageShader(
|
||||
"org/objectweb/asm/", "org/lombokweb/asm/");
|
||||
|
||||
public void prependParent(ClassLoader loader) {
|
||||
if (loader == null) return;
|
||||
if (loader == getParent()) return;
|
||||
@@ -470,7 +474,9 @@ class ShadowClassLoader extends ClassLoader {
|
||||
|
||||
private URL getResource_(String name, boolean noSuper) {
|
||||
String altName = null;
|
||||
name = shader.reverseResourceName(name);
|
||||
if (name.endsWith(".class")) altName = name.substring(0, name.length() - 6) + ".SCL." + sclSuffix;
|
||||
|
||||
for (File ce : override) {
|
||||
URL url = getResourceFromLocation(name, altName, ce);
|
||||
if (url != null) return url;
|
||||
@@ -542,7 +548,7 @@ class ShadowClassLoader extends ClassLoader {
|
||||
if (res == null) {
|
||||
if (!exclusionListMatch(fileNameOfClass)) {
|
||||
try {
|
||||
// First search in the prepended classloaders, the class might be their already
|
||||
// First search in the prepended classloaders, the class might be there already
|
||||
for (ClassLoader pre : prependedParentLoaders) {
|
||||
try {
|
||||
Class<?> loadClass = pre.loadClass(name);
|
||||
@@ -589,6 +595,8 @@ class ShadowClassLoader extends ClassLoader {
|
||||
throw new ClassNotFoundException("I/O exception reading class " + name, e);
|
||||
}
|
||||
|
||||
shader.apply(b);
|
||||
|
||||
Class<?> c;
|
||||
try {
|
||||
c = defineClass(name, b, 0, p);
|
||||
|
||||
Reference in new issue
Block a user