diff --git a/bungee/build.gradle.kts b/bungee/build.gradle.kts index 12469ce8c..e205d25d1 100644 --- a/bungee/build.gradle.kts +++ b/bungee/build.gradle.kts @@ -3,9 +3,3 @@ dependencies { implementation(projects.javaCompat) compileOnly(libs.bungee) } - -configure { - // This is necessary to allow compilation for Java 8 while still including - // newer Java versions in the code. - disableAutoTargetJvm() -} diff --git a/bungee/src/main/java/us/myles/ViaVersion/bungee/platform/BungeeViaInjector.java b/bungee/src/main/java/us/myles/ViaVersion/bungee/platform/BungeeViaInjector.java index 49e6b85bb..ce4183388 100644 --- a/bungee/src/main/java/us/myles/ViaVersion/bungee/platform/BungeeViaInjector.java +++ b/bungee/src/main/java/us/myles/ViaVersion/bungee/platform/BungeeViaInjector.java @@ -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) 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; diff --git a/java-compat/build.gradle.kts b/java-compat/build.gradle.kts index da0e6981f..25b99b661 100644 --- a/java-compat/build.gradle.kts +++ b/java-compat/build.gradle.kts @@ -1,12 +1,4 @@ dependencies { api(projects.javaCompat.javaCompatCommon) - api(projects.javaCompat.javaCompat8) - api(projects.javaCompat.javaCompat9) - api(projects.javaCompat.javaCompat16) -} - -configure { - // This is necessary to allow compilation for Java 8 while still including - // newer Java versions in the code. - disableAutoTargetJvm() + api(projects.javaCompat.javaCompatUnsafe) } diff --git a/java-compat/java-compat-16/build.gradle.kts b/java-compat/java-compat-16/build.gradle.kts deleted file mode 100644 index eb1d78ded..000000000 --- a/java-compat/java-compat-16/build.gradle.kts +++ /dev/null @@ -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) diff --git a/java-compat/java-compat-16/src/main/java/us/myles/ViaVersion/compatibility/jre16/Jre16FieldModifierAccessor.java b/java-compat/java-compat-16/src/main/java/us/myles/ViaVersion/compatibility/jre16/Jre16FieldModifierAccessor.java deleted file mode 100644 index afc4d2c65..000000000 --- a/java-compat/java-compat-16/src/main/java/us/myles/ViaVersion/compatibility/jre16/Jre16FieldModifierAccessor.java +++ /dev/null @@ -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); - } -} diff --git a/java-compat/java-compat-8/build.gradle.kts b/java-compat/java-compat-8/build.gradle.kts deleted file mode 100644 index 5480dcb03..000000000 --- a/java-compat/java-compat-8/build.gradle.kts +++ /dev/null @@ -1,5 +0,0 @@ -dependencies { - api(projects.javaCompat.javaCompatCommon) -} - -configureJavaTarget(8) diff --git a/java-compat/java-compat-8/src/main/java/us/myles/ViaVersion/compatibility/jre8/Jre8FieldModifierAccessor.java b/java-compat/java-compat-8/src/main/java/us/myles/ViaVersion/compatibility/jre8/Jre8FieldModifierAccessor.java deleted file mode 100644 index ac50412ab..000000000 --- a/java-compat/java-compat-8/src/main/java/us/myles/ViaVersion/compatibility/jre8/Jre8FieldModifierAccessor.java +++ /dev/null @@ -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); - } -} diff --git a/java-compat/java-compat-9/build.gradle.kts b/java-compat/java-compat-9/build.gradle.kts deleted file mode 100644 index 9966cb2dc..000000000 --- a/java-compat/java-compat-9/build.gradle.kts +++ /dev/null @@ -1,5 +0,0 @@ -dependencies { - api(projects.javaCompat.javaCompatCommon) -} - -configureJavaTarget(9) diff --git a/java-compat/java-compat-9/src/main/java/us/myles/ViaVersion/compatibility/jre9/Jre9FieldModifierAccessor.java b/java-compat/java-compat-9/src/main/java/us/myles/ViaVersion/compatibility/jre9/Jre9FieldModifierAccessor.java deleted file mode 100644 index 8ca782a69..000000000 --- a/java-compat/java-compat-9/src/main/java/us/myles/ViaVersion/compatibility/jre9/Jre9FieldModifierAccessor.java +++ /dev/null @@ -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); - } -} diff --git a/java-compat/java-compat-common/src/main/java/us/myles/ViaVersion/compatibility/FieldModifierAccessor.java b/java-compat/java-compat-common/src/main/java/us/myles/ViaVersion/compatibility/FieldModifierAccessor.java deleted file mode 100644 index 6c7b4b683..000000000 --- a/java-compat/java-compat-common/src/main/java/us/myles/ViaVersion/compatibility/FieldModifierAccessor.java +++ /dev/null @@ -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. - *

- * Note: This is explicitly an implementation detail. Do not rely on this within plugins and any - * non-ViaVersion code. - *

- */ -public interface FieldModifierAccessor { - /** - * Sets the modifiers of a field. - *

- * Note: This does not set the accessibility of the field. If you need to read or mutate it, you must handle - * that yourself. - *

- * - * @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; -} diff --git a/java-compat/java-compat-common/src/main/java/us/myles/ViaVersion/compatibility/ForcefulFieldModifier.java b/java-compat/java-compat-common/src/main/java/us/myles/ViaVersion/compatibility/ForcefulFieldModifier.java new file mode 100644 index 000000000..ad0ca5368 --- /dev/null +++ b/java-compat/java-compat-common/src/main/java/us/myles/ViaVersion/compatibility/ForcefulFieldModifier.java @@ -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). + *

+ * Note: This is explicitly an implementation detail. Do not rely on this within plugins and any + * non-ViaVersion code. + *

+ */ +public interface ForcefulFieldModifier { + /** + * Sets the field regardless of field finality. + *

+ * Note: This does not set the accessibility of the field. + *

+ * + * @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; +} diff --git a/java-compat/java-compat-common/src/main/java/us/myles/ViaVersion/compatibility/JavaVersionIdentifier.java b/java-compat/java-compat-common/src/main/java/us/myles/ViaVersion/compatibility/JavaVersionIdentifier.java deleted file mode 100644 index 43fa5fbbb..000000000 --- a/java-compat/java-compat-common/src/main/java/us/myles/ViaVersion/compatibility/JavaVersionIdentifier.java +++ /dev/null @@ -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#stream()Stream is marked `@since 9`. - IS_JAVA_9 = doesMethodExist(Optional.class, "stream"); - - // Stream#toList()List 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. - *

- * Note: This should only check for stable methods that are expected to stay permanently. - *

- * - * @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; - } -} diff --git a/java-compat/java-compat-unsafe/build.gradle.kts b/java-compat/java-compat-unsafe/build.gradle.kts new file mode 100644 index 000000000..6b8e88798 --- /dev/null +++ b/java-compat/java-compat-unsafe/build.gradle.kts @@ -0,0 +1,3 @@ +dependencies { + api(project(":java-compat:java-compat-common")) +} diff --git a/java-compat/java-compat-unsafe/src/main/java/us/myles/ViaVersion/compatibility/unsafe/UnsafeBackedForcefulFieldModifier.java b/java-compat/java-compat-unsafe/src/main/java/us/myles/ViaVersion/compatibility/unsafe/UnsafeBackedForcefulFieldModifier.java new file mode 100644 index 000000000..676a25d63 --- /dev/null +++ b/java-compat/java-compat-unsafe/src/main/java/us/myles/ViaVersion/compatibility/unsafe/UnsafeBackedForcefulFieldModifier.java @@ -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); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index a29fef5a6..f19cd4ee3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -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")