-
Notifications
You must be signed in to change notification settings - Fork 273
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
257 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
116 changes: 116 additions & 0 deletions
116
src/main/java/net/glowstone/util/linkstone/LinkstoneClassInitObserver.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package net.glowstone.util.linkstone; | ||
|
||
import net.glowstone.linkstone.annotations.LBox; | ||
import net.glowstone.linkstone.annotations.LField; | ||
import net.glowstone.linkstone.runtime.inithook.ClassInitHook; | ||
import net.glowstone.linkstone.runtime.reflectionredirect.DynamicClassLoader; | ||
import net.glowstone.linkstone.runtime.reflectionredirect.ReflectionUtil; | ||
import net.glowstone.linkstone.runtime.reflectionredirect.field.BoxingFieldAccessor; | ||
import net.glowstone.linkstone.runtime.reflectionredirect.field.FieldAccessorUtility; | ||
import net.glowstone.linkstone.runtime.reflectionredirect.field.LFieldAccessor; | ||
import net.glowstone.linkstone.runtime.reflectionredirect.field.RedirectFieldAccessorGenerator; | ||
import net.glowstone.linkstone.runtime.reflectionredirect.method.BoxingMethodAccessor; | ||
import net.glowstone.linkstone.runtime.reflectionredirect.method.LMethodAccessor; | ||
import net.glowstone.linkstone.runtime.reflectionredirect.method.MethodAccessorUtility; | ||
|
||
import java.lang.reflect.Field; | ||
import java.lang.reflect.Method; | ||
import java.util.Map; | ||
import java.util.WeakHashMap; | ||
|
||
/** | ||
* Utility that redirects reflective uses of annotated fields to their getters and setters. | ||
*/ | ||
public class LinkstoneClassInitObserver implements ClassInitHook.Observer { | ||
private final FieldAccessorUtility fieldAccessorUtil; | ||
private final MethodAccessorUtility methodAccessorUtil; | ||
|
||
private final Map<ClassLoader, DynamicClassLoader> classLoaders = new WeakHashMap<>(); | ||
|
||
/** | ||
* Create a new observer instance. | ||
*/ | ||
public LinkstoneClassInitObserver() { | ||
try { | ||
fieldAccessorUtil = FieldAccessorUtility.isSupported() | ||
? new FieldAccessorUtility() : null; | ||
} catch (Exception t) { | ||
throw new IllegalStateException("Could not initialize FieldAccessorUtility"); | ||
} | ||
|
||
try { | ||
methodAccessorUtil = MethodAccessorUtility.isSupported() | ||
? new MethodAccessorUtility() : null; | ||
} catch (Exception e) { | ||
throw new IllegalStateException("Could not initialize MethodAccessorUtility"); | ||
} | ||
} | ||
|
||
@Override | ||
public void onInit(Class<?> clazz) { | ||
try { | ||
hijackFields(clazz); | ||
hijackMethods(clazz); | ||
} catch (Exception e) { | ||
e.printStackTrace(); | ||
} | ||
} | ||
|
||
private void hijackFields(Class<?> clazz) throws ReflectiveOperationException { | ||
if (fieldAccessorUtil == null) { | ||
return; | ||
} | ||
|
||
boolean isBox = clazz.getAnnotation(LBox.class) != null; | ||
|
||
for (Field field : ReflectionUtil.getInternalFields(clazz)) { | ||
LField[] fieldAnnotations = field.getAnnotationsByType(LField.class); | ||
if (fieldAnnotations.length > 0) { | ||
LFieldAccessor accessor = newRedirectFieldAccessor(field); | ||
fieldAccessorUtil.setAccessor(field, accessor); | ||
fieldAccessorUtil.setOverrideAccessor(field, accessor); | ||
} | ||
|
||
if (isBox) { | ||
LFieldAccessor accessor = fieldAccessorUtil.getAccessor(field); | ||
accessor = new BoxingFieldAccessor(accessor, field); | ||
fieldAccessorUtil.setAccessor(field, accessor); | ||
|
||
LFieldAccessor overrideAccessor = fieldAccessorUtil.getOverrideAccessor(field); | ||
overrideAccessor = new BoxingFieldAccessor(overrideAccessor, field); | ||
fieldAccessorUtil.setOverrideAccessor(field, overrideAccessor); | ||
} | ||
} | ||
} | ||
|
||
private LFieldAccessor newRedirectFieldAccessor(Field field) | ||
throws ReflectiveOperationException { | ||
DynamicClassLoader classloader = classLoaders.computeIfAbsent( | ||
field.getDeclaringClass().getClassLoader(), DynamicClassLoader::new); | ||
|
||
RedirectFieldAccessorGenerator generator = new RedirectFieldAccessorGenerator(field); | ||
String className = generator.getClassName().replace('/', '.'); | ||
|
||
Class<?> accessorClass; | ||
try { | ||
accessorClass = Class.forName(className, false, classloader); | ||
} catch (ClassNotFoundException e) { | ||
byte[] bytecode = generator.generateAccessor(); | ||
accessorClass = classloader.loadBytecode(className, bytecode); | ||
} | ||
|
||
return (LFieldAccessor) accessorClass.getDeclaredConstructor().newInstance(); | ||
} | ||
|
||
private void hijackMethods(Class<?> clazz) throws ReflectiveOperationException { | ||
if (methodAccessorUtil == null || clazz.getAnnotation(LBox.class) == null) { | ||
return; | ||
} | ||
|
||
for (Method method : ReflectionUtil.getInternalMethods(clazz)) { | ||
LMethodAccessor accessor = methodAccessorUtil.getAccessor(method); | ||
accessor = new BoxingMethodAccessor(method, accessor); | ||
methodAccessorUtil.setAccessor(method, accessor); | ||
} | ||
} | ||
} |
63 changes: 63 additions & 0 deletions
63
src/main/java/net/glowstone/util/linkstone/LinkstonePluginLoader.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package net.glowstone.util.linkstone; | ||
|
||
import net.glowstone.linkstone.runtime.LinkstoneRuntimeData; | ||
import net.glowstone.linkstone.runtime.boxing.BoxPatchVisitor; | ||
import net.glowstone.linkstone.runtime.direct.DirectFieldAccessReplaceVisitor; | ||
import net.glowstone.linkstone.runtime.inithook.ClassInitInvokeVisitor; | ||
import net.glowstone.linkstone.runtime.reflectionredirect.field.FieldAccessorUtility; | ||
import net.glowstone.linkstone.runtime.reflectionredirect.method.MethodAccessorUtility; | ||
import net.glowstone.linkstone.runtime.reflectionreplace.ReflectionReplaceVisitor; | ||
import org.bukkit.Server; | ||
import org.bukkit.plugin.PluginDescriptionFile; | ||
import org.bukkit.plugin.java.JavaPluginLoader; | ||
import org.bukkit.plugin.java.PluginClassLoader; | ||
import org.objectweb.asm.ClassReader; | ||
import org.objectweb.asm.ClassVisitor; | ||
import org.objectweb.asm.ClassWriter; | ||
|
||
import java.io.File; | ||
|
||
public class LinkstonePluginLoader extends JavaPluginLoader { | ||
/** | ||
* Bukkit will invoke this constructor via reflection. | ||
* Its signature should therefore not be changed! | ||
* | ||
* @param instance the server instance | ||
*/ | ||
public LinkstonePluginLoader(Server instance) { | ||
super(instance); | ||
LinkstoneRuntimeData.setPluginClassLoader(new ClassLoader() { | ||
@Override | ||
protected Class<?> findClass(String name) throws ClassNotFoundException { | ||
return loadClass(name); | ||
} | ||
}); | ||
} | ||
|
||
@Override | ||
protected PluginClassLoader newPluginLoader(JavaPluginLoader loader, ClassLoader parent, PluginDescriptionFile description, File dataFolder, File file, ClassLoader libraryLoader) throws Exception { | ||
return new PluginClassLoader(loader, parent, description, dataFolder, file, libraryLoader) { | ||
@Override | ||
protected byte[] transformBytecode(byte[] bytecode) { | ||
if (LinkstoneRuntimeData.getFields().isEmpty() | ||
&& LinkstoneRuntimeData.getBoxes().isEmpty()) { | ||
// There are no plugins installed that use a @LField or @LBox annotation | ||
// so there's no need for runtime support | ||
return bytecode; | ||
} | ||
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); | ||
|
||
ClassVisitor cv = cw; | ||
cv = new DirectFieldAccessReplaceVisitor(LinkstoneRuntimeData.getFields(), cv); | ||
if (!FieldAccessorUtility.isSupported() || !MethodAccessorUtility.isSupported()) { | ||
cv = new ReflectionReplaceVisitor(cv); | ||
} | ||
cv = new ClassInitInvokeVisitor(cv); | ||
cv = new BoxPatchVisitor(LinkstoneRuntimeData.getBoxes(), cv); | ||
|
||
new ClassReader(bytecode).accept(cv, 0); | ||
return cw.toByteArray(); | ||
} | ||
}; | ||
} | ||
} |
59 changes: 59 additions & 0 deletions
59
src/main/java/net/glowstone/util/linkstone/LinkstonePluginScanner.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package net.glowstone.util.linkstone; | ||
|
||
import net.glowstone.linkstone.annotations.LField; | ||
import net.glowstone.linkstone.runtime.Boxes; | ||
import net.glowstone.linkstone.runtime.FieldSet; | ||
import net.glowstone.linkstone.runtime.collect.AnnotatedFieldCollectVisitor; | ||
import net.glowstone.linkstone.runtime.collect.BoxCollectVisitor; | ||
import org.objectweb.asm.ClassReader; | ||
import org.objectweb.asm.ClassVisitor; | ||
|
||
import java.io.File; | ||
import java.io.FileInputStream; | ||
import java.io.IOException; | ||
import java.util.List; | ||
import java.util.zip.ZipEntry; | ||
import java.util.zip.ZipInputStream; | ||
|
||
public class LinkstonePluginScanner { | ||
private final FieldSet fields; | ||
private final Boxes boxes; | ||
|
||
public LinkstonePluginScanner(final FieldSet fields, final Boxes boxes) { | ||
this.fields = fields; | ||
this.boxes = boxes; | ||
} | ||
|
||
/** | ||
* Look through a list of plugins jar files and store all | ||
* fields annotated with a {@link LField} annotation. | ||
* | ||
* @param pluginJars list of plugins jars to be scanned | ||
*/ | ||
public void scanPlugins(List<File> pluginJars) { | ||
for (File pluginJar : pluginJars) { | ||
try { | ||
scanPlugin(pluginJar); | ||
} catch (Throwable t) { | ||
t.printStackTrace(); | ||
} | ||
} | ||
} | ||
|
||
private void scanPlugin(File pluginJar) throws IOException { | ||
ZipInputStream zin = new ZipInputStream(new FileInputStream(pluginJar)); | ||
ZipEntry entry; | ||
while ((entry = zin.getNextEntry()) != null) { | ||
if (entry.isDirectory() || !entry.getName().endsWith(".class")) { | ||
continue; | ||
} | ||
|
||
ClassVisitor cv = new AnnotatedFieldCollectVisitor(this.fields); | ||
cv = new BoxCollectVisitor(this.boxes, cv); | ||
|
||
new ClassReader(zin).accept(cv, ClassReader.SKIP_CODE); | ||
zin.closeEntry(); | ||
} | ||
zin.close(); | ||
} | ||
} |