Skip to content

Commit

Permalink
Add java agent to reload mixin
Browse files Browse the repository at this point in the history
  • Loading branch information
skinnyBat committed Oct 19, 2015
1 parent f3c4945 commit a8558bb
Show file tree
Hide file tree
Showing 7 changed files with 322 additions and 14 deletions.
148 changes: 148 additions & 0 deletions src/agent/java/org/spongepowered/tools/agent/MixinAgent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
* This file is part of Mixin, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.spongepowered.tools.agent;

import net.minecraft.launchwrapper.Launch;
import org.spongepowered.asm.mixin.transformer.MixinTransformer;
import org.spongepowered.asm.mixin.transformer.debug.IHotSwap;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.List;

/**
* A java agent that re-transforms a mixin's target class if the mixin has been
* redefined. This agent enables hot-swapping of mixins.
*
* This class is a singleton because it needs to conofrm to the IHotSwap interface.
*/
public class MixinAgent implements IHotSwap{

private class Transformer implements ClassFileTransformer{

@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if(classBeingRedefined == null)
return null;
byte[] mixinBytecode = MixinAgent.classLoader.getOriginalBytecode(classBeingRedefined);
if(mixinBytecode != null){
List<String> targets = MixinAgent.this.classTransformer.reload(className.replace('/', '.'), classfileBuffer);
try {
for (String target:targets) {
instrumentation.retransformClasses(Launch.classLoader.findClass(target));
}
} catch (UnmodifiableClassException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return mixinBytecode;
}
return MixinAgent.this.classTransformer.transform(null, className, classfileBuffer);
}
}

/**
* Class loader used to load mixin classes
*/
private static final MixinClassLoader classLoader = new MixinClassLoader();

/**
* Instance used to register the transformer
*/
private static Instrumentation instrumentation = null;

/**
* Instances of all agents
*/
private static List<MixinAgent> agents = new ArrayList<MixinAgent>();

private final MixinTransformer classTransformer;

/**
* Constructs an agent from a class transformer in which it will use to
* transform the class.
*
* @param classTransformer Class transformer that will transform a mixin's
* target class
*/
public MixinAgent(MixinTransformer classTransformer) {
this.classTransformer = classTransformer;
MixinAgent.agents.add(this);
if (MixinAgent.instrumentation != null)
initTransformer();
}

private void initTransformer() {
MixinAgent.instrumentation.addTransformer(new Transformer());
}

@Override
public void registerMixinClass(String name, byte[] bytecode) {
MixinAgent.classLoader.addMixinClass(name, bytecode);
}

/**
* Initialize the agents
*
* @param instrumentation Instance to use to transform the mixins
*/
public static void init(Instrumentation instrumentation) {
MixinAgent.instrumentation = instrumentation;
for (MixinAgent agent:agents) {
agent.initTransformer();
}
}

/**
* Initialize the agent
*
* This will be called automatically if the jar is in a -javaagent java
* command line argument
*
* @param arg Ignored
* @param instrumentation Instance to use to transform the mixins
*/
public static void premain(String arg, Instrumentation instrumentation){
init(instrumentation);
}

/**
* Initialize the agent
*
* This will be called automatically if the agent is loaded after jvm
* startup
*
* @param arg Ignored
* @param instrumentation Instance to use to transform the mixins
*/
public static void agentmain(String arg, Instrumentation instrumentation) {
init(instrumentation);
}
}
49 changes: 49 additions & 0 deletions src/agent/java/org/spongepowered/tools/agent/MixinClassLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* This file is part of Mixin, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.spongepowered.tools.agent;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.HashMap;
import java.util.Map;

class MixinClassLoader extends ClassLoader {

private static final Logger logger = LogManager.getLogger("mixin.agent");

private Map<Class<?>, byte[]> bytecodes = new HashMap<Class<?>, byte[]>();

void addMixinClass(String name, byte[] bytes){
logger.debug("add class "+name+" to class loader");
try {
logger.catching(e);
}
}

byte[] getOriginalBytecode(Class<?> clazz){
return this.bytecodes.get(clazz);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -290,8 +290,13 @@ public static enum Option {
/**
* Ignore all constraints on mixin annotations, output warnings instead
*/
IGNORE_CONSTRAINTS("ignoreConstraints");

IGNORE_CONSTRAINTS("ignoreConstraints"),

/**
* Enables the hot-swap agent
*/
HOT_SWAP("hotSwap");

/**
* Prefix for mixin options
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import org.spongepowered.asm.mixin.extensibility.IMixinConfig;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.injection.struct.ReferenceMapper;
import org.spongepowered.asm.mixin.transformer.debug.IHotSwap;
import org.spongepowered.asm.util.VersionNumber;

import com.google.gson.Gson;
Expand Down Expand Up @@ -274,20 +275,20 @@ private boolean onLoad(String name) {
* either the <em>hasMixinsFor()</em> or <em>getMixinsFor()</em> methods.
* </p>
*/
void initialise() {
void initialise(IHotSwap hotSwapper) {
if (this.initialised) {
return;
}
this.initialised = true;

this.initialiseMixins(this.mixinClasses, false);
this.initialiseMixins(this.mixinClasses, false, hotSwapper);

switch (MixinEnvironment.getCurrentEnvironment().getSide()) {
case CLIENT:
this.initialiseMixins(this.mixinClassesClient, false);
this.initialiseMixins(this.mixinClassesClient, false, hotSwapper);
break;
case SERVER:
this.initialiseMixins(this.mixinClassesServer, false);
this.initialiseMixins(this.mixinClassesServer, false, hotSwapper);
break;
case UNKNOWN:
//$FALL-THROUGH$
Expand All @@ -297,10 +298,10 @@ void initialise() {
}
}

void postInitialise() {
void postInitialise(IHotSwap hotSwapper) {
if (this.plugin != null) {
List<String> pluginMixins = this.plugin.getMixins();
this.initialiseMixins(pluginMixins, true);
this.initialiseMixins(pluginMixins, true, hotSwapper);
}

for (Iterator<MixinInfo> iter = this.mixins.iterator(); iter.hasNext();) {
Expand Down Expand Up @@ -328,7 +329,7 @@ private void removeMixin(MixinInfo remove) {
}
}

private void initialiseMixins(List<String> mixinClasses, boolean suppressPlugin) {
private void initialiseMixins(List<String> mixinClasses, boolean suppressPlugin, IHotSwap hotSwapper) {
if (mixinClasses == null) {
return;
}
Expand All @@ -349,6 +350,7 @@ private void initialiseMixins(List<String> mixinClasses, boolean suppressPlugin)
this.mixinsFor(targetClassName).add(mixin);
this.unhandledTargets.add(targetClassName);
}
hotSwapper.registerMixinClass(mixin.getClassName(), mixin.getClassBytes());
this.mixins.add(mixin);
}
} catch (Exception ex) {
Expand Down Expand Up @@ -504,6 +506,25 @@ private List<MixinInfo> mixinsFor(String targetClass) {
}
return mixins;
}

public List<String> reloadMixin(String mixinClass, byte[] bytes) {
for (Iterator<MixinInfo> iter = this.mixins.iterator(); iter.hasNext();) {
MixinInfo mixin = iter.next();
if (mixin.getClassName().equals(mixinClass)) {
mixin.reloadMixin(bytes);
try {
mixin.validate();
} catch (Exception e) {
this.logger.error(e.getMessage(), e);
this.removeMixin(mixin);
iter.remove();
return Collections.<String>emptyList();
}
return mixin.getTargetClasses();
}
}
return null;
}

@Override
public String toString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ class MixinInfo extends TreeInfo implements Comparable<MixinInfo>, IMixinInfo {
/**
* Mixin bytes (read once, generate tree on demand)
*/
private final transient byte[] mixinBytes;
private transient byte[] mixinBytes;

/**
* Configuration plugin
Expand Down Expand Up @@ -539,6 +539,11 @@ private byte[] loadMixinClass(String mixinClassName, boolean runTransformers) th
return mixinBytes;
}

void reloadMixin(byte[] mixinBytes) {
this.mixinBytes = mixinBytes;
this.validationClassNode = getClassNode(0);
}

/* (non-Javadoc)
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
Expand Down
Loading

0 comments on commit a8558bb

Please sign in to comment.