Skip to content

Commit 4b3e489

Browse files
0utplayderklaro
andauthored
fix: improve paperclip indicator checks to support e.g. leafmc (#1847)
### Motivation With #1839 the wrapper was converted into a java agent which resulted in a regression regarding leafmc support. ### Modification Instead of strictly checking on the paperclip package & class name we now only check on an existing version list (used by paperclip) and the existance of a method called `extractAndApplyPatches` inside a class in the same package as the main class is located in. ### Result Paperclip, Leaf's QuantumLeaper and possible other paperclip variants are supported. ##### Other context Results from this conversation https://discord.com/channels/325362837184577536/818777626663321671/1465822591880003787 --------- Co-authored-by: Pasqual Koschmieder <git@derklaro.dev>
1 parent 347ad6f commit 4b3e489

File tree

1 file changed

+58
-14
lines changed

1 file changed

+58
-14
lines changed

wrapper-jvm/impl/src/main/java/eu/cloudnetservice/wrapper/impl/transform/bukkit/PaperclipClassLoaderTransformer.java

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,53 +20,97 @@
2020
import java.lang.classfile.ClassModel;
2121
import java.lang.classfile.ClassTransform;
2222
import java.lang.classfile.instruction.InvokeInstruction;
23+
import java.lang.reflect.AccessFlag;
2324
import lombok.NonNull;
2425
import org.jetbrains.annotations.ApiStatus;
2526

2627
/**
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).
2931
*
3032
* @since 4.0
3133
*/
3234
@ApiStatus.Internal
33-
public class PaperclipClassLoaderTransformer implements ClassTransformer {
35+
public final class PaperclipClassLoaderTransformer implements ClassTransformer {
3436

3537
private static final String MAIN_METHOD_NAME = "main";
3638
private static final String CLASS_LOADER_GET_PARENT_METHOD = "getParent";
3739
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;
3945

4046
/**
4147
* Constructs a new instance of this transformer, usually done via SPI.
4248
*/
4349
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");
4579
}
4680

4781
/**
4882
* {@inheritDoc}
4983
*/
5084
@Override
5185
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;
5592
}
5693

5794
/**
5895
* {@inheritDoc}
5996
*/
6097
@Override
6198
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+
62106
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),
64109
(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;
70114
}
71115

72116
builder.with(element);

0 commit comments

Comments
 (0)