Mirror von
https://github.com/ViaVersion/ViaVersion.git
synchronisiert 2024-12-27 08:30:09 +01:00
Feat (Bungee): Use only unsafe field modifications (#2440)
Dieser Commit ist enthalten in:
Ursprung
d0882cf02c
Commit
2c884dc241
@ -3,9 +3,3 @@ dependencies {
|
|||||||
implementation(projects.javaCompat)
|
implementation(projects.javaCompat)
|
||||||
compileOnly(libs.bungee)
|
compileOnly(libs.bungee)
|
||||||
}
|
}
|
||||||
|
|
||||||
configure<JavaPluginConvention> {
|
|
||||||
// This is necessary to allow compilation for Java 8 while still including
|
|
||||||
// newer Java versions in the code.
|
|
||||||
disableAutoTargetJvm()
|
|
||||||
}
|
|
||||||
|
@ -25,50 +25,23 @@ import it.unimi.dsi.fastutil.ints.IntSortedSet;
|
|||||||
import us.myles.ViaVersion.api.Via;
|
import us.myles.ViaVersion.api.Via;
|
||||||
import us.myles.ViaVersion.api.platform.ViaInjector;
|
import us.myles.ViaVersion.api.platform.ViaInjector;
|
||||||
import us.myles.ViaVersion.bungee.handlers.BungeeChannelInitializer;
|
import us.myles.ViaVersion.bungee.handlers.BungeeChannelInitializer;
|
||||||
import us.myles.ViaVersion.compatibility.FieldModifierAccessor;
|
import us.myles.ViaVersion.compatibility.ForcefulFieldModifier;
|
||||||
import us.myles.ViaVersion.compatibility.JavaVersionIdentifier;
|
import us.myles.ViaVersion.compatibility.unsafe.UnsafeBackedForcefulFieldModifier;
|
||||||
import us.myles.ViaVersion.compatibility.jre16.Jre16FieldModifierAccessor;
|
|
||||||
import us.myles.ViaVersion.compatibility.jre8.Jre8FieldModifierAccessor;
|
|
||||||
import us.myles.ViaVersion.compatibility.jre9.Jre9FieldModifierAccessor;
|
|
||||||
import us.myles.ViaVersion.util.ReflectionUtil;
|
import us.myles.ViaVersion.util.ReflectionUtil;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class BungeeViaInjector implements ViaInjector {
|
public class BungeeViaInjector implements ViaInjector {
|
||||||
|
|
||||||
private final FieldModifierAccessor fieldModifierAccessor;
|
private final ForcefulFieldModifier forcefulFieldModifier;
|
||||||
|
|
||||||
public BungeeViaInjector() {
|
public BungeeViaInjector() {
|
||||||
FieldModifierAccessor fieldModifierAccessor = null;
|
|
||||||
try {
|
try {
|
||||||
if (JavaVersionIdentifier.IS_JAVA_16) {
|
this.forcefulFieldModifier = new UnsafeBackedForcefulFieldModifier();
|
||||||
fieldModifierAccessor = new Jre16FieldModifierAccessor();
|
|
||||||
} else if (JavaVersionIdentifier.IS_JAVA_9) {
|
|
||||||
fieldModifierAccessor = new Jre9FieldModifierAccessor();
|
|
||||||
}
|
|
||||||
} catch (final ReflectiveOperationException outer) {
|
|
||||||
try {
|
|
||||||
fieldModifierAccessor = new Jre8FieldModifierAccessor();
|
|
||||||
Via.getPlatform().getLogger().warning("Had to fall back to the Java 8 field modifier accessor.");
|
|
||||||
outer.printStackTrace();
|
|
||||||
} catch (final ReflectiveOperationException inner) {
|
|
||||||
inner.addSuppressed(outer);
|
|
||||||
throw new IllegalStateException("Cannot create a modifier accessor", inner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (fieldModifierAccessor == null) {
|
|
||||||
fieldModifierAccessor = new Jre8FieldModifierAccessor();
|
|
||||||
}
|
|
||||||
} catch (final ReflectiveOperationException ex) {
|
} catch (final ReflectiveOperationException ex) {
|
||||||
throw new IllegalStateException("Cannot create a modifier accessor", ex);
|
throw new IllegalStateException("Cannot create a modifier accessor", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must be non-null by now.
|
|
||||||
this.fieldModifierAccessor = fieldModifierAccessor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -78,14 +51,9 @@ public class BungeeViaInjector implements ViaInjector {
|
|||||||
Field field = pipelineUtils.getDeclaredField("SERVER_CHILD");
|
Field field = pipelineUtils.getDeclaredField("SERVER_CHILD");
|
||||||
field.setAccessible(true);
|
field.setAccessible(true);
|
||||||
|
|
||||||
// Remove the final modifier (unless removed by a fork)
|
|
||||||
int modifiers = field.getModifiers();
|
|
||||||
if (Modifier.isFinal(modifiers)) {
|
|
||||||
this.fieldModifierAccessor.setModifiers(field, modifiers & ~Modifier.FINAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
BungeeChannelInitializer newInit = new BungeeChannelInitializer((ChannelInitializer<Channel>) field.get(null));
|
BungeeChannelInitializer newInit = new BungeeChannelInitializer((ChannelInitializer<Channel>) field.get(null));
|
||||||
field.set(null, newInit);
|
|
||||||
|
this.forcefulFieldModifier.setField(field, null, newInit);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Via.getPlatform().getLogger().severe("Unable to inject ViaVersion, please post these details on our GitHub and ensure you're using a compatible server version.");
|
Via.getPlatform().getLogger().severe("Unable to inject ViaVersion, please post these details on our GitHub and ensure you're using a compatible server version.");
|
||||||
throw e;
|
throw e;
|
||||||
|
@ -1,12 +1,4 @@
|
|||||||
dependencies {
|
dependencies {
|
||||||
api(projects.javaCompat.javaCompatCommon)
|
api(projects.javaCompat.javaCompatCommon)
|
||||||
api(projects.javaCompat.javaCompat8)
|
api(projects.javaCompat.javaCompatUnsafe)
|
||||||
api(projects.javaCompat.javaCompat9)
|
|
||||||
api(projects.javaCompat.javaCompat16)
|
|
||||||
}
|
|
||||||
|
|
||||||
configure<JavaPluginConvention> {
|
|
||||||
// This is necessary to allow compilation for Java 8 while still including
|
|
||||||
// newer Java versions in the code.
|
|
||||||
disableAutoTargetJvm()
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
dependencies {
|
|
||||||
api(projects.javaCompat.javaCompatCommon)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is for Java 16, but the minimum required for this
|
|
||||||
// is actually just Java 9!
|
|
||||||
configureJavaTarget(9)
|
|
@ -1,35 +0,0 @@
|
|||||||
package us.myles.ViaVersion.compatibility.jre16;
|
|
||||||
|
|
||||||
import us.myles.ViaVersion.compatibility.FieldModifierAccessor;
|
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandles;
|
|
||||||
import java.lang.invoke.VarHandle;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
@SuppressWarnings({
|
|
||||||
"java:S1191", // SonarLint/-Qube/-Cloud: We (sadly) need Unsafe for the Java 16 impl.
|
|
||||||
"java:S3011", // ^: We need to circumvent the access restrictions of fields.
|
|
||||||
})
|
|
||||||
public final class Jre16FieldModifierAccessor implements FieldModifierAccessor {
|
|
||||||
private final VarHandle modifiersHandle;
|
|
||||||
|
|
||||||
public Jre16FieldModifierAccessor() throws ReflectiveOperationException {
|
|
||||||
final Field theUnsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
|
|
||||||
theUnsafeField.setAccessible(true);
|
|
||||||
final sun.misc.Unsafe unsafe = (sun.misc.Unsafe) theUnsafeField.get(null);
|
|
||||||
|
|
||||||
final Field trustedLookup = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
|
|
||||||
final MethodHandles.Lookup lookup = (MethodHandles.Lookup) unsafe.getObject(
|
|
||||||
unsafe.staticFieldBase(trustedLookup), unsafe.staticFieldOffset(trustedLookup));
|
|
||||||
|
|
||||||
this.modifiersHandle = lookup.findVarHandle(Field.class, "modifiers", int.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setModifiers(final Field field, final int modifiers) {
|
|
||||||
Objects.requireNonNull(field, "field must not be null");
|
|
||||||
|
|
||||||
this.modifiersHandle.set(field, modifiers);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
dependencies {
|
|
||||||
api(projects.javaCompat.javaCompatCommon)
|
|
||||||
}
|
|
||||||
|
|
||||||
configureJavaTarget(8)
|
|
@ -1,23 +0,0 @@
|
|||||||
package us.myles.ViaVersion.compatibility.jre8;
|
|
||||||
|
|
||||||
import us.myles.ViaVersion.compatibility.FieldModifierAccessor;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
@SuppressWarnings("java:S3011") // SonarLint/-Qube/-Cloud: we are intentionally bypassing the setter.
|
|
||||||
public final class Jre8FieldModifierAccessor implements FieldModifierAccessor {
|
|
||||||
private final Field modifiersField;
|
|
||||||
|
|
||||||
public Jre8FieldModifierAccessor() throws ReflectiveOperationException {
|
|
||||||
this.modifiersField = Field.class.getDeclaredField("modifiers");
|
|
||||||
this.modifiersField.setAccessible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setModifiers(final Field field, final int modifiers) throws ReflectiveOperationException {
|
|
||||||
Objects.requireNonNull(field, "field must not be null");
|
|
||||||
|
|
||||||
this.modifiersField.setInt(field, modifiers);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
dependencies {
|
|
||||||
api(projects.javaCompat.javaCompatCommon)
|
|
||||||
}
|
|
||||||
|
|
||||||
configureJavaTarget(9)
|
|
@ -1,24 +0,0 @@
|
|||||||
package us.myles.ViaVersion.compatibility.jre9;
|
|
||||||
|
|
||||||
import us.myles.ViaVersion.compatibility.FieldModifierAccessor;
|
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandles;
|
|
||||||
import java.lang.invoke.VarHandle;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public final class Jre9FieldModifierAccessor implements FieldModifierAccessor {
|
|
||||||
private final VarHandle modifiersHandle;
|
|
||||||
|
|
||||||
public Jre9FieldModifierAccessor() throws ReflectiveOperationException {
|
|
||||||
final MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
|
|
||||||
this.modifiersHandle = lookup.findVarHandle(Field.class, "modifiers", int.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setModifiers(final Field field, final int modifiers) {
|
|
||||||
Objects.requireNonNull(field, "field must not be null");
|
|
||||||
|
|
||||||
this.modifiersHandle.set(field, modifiers);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
package us.myles.ViaVersion.compatibility;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exposes a way to access the modifiers of a {@link Field} mutably.
|
|
||||||
* <p>
|
|
||||||
* <i>Note:</i> This is <b>explicitly</b> an implementation detail. Do not rely on this within plugins and any
|
|
||||||
* non-ViaVersion code.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public interface FieldModifierAccessor {
|
|
||||||
/**
|
|
||||||
* Sets the modifiers of a field.
|
|
||||||
* <p>
|
|
||||||
* <i>Note:</i> This does not set the accessibility of the field. If you need to read or mutate it, you must handle
|
|
||||||
* that yourself.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param field the field to set the modifiers of. Will throw if {@code null}.
|
|
||||||
* @param modifiers the modifiers to set on the given {@code field}.
|
|
||||||
* @throws ReflectiveOperationException if the reflective operation fails this method is implemented with fails.
|
|
||||||
*/
|
|
||||||
void setModifiers(final Field field, final int modifiers)
|
|
||||||
throws ReflectiveOperationException;
|
|
||||||
}
|
|
@ -0,0 +1,25 @@
|
|||||||
|
package us.myles.ViaVersion.compatibility;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exposes a way to modify a {@link Field}, regardless of its limitations (given it is accessible by the caller).
|
||||||
|
* <p>
|
||||||
|
* <i>Note:</i> This is <b>explicitly</b> an implementation detail. Do not rely on this within plugins and any
|
||||||
|
* non-ViaVersion code.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public interface ForcefulFieldModifier {
|
||||||
|
/**
|
||||||
|
* Sets the field regardless of field finality.
|
||||||
|
* <p>
|
||||||
|
* <i>Note:</i> This does not set the accessibility of the field.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param field the field to set the modifiers of. Will throw if {@code null}.
|
||||||
|
* @param holder the eye of the beholder. For static fields, use {@code null}.
|
||||||
|
* @param object the new value to set of the object.
|
||||||
|
*/
|
||||||
|
void setField(final Field field, final Object holder, final Object object)
|
||||||
|
throws ReflectiveOperationException;
|
||||||
|
}
|
@ -1,41 +0,0 @@
|
|||||||
package us.myles.ViaVersion.compatibility;
|
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
public enum JavaVersionIdentifier {
|
|
||||||
;
|
|
||||||
|
|
||||||
public static final boolean IS_JAVA_9;
|
|
||||||
public static final boolean IS_JAVA_16;
|
|
||||||
|
|
||||||
static {
|
|
||||||
// Optional<T>#stream()Stream<T> is marked `@since 9`.
|
|
||||||
IS_JAVA_9 = doesMethodExist(Optional.class, "stream");
|
|
||||||
|
|
||||||
// Stream<T>#toList()List<T> is marked `@since 16`.
|
|
||||||
IS_JAVA_16 = doesMethodExist(Stream.class, "toList");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the given name of a {@link Method} exists on the given {@link Class} without comparing parameters or
|
|
||||||
* other parts of the descriptor. The method must be public and declared on the given class.
|
|
||||||
* <p>
|
|
||||||
* <i>Note:</i> This should only check for stable methods that are expected to stay permanently.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param clazz the type to get the given {@code method} on.
|
|
||||||
* @param method the name to find.
|
|
||||||
* @return whether the given method exists.
|
|
||||||
*/
|
|
||||||
private static boolean doesMethodExist(final Class<?> clazz, final String method) {
|
|
||||||
for (final Method reflect : clazz.getMethods()) {
|
|
||||||
if (reflect.getName().equals(method)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
3
java-compat/java-compat-unsafe/build.gradle.kts
Normale Datei
3
java-compat/java-compat-unsafe/build.gradle.kts
Normale Datei
@ -0,0 +1,3 @@
|
|||||||
|
dependencies {
|
||||||
|
api(project(":java-compat:java-compat-common"))
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package us.myles.ViaVersion.compatibility.unsafe;
|
||||||
|
|
||||||
|
import us.myles.ViaVersion.compatibility.ForcefulFieldModifier;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@SuppressWarnings({
|
||||||
|
"java:S1191", // SonarLint/-Qube/-Cloud: We need Unsafe for the modifier implementation.
|
||||||
|
"java:S3011", // ^: We need to circumvent the access restrictions of fields.
|
||||||
|
})
|
||||||
|
public final class UnsafeBackedForcefulFieldModifier implements ForcefulFieldModifier {
|
||||||
|
private final sun.misc.Unsafe unsafe;
|
||||||
|
|
||||||
|
public UnsafeBackedForcefulFieldModifier() throws ReflectiveOperationException {
|
||||||
|
final Field theUnsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
|
||||||
|
theUnsafeField.setAccessible(true);
|
||||||
|
this.unsafe = (sun.misc.Unsafe) theUnsafeField.get(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setField(final Field field, final Object holder, final Object object) {
|
||||||
|
Objects.requireNonNull(field, "field must not be null");
|
||||||
|
|
||||||
|
final Object ufo = holder != null ? holder : this.unsafe.staticFieldBase(field);
|
||||||
|
final long offset = holder != null ? this.unsafe.objectFieldOffset(field) : this.unsafe.staticFieldOffset(field);
|
||||||
|
|
||||||
|
this.unsafe.putObject(ufo, offset, object);
|
||||||
|
}
|
||||||
|
}
|
@ -4,8 +4,7 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
|||||||
enableFeaturePreview("VERSION_CATALOGS")
|
enableFeaturePreview("VERSION_CATALOGS")
|
||||||
|
|
||||||
include("adventure")
|
include("adventure")
|
||||||
include("java-compat", "java-compat:java-compat-common", "java-compat:java-compat-8",
|
include("java-compat", "java-compat:java-compat-common", "java-compat:java-compat-unsafe")
|
||||||
"java-compat:java-compat-9", "java-compat:java-compat-16")
|
|
||||||
|
|
||||||
setupViaSubproject("api")
|
setupViaSubproject("api")
|
||||||
setupViaSubproject("common")
|
setupViaSubproject("common")
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren