Mirror von
https://github.com/ViaVersion/ViaVersion.git
synchronisiert 2024-12-26 16:12:42 +01:00
Fix (Bungee): Java 16 compatibility (#2433)
This has been tested on the following: - AdoptOpenJDK Java 1.8.0_282 - GraalVM CE 21.0.0 OpenJDK 11.0.10 - AdoptOpenJDK Java 15.0.2 - AdoptOpenJDK Java 16 (also tested with BungeeCord b1556) - Amazon Corretto OpenJDK 16.0.0.36.1 ... with Waterfall b406 on Linux 5.10.28.
Dieser Commit ist enthalten in:
Ursprung
7300a69817
Commit
458279d111
@ -1,4 +1,11 @@
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":viaversion-common"))
|
implementation(project(":viaversion-common"))
|
||||||
|
implementation(project(":java-compat"))
|
||||||
compileOnly("net.md-5", "bungeecord-api", Versions.bungee)
|
compileOnly("net.md-5", "bungeecord-api", Versions.bungee)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
configure<JavaPluginConvention> {
|
||||||
|
// This is necessary to allow compilation for Java 8 while still including
|
||||||
|
// newer Java versions in the code.
|
||||||
|
disableAutoTargetJvm()
|
||||||
|
}
|
||||||
|
@ -25,15 +25,52 @@ 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.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.util.ReflectionUtil;
|
import us.myles.ViaVersion.util.ReflectionUtil;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.lang.reflect.Modifier;
|
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;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
} catch (final ReflectiveOperationException ex) {
|
||||||
|
throw new IllegalStateException("Cannot create a modifier accessor", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be non-null by now.
|
||||||
|
this.fieldModifierAccessor = fieldModifierAccessor;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void inject() throws Exception {
|
public void inject() throws Exception {
|
||||||
try {
|
try {
|
||||||
@ -42,26 +79,9 @@ public class BungeeViaInjector implements ViaInjector {
|
|||||||
field.setAccessible(true);
|
field.setAccessible(true);
|
||||||
|
|
||||||
// Remove the final modifier (unless removed by a fork)
|
// Remove the final modifier (unless removed by a fork)
|
||||||
//TODO Fix Java 16 compatibility
|
|
||||||
int modifiers = field.getModifiers();
|
int modifiers = field.getModifiers();
|
||||||
if (Modifier.isFinal(modifiers)) {
|
if (Modifier.isFinal(modifiers)) {
|
||||||
try {
|
this.fieldModifierAccessor.setModifiers(field, modifiers & ~Modifier.FINAL);
|
||||||
Field modifiersField = Field.class.getDeclaredField("modifiers");
|
|
||||||
modifiersField.setAccessible(true);
|
|
||||||
modifiersField.setInt(field, modifiers & ~Modifier.FINAL);
|
|
||||||
} catch (NoSuchFieldException e) {
|
|
||||||
// Java 12 compatibility *this is fine*
|
|
||||||
Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
|
|
||||||
getDeclaredFields0.setAccessible(true);
|
|
||||||
Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false);
|
|
||||||
for (Field classField : fields) {
|
|
||||||
if ("modifiers".equals(classField.getName())) {
|
|
||||||
classField.setAccessible(true);
|
|
||||||
classField.set(field, modifiers & ~Modifier.FINAL);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BungeeChannelInitializer newInit = new BungeeChannelInitializer((ChannelInitializer<Channel>) field.get(null));
|
BungeeChannelInitializer newInit = new BungeeChannelInitializer((ChannelInitializer<Channel>) field.get(null));
|
||||||
|
12
java-compat/build.gradle.kts
Normale Datei
12
java-compat/build.gradle.kts
Normale Datei
@ -0,0 +1,12 @@
|
|||||||
|
dependencies {
|
||||||
|
api(project(":java-compat:java-compat-common"))
|
||||||
|
api(project(":java-compat:java-compat-8"))
|
||||||
|
api(project(":java-compat:java-compat-9"))
|
||||||
|
api(project(":java-compat:java-compat-16"))
|
||||||
|
}
|
||||||
|
|
||||||
|
configure<JavaPluginConvention> {
|
||||||
|
// This is necessary to allow compilation for Java 8 while still including
|
||||||
|
// newer Java versions in the code.
|
||||||
|
disableAutoTargetJvm()
|
||||||
|
}
|
10
java-compat/java-compat-16/build.gradle.kts
Normale Datei
10
java-compat/java-compat-16/build.gradle.kts
Normale Datei
@ -0,0 +1,10 @@
|
|||||||
|
dependencies {
|
||||||
|
api(project(":java-compat:java-compat-common"))
|
||||||
|
}
|
||||||
|
|
||||||
|
configure<JavaPluginConvention> {
|
||||||
|
// This is for Java 16, but the minimum required for this
|
||||||
|
// is actually just Java 9!
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_9
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_9
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
3
java-compat/java-compat-8/build.gradle.kts
Normale Datei
3
java-compat/java-compat-8/build.gradle.kts
Normale Datei
@ -0,0 +1,3 @@
|
|||||||
|
dependencies {
|
||||||
|
api(project(":java-compat:java-compat-common"))
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
8
java-compat/java-compat-9/build.gradle.kts
Normale Datei
8
java-compat/java-compat-9/build.gradle.kts
Normale Datei
@ -0,0 +1,8 @@
|
|||||||
|
dependencies {
|
||||||
|
api(project(":java-compat:java-compat-common"))
|
||||||
|
}
|
||||||
|
|
||||||
|
configure<JavaPluginConvention> {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_9
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_9
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
0
java-compat/java-compat-common/build.gradle.kts
Normale Datei
0
java-compat/java-compat-common/build.gradle.kts
Normale Datei
@ -0,0 +1,26 @@
|
|||||||
|
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,41 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
rootProject.name = "viaversion-parent"
|
rootProject.name = "viaversion-parent"
|
||||||
|
|
||||||
include("adventure")
|
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")
|
||||||
|
|
||||||
setupViaSubproject("api")
|
setupViaSubproject("api")
|
||||||
setupViaSubproject("common")
|
setupViaSubproject("common")
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren