Mirror von
https://github.com/ViaVersion/ViaVersion.git
synchronisiert 2024-12-26 00:00:28 +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)
|
||||
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.platform.ViaInjector;
|
||||
import us.myles.ViaVersion.bungee.handlers.BungeeChannelInitializer;
|
||||
import us.myles.ViaVersion.compatibility.FieldModifierAccessor;
|
||||
import us.myles.ViaVersion.compatibility.JavaVersionIdentifier;
|
||||
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.compatibility.ForcefulFieldModifier;
|
||||
import us.myles.ViaVersion.compatibility.unsafe.UnsafeBackedForcefulFieldModifier;
|
||||
import us.myles.ViaVersion.util.ReflectionUtil;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.List;
|
||||
|
||||
public class BungeeViaInjector implements ViaInjector {
|
||||
|
||||
private final FieldModifierAccessor fieldModifierAccessor;
|
||||
private final ForcefulFieldModifier forcefulFieldModifier;
|
||||
|
||||
public BungeeViaInjector() {
|
||||
FieldModifierAccessor fieldModifierAccessor = null;
|
||||
try {
|
||||
if (JavaVersionIdentifier.IS_JAVA_16) {
|
||||
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();
|
||||
}
|
||||
this.forcefulFieldModifier = new UnsafeBackedForcefulFieldModifier();
|
||||
} catch (final ReflectiveOperationException ex) {
|
||||
throw new IllegalStateException("Cannot create a modifier accessor", ex);
|
||||
}
|
||||
|
||||
// Must be non-null by now.
|
||||
this.fieldModifierAccessor = fieldModifierAccessor;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -78,14 +51,9 @@ public class BungeeViaInjector implements ViaInjector {
|
||||
Field field = pipelineUtils.getDeclaredField("SERVER_CHILD");
|
||||
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));
|
||||
field.set(null, newInit);
|
||||
|
||||
this.forcefulFieldModifier.setField(field, null, newInit);
|
||||
} 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.");
|
||||
throw e;
|
||||
|
@ -1,12 +1,4 @@
|
||||
dependencies {
|
||||
api(projects.javaCompat.javaCompatCommon)
|
||||
api(projects.javaCompat.javaCompat8)
|
||||
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()
|
||||
api(projects.javaCompat.javaCompatUnsafe)
|
||||
}
|
||||
|
@ -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")
|
||||
|
||||
include("adventure")
|
||||
include("java-compat", "java-compat:java-compat-common", "java-compat:java-compat-8",
|
||||
"java-compat:java-compat-9", "java-compat:java-compat-16")
|
||||
include("java-compat", "java-compat:java-compat-common", "java-compat:java-compat-unsafe")
|
||||
|
||||
setupViaSubproject("api")
|
||||
setupViaSubproject("common")
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren