|
20 | 20 | import java.lang.classfile.ClassModel; |
21 | 21 | import java.lang.classfile.ClassTransform; |
22 | 22 | import java.lang.classfile.instruction.InvokeInstruction; |
| 23 | +import java.lang.reflect.AccessFlag; |
23 | 24 | import lombok.NonNull; |
24 | 25 | import org.jetbrains.annotations.ApiStatus; |
25 | 26 |
|
26 | 27 | /** |
27 | | - * A transformer implementation that removes the paperclip call to get the parent class loader of the paperclip class |
28 | | - * which results in the bootstrap class loader which does not have the files from the class path. |
| 28 | + * A transformer that removes the {@code ClassLoader.getParent()} call from the paperclip main method, as it results in |
| 29 | + * the paper main class being loaded by the bootstrap class loader. This then causes, for example, plugins to not have |
| 30 | + * access to the wrapper classes (which reside on the class path and therefore on the system class loader). |
29 | 31 | * |
30 | 32 | * @since 4.0 |
31 | 33 | */ |
32 | 34 | @ApiStatus.Internal |
33 | | -public class PaperclipClassLoaderTransformer implements ClassTransformer { |
| 35 | +public final class PaperclipClassLoaderTransformer implements ClassTransformer { |
34 | 36 |
|
35 | 37 | private static final String MAIN_METHOD_NAME = "main"; |
36 | 38 | private static final String CLASS_LOADER_GET_PARENT_METHOD = "getParent"; |
37 | 39 | private static final String CLASS_LOADER_INTERNAL_NAME = "java/lang/ClassLoader"; |
38 | | - private static final String PAPERCLIP_MAIN_CLASS = "io/papermc/paperclip/Paperclip"; |
| 40 | + private static final String PAPERCLIP_INDICATOR_METHOD = "extractAndApplyPatches"; |
| 41 | + |
| 42 | + // need to check by package and not by exact class name, as the real paperclip main class is just |
| 43 | + // a dummy implementation to check if the correct java version for the paper version is being used |
| 44 | + private final String mainClassPackage; |
39 | 45 |
|
40 | 46 | /** |
41 | 47 | * Constructs a new instance of this transformer, usually done via SPI. |
42 | 48 | */ |
43 | 49 | public PaperclipClassLoaderTransformer() { |
44 | | - // used by SPI |
| 50 | + this.mainClassPackage = findPaperclipMainClassPackage(); |
| 51 | + } |
| 52 | + |
| 53 | + /** |
| 54 | + * Finds the package where the main class of paperclip might be located in. Throws an exception in case no paperclip |
| 55 | + * is on the classpath or the main class package cannot be resolved. |
| 56 | + * |
| 57 | + * @return the package name of the paperclip main class. |
| 58 | + * @throws RuntimeException if no paperclip main package can be resolved. |
| 59 | + */ |
| 60 | + private static @NonNull String findPaperclipMainClassPackage() { |
| 61 | + // check for the 'patches.list' file included in modern paperclip (since 1.18) |
| 62 | + // previously paperclip used instrumentation to add the paper jar to the system cl, making this transform obsolete |
| 63 | + var hasPatchesList = ClassLoader.getSystemClassLoader().getResource("META-INF/patches.list") != null; |
| 64 | + if (hasPatchesList) { |
| 65 | + // 'sun.java.command' holds the main class and arguments to supply to the main class, we use this |
| 66 | + // property to resolve the main class that is being invoked by the jvm |
| 67 | + var mainClassAndArgs = System.getProperty("sun.java.command", ""); |
| 68 | + var mainClass = mainClassAndArgs.split(" ")[0]; |
| 69 | + if (!mainClass.isBlank()) { |
| 70 | + var internalMainClassName = mainClass.replace('.', '/'); |
| 71 | + var lastPackageDelimiter = internalMainClassName.lastIndexOf('/'); |
| 72 | + if (lastPackageDelimiter != -1) { |
| 73 | + return internalMainClassName.substring(0, lastPackageDelimiter); |
| 74 | + } |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + throw new RuntimeException("not running paperclip or main class not found"); |
45 | 79 | } |
46 | 80 |
|
47 | 81 | /** |
48 | 82 | * {@inheritDoc} |
49 | 83 | */ |
50 | 84 | @Override |
51 | 85 | public @NonNull TransformWillingness classTransformWillingness(@NonNull String internalClassName) { |
52 | | - return internalClassName.equals(PAPERCLIP_MAIN_CLASS) |
53 | | - ? TransformWillingness.ACCEPT_ONCE |
54 | | - : TransformWillingness.REJECT; |
| 86 | + var lastPkgDelimIdx = internalClassName.lastIndexOf('/'); |
| 87 | + if (lastPkgDelimIdx == this.mainClassPackage.length() && internalClassName.startsWith(this.mainClassPackage)) { |
| 88 | + return TransformWillingness.ACCEPT; |
| 89 | + } |
| 90 | + |
| 91 | + return TransformWillingness.REJECT; |
55 | 92 | } |
56 | 93 |
|
57 | 94 | /** |
58 | 95 | * {@inheritDoc} |
59 | 96 | */ |
60 | 97 | @Override |
61 | 98 | public @NonNull ClassTransform provideClassTransform(@NonNull ClassModel original) { |
| 99 | + var hasPaperclipMainClassIndicatorMethod = original.methods() |
| 100 | + .stream() |
| 101 | + .anyMatch(method -> method.methodName().equalsString(PAPERCLIP_INDICATOR_METHOD)); |
| 102 | + if (!hasPaperclipMainClassIndicatorMethod) { |
| 103 | + return ClassTransform.ACCEPT_ALL; |
| 104 | + } |
| 105 | + |
62 | 106 | return ClassTransform.transformingMethodBodies( |
63 | | - methodModel -> methodModel.methodName().equalsString(MAIN_METHOD_NAME), |
| 107 | + methodModel -> methodModel.flags().has(AccessFlag.STATIC) |
| 108 | + && methodModel.methodName().equalsString(MAIN_METHOD_NAME), |
64 | 109 | (builder, element) -> { |
65 | | - if (element instanceof InvokeInstruction invoke) { |
66 | | - if (invoke.method().name().equalsString(CLASS_LOADER_GET_PARENT_METHOD) |
67 | | - && invoke.owner().asInternalName().equals(CLASS_LOADER_INTERNAL_NAME)) { |
68 | | - return; |
69 | | - } |
| 110 | + if (element instanceof InvokeInstruction invokeInst |
| 111 | + && invokeInst.owner().asInternalName().equals(CLASS_LOADER_INTERNAL_NAME) |
| 112 | + && invokeInst.method().name().equalsString(CLASS_LOADER_GET_PARENT_METHOD)) { |
| 113 | + return; |
70 | 114 | } |
71 | 115 |
|
72 | 116 | builder.with(element); |
|
0 commit comments