Skip to content

Commit

Permalink
Switch from hacking EnumHelper private apis to a java 9+ compatible r…
Browse files Browse the repository at this point in the history
…eflection/unsafe approach (#19)

* Switch from hacking EnumHelper private apis to a java 9+ compatible reflection/unsafe approach

* Switch golem enum creation to an asm accessor for java 12+ compat

* Update buildscript

* Apply spotless
  • Loading branch information
eigenraven authored Jan 29, 2023
1 parent 2e5dcb6 commit ac35f7a
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 39 deletions.
33 changes: 31 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//version: 1674943145
//version: 1674988119
/*
DO NOT CHANGE THIS FILE!
Also, you may replace this file at any time if there is an update available.
Expand Down Expand Up @@ -66,7 +66,7 @@ plugins {
id 'com.diffplug.spotless' version '6.7.2' apply false
id 'com.modrinth.minotaur' version '2.+' apply false
id 'com.matthewprenger.cursegradle' version '1.4.0' apply false
id 'com.gtnewhorizons.retrofuturagradle' version '1.0.16'
id 'com.gtnewhorizons.retrofuturagradle' version '1.0.18'
}
boolean settingsupdated = verifySettingsGradle()
settingsupdated = verifyGitAttributes() || settingsupdated
Expand Down Expand Up @@ -212,6 +212,25 @@ if (accessTransformersFile) {
}
tasks.deobfuscateMergedJarToSrg.accessTransformerFiles.from(targetFile)
tasks.srgifyBinpatchedJar.accessTransformerFiles.from(targetFile)
} else {
boolean atsFound = false
for (File at : sourceSets.getByName("main").resources.files) {
if (at.name.toLowerCase().endsWith("_at.cfg")) {
atsFound = true
tasks.deobfuscateMergedJarToSrg.accessTransformerFiles.from(at)
tasks.srgifyBinpatchedJar.accessTransformerFiles.from(at)
}
}
for (File at : sourceSets.getByName("api").resources.files) {
if (at.name.toLowerCase().endsWith("_at.cfg")) {
atsFound = true
tasks.deobfuscateMergedJarToSrg.accessTransformerFiles.from(at)
tasks.srgifyBinpatchedJar.accessTransformerFiles.from(at)
}
}
if (atsFound) {
logger.warn("Found and added access transformers in the resources folder, please configure gradle.properties to explicitly mention them by name")
}
}

if (usesMixins.toBoolean()) {
Expand Down Expand Up @@ -356,6 +375,16 @@ configurations.configureEach {
}
}

// Ensure tests have access to minecraft classes
sourceSets {
test {
java {
compileClasspath += sourceSets.patchedMc.output + sourceSets.mcLauncher.output
runtimeClasspath += sourceSets.patchedMc.output + sourceSets.mcLauncher.output
}
}
}

if (file('addon.gradle').exists()) {
apply from: 'addon.gradle'
}
Expand Down
62 changes: 37 additions & 25 deletions src/main/java/makeo/gadomancy/common/utils/GolemEnumHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,26 @@ public class GolemEnumHelper {
private static final RemovedGolemType REMOVED_GOLEM_TYPE = new RemovedGolemType();
private static final Injector INJECTOR = new Injector(EnumGolemType.class);
private static final Injector ENUM_INJECTOR = new Injector(Enum.class);
private static final Injector HELPER_INJECTOR = new Injector(EnumHelper.class);
private static final Injector CLASS_INJECTOR = new Injector(Class.class);

private static Field valuesField;

private static Field getValuesField() {
if (GolemEnumHelper.valuesField == null) {
for (Field field : EnumGolemType.class.getDeclaredFields()) {
String name = field.getName();
if (name.equals("$VALUES") || name.equals("ENUM$VALUES")) // Added 'ENUM$VALUES' because Eclipse's
// internal compiler doesn't
// follow standards
{
if (field.getType().equals(EnumGolemType[].class)) {
field.setAccessible(true);
GolemEnumHelper.valuesField = field;
GolemEnumHelper.valuesField.setAccessible(true);
break;
return field;
}
}
throw new IllegalStateException("Couldn't find the values$ field of EnumGolemType");
}
return GolemEnumHelper.valuesField;
}

private static final Class[] ENUM_PARAMS = { int.class, int.class, float.class, boolean.class, int.class, int.class,
int.class, int.class };
private static final Class<?>[] ENUM_PARAMS = { String.class, int.class, int.class, int.class, float.class,
boolean.class, int.class, int.class, int.class, int.class };

private static Field ordinalField;

Expand All @@ -62,30 +59,41 @@ public static Field getOrdinalField() {
return GolemEnumHelper.ordinalField;
}

private static final Class[] MAKE_ENUM_PARAMS = { Class.class, String.class, int.class, Class[].class,
Object[].class };

private static EnumGolemType createEnum(String name, int ordinal, AdditionalGolemType type) {
return GolemEnumHelper.HELPER_INJECTOR.invokeMethod(
"makeEnum",
GolemEnumHelper.MAKE_ENUM_PARAMS,
EnumGolemType.class,
name,
ordinal,
GolemEnumHelper.ENUM_PARAMS,
new Object[] { type.maxHealth, type.armor, type.movementSpeed, type.fireResist, type.upgradeAmount,
type.carryLimit, type.regenDelay, type.strength });
resetEnumCache();
return INJECTOR.invokeUnsafeConstructor(
ENUM_PARAMS,
new Object[] { name, ordinal, type.maxHealth, type.armor, type.movementSpeed, type.fireResist,
type.upgradeAmount, type.carryLimit, type.regenDelay, type.strength });
}

private static void resetEnumCache() {
CLASS_INJECTOR.setObject(EnumGolemType.class);
try {
Field enumConstants = EnumGolemType.class.getClass().getDeclaredField("enumConstants");
CLASS_INJECTOR.setField(enumConstants, null);
} catch (Exception e) {
// no-op, worst case there is an outdated cache on new JVM versions
}
try {
Field enumConstantDirectory = EnumGolemType.class.getClass().getDeclaredField("enumConstantDirectory");
CLASS_INJECTOR.setField(enumConstantDirectory, null);
} catch (Exception e) {
// no-op, worst case there is an outdated cache on new JVM versions
}
}

private static void addEnum(int ordinal, EnumGolemType type) {
EnumGolemType[] values = GolemEnumHelper.resizeValues(ordinal + 1);
values[ordinal] = type;
resetEnumCache();
}

private static EnumGolemType addEnum(String name, int ordinal, AdditionalGolemType type) {
EnumGolemType enumEntry = GolemEnumHelper.createEnum(name, ordinal, type);
GolemEnumHelper.addEnum(ordinal, enumEntry);
type.setEnumEntry(enumEntry);
resetEnumCache();
return enumEntry;
}

Expand All @@ -99,6 +107,7 @@ private static EnumGolemType[] resizeValues(int size) {
newValues[i] = GolemEnumHelper.createEnum("REMOVED", i, GolemEnumHelper.REMOVED_GOLEM_TYPE);
}
GolemEnumHelper.setValues(newValues);
resetEnumCache();
return newValues;
}
return values;
Expand All @@ -107,6 +116,7 @@ private static EnumGolemType[] resizeValues(int size) {
private static void setValues(EnumGolemType[] values) {
try {
EnumHelper.setFailsafeFieldValue(GolemEnumHelper.getValuesField(), null, values);
resetEnumCache();
} catch (Exception ignored) {}
}

Expand Down Expand Up @@ -154,13 +164,14 @@ public static void reorderEnum(Map<String, Integer> mapping) {
for (EnumGolemType type : oldValues) {
if (type.name().equals(entry.getKey())) {
GolemEnumHelper.ENUM_INJECTOR.setObject(type);
GolemEnumHelper.ENUM_INJECTOR.setField(GolemEnumHelper.getOrdinalField(), entry.getValue());
GolemEnumHelper.ENUM_INJECTOR.setFieldInt(GolemEnumHelper.getOrdinalField(), entry.getValue());
GolemEnumHelper.addEnum(entry.getValue(), type);
}
}
}

new Injector(EnumGolemType.class).setField("codeToTypeMapping", null);
resetEnumCache();
if (Gadomancy.proxy.getSide() == Side.CLIENT) {
ResourceReloadListener.getInstance().reloadGolemResources();
}
Expand All @@ -170,6 +181,7 @@ private static void resetEnum() {
EnumGolemType[] newValues = Arrays
.copyOfRange(EnumGolemType.values(), 0, GolemEnumHelper.calcDefaultGolemCount());
GolemEnumHelper.setValues(newValues);
resetEnumCache();
}

private static int calcDefaultGolemCount() {
Expand All @@ -193,11 +205,11 @@ public static void validateSavedMapping() {
}
}

private static Map<String, Integer> defaultMapping = new HashMap<String, Integer>();
private static Map<String, Integer> defaultMapping = new HashMap<>();

public static Map<String, Integer> getCurrentMapping() {
if (GolemEnumHelper.hasCurrentMapping()) {
return Gadomancy.getModData().get("GolemTypeMapping", new HashMap<String, Integer>());
return Gadomancy.getModData().get("GolemTypeMapping", new HashMap<>());
} else if (Gadomancy.getModData() != null) {
Gadomancy.getModData().set("GolemTypeMapping", GolemEnumHelper.defaultMapping);
}
Expand Down
81 changes: 70 additions & 11 deletions src/main/java/makeo/gadomancy/common/utils/Injector.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import java.lang.reflect.Modifier;
import java.util.Arrays;

import sun.misc.Unsafe;

import com.google.common.base.Throwables;
import cpw.mods.fml.relauncher.ReflectionHelper;

/**
Expand All @@ -16,10 +19,22 @@
*/
public class Injector {

Class clazz;
static final Unsafe UNSAFE;

static {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
UNSAFE = (Unsafe) theUnsafe.get(null);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}

Class<?> clazz;
Object object;

public Injector(Object object, Class clazz) {
public Injector(Object object, Class<?> clazz) {
this.object = object;
this.clazz = clazz;
}
Expand All @@ -32,7 +47,7 @@ public Injector(Object object) {
this(object, object.getClass());
}

public Injector(Class clazz) {
public Injector(Class<?> clazz) {
this.object = null;
this.clazz = clazz;
}
Expand All @@ -46,11 +61,11 @@ public Injector(String clazz) throws IllegalArgumentException {
}
}

public void setObjectClass(Class clazz) {
public void setObjectClass(Class<?> clazz) {
this.clazz = clazz;
}

public Class getObjectClass() {
public Class<?> getObjectClass() {
return this.clazz;
}

Expand All @@ -66,13 +81,13 @@ public <T> T invokeConstructor(Object... params) {
return this.invokeConstructor(this.extractClasses(params), params);
}

public <T> T invokeConstructor(Class clazz, Object param) {
public <T> T invokeConstructor(Class<?> clazz, Object param) {
return this.invokeConstructor(new Class[] { clazz }, param);
}

public <T> T invokeConstructor(Class[] classes, Object... params) {
public <T> T invokeConstructor(Class<?>[] classes, Object... params) {
try {
Constructor constructor = this.clazz.getDeclaredConstructor(classes);
Constructor<?> constructor = this.clazz.getDeclaredConstructor(classes);
this.object = constructor.newInstance(params);
return (T) this.object;
} catch (Exception e) { // NoSuchMethodException | InvocationTargetException | InstantiationException |
Expand All @@ -82,6 +97,17 @@ public <T> T invokeConstructor(Class[] classes, Object... params) {
return null;
}

public <T> T invokeUnsafeConstructor(Class<?>[] paramTypes, Object... params) {
try {
final Method constructor = this.clazz.getMethod("gadomancyRawCreate", paramTypes);
Object created = constructor.invoke(null, params);
return (T) created;
} catch (Throwable e) {
Throwables.propagate(e);
}
throw new IllegalStateException();
}

public <T> T invokeMethod(String name, Object... params) {
return this.invokeMethod(name, this.extractClasses(params), params);
}
Expand Down Expand Up @@ -129,9 +155,17 @@ public boolean setField(String name, Object value) {
public boolean setField(Field field, Object value) {
try {
if (Modifier.isFinal(field.getModifiers())) {
Field modifiers = Field.class.getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
if (value != null && !field.getType().isAssignableFrom(value.getClass())) {
throw new ClassCastException("Can't assign " + value.getClass() + " to " + field.getType());
}
Object base = object;
if (object == null) {
base = UNSAFE.staticFieldBase(field);
}
final long offset = Modifier.isStatic(field.getModifiers()) ? UNSAFE.staticFieldOffset(field)
: UNSAFE.objectFieldOffset(field);
UNSAFE.putObject(base, offset, value);
return true;
}

field.setAccessible(true);
Expand All @@ -143,6 +177,31 @@ public boolean setField(Field field, Object value) {
}
}

public boolean setFieldInt(Field field, int value) {
try {
if (Modifier.isFinal(field.getModifiers())) {
if (!field.getType().equals(int.class)) {
throw new ClassCastException("Can't assign int to " + field.getType());
}
Object base = object;
if (object == null) {
base = UNSAFE.staticFieldBase(field);
}
final long offset = Modifier.isStatic(field.getModifiers()) ? UNSAFE.staticFieldOffset(field)
: UNSAFE.objectFieldOffset(field);
UNSAFE.putInt(base, offset, value);
return true;
}

field.setAccessible(true);
field.setInt(this.object, value);
return true;
} catch (Exception e) { // IllegalAccessException | NoSuchFieldException
e.printStackTrace();
return false;
}
}

public <T> T getField(String name) {
try {
return this.getField(this.clazz.getDeclaredField(name));
Expand Down
Loading

0 comments on commit ac35f7a

Please sign in to comment.