Feat (Bungee): Use only unsafe field modifications (#2440)

This commit is contained in:
Mariell Hoversholm 2021-04-17 09:59:24 +02:00 committed by GitHub
parent d0882cf02c
commit 2c884dc241
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 66 additions and 221 deletions

View File

@ -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()
}

View File

@ -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;

View File

@ -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)
}

View File

@ -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)

View File

@ -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);
}
}

View File

@ -1,5 +0,0 @@
dependencies {
api(projects.javaCompat.javaCompatCommon)
}
configureJavaTarget(8)

View File

@ -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);
}
}

View File

@ -1,5 +0,0 @@
dependencies {
api(projects.javaCompat.javaCompatCommon)
}
configureJavaTarget(9)

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -0,0 +1,3 @@
dependencies {
api(project(":java-compat:java-compat-common"))
}

View File

@ -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);
}
}

View File

@ -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")