Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
ee1ab4191c | |||
![]() |
58982c8f99 | ||
![]() |
531a5ffe0c | ||
![]() |
dd0748a52c | ||
![]() |
ffcd11f05b | ||
![]() |
a1bda63564 | ||
![]() |
a13c241f47 | ||
![]() |
baf2739bf5 | ||
![]() |
98b3b7cb3b | ||
47723fccfb | |||
e4a8470ecf | |||
758a94f02d | |||
092976ee5c |
65
README.md
65
README.md
@ -31,16 +31,24 @@ methods and fields.
|
|||||||
|
|
||||||
* Inject fields
|
* Inject fields
|
||||||
|
|
||||||
|
* Handle exceptions
|
||||||
|
|
||||||
|
* Inject try-catch-finally
|
||||||
|
|
||||||
|
* Inject assertions
|
||||||
|
|
||||||
|
*A caveat regarding assert-statements: the compiler synthesizes a static final field named `$assertionsDisabled`, so if a target
|
||||||
|
class declares a static field with this name and does not declare any assertions in its code, loading of the field may already
|
||||||
|
be done in static initialization or field declaration, preventing assertions from functioning as intended for injected code.*
|
||||||
|
|
||||||
### TODO
|
### TODO
|
||||||
|
|
||||||
* Better tests
|
* Better tests
|
||||||
|
|
||||||
* Execution path optimization (e.g. remove unnecessary [GOTO](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.goto)s)
|
* ~~Implement subroutines ([JSR](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.jsr) / [RET](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.ret))~~
|
||||||
|
|
||||||
* Implement exceptions
|
*Note: Subroutines are prohibited as of class file version 51, so unless this project sees much use with projects targeting Java
|
||||||
|
version 1.6 or lower on very specific compilers, this implementation can be set aside until everything else is polished.*
|
||||||
* Implement try-catch
|
|
||||||
* Implement subroutines ([JSR](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.jsr) / [RET](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.ret))
|
|
||||||
|
|
||||||
## How do I use this?
|
## How do I use this?
|
||||||
|
|
||||||
@ -90,6 +98,10 @@ public class TweakFoo {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If this looks a bit confusing: don't worry, [it gets simpler](#a-simple-example) after we remove all the things that can
|
||||||
|
be inferred automatically by the library. We start with a more complicated example to show explicitly what's happening
|
||||||
|
under the hood in case it's needed in more complex use-cases.
|
||||||
|
|
||||||
We specify using the `@Inject` annotation that we would like to inject the annotated method into the targeted class (
|
We specify using the `@Inject` annotation that we would like to inject the annotated method into the targeted class (
|
||||||
we'll get to how we target a class in a sec), that we would like to append the code `AFTER` the existing code, that we
|
we'll get to how we target a class in a sec), that we would like to append the code `AFTER` the existing code, that we
|
||||||
are targeting a method named `addMyNumber` which accepts an int (specified by `(I)`) and returns an int (specified by
|
are targeting a method named `addMyNumber` which accepts an int (specified by `(I)`) and returns an int (specified by
|
||||||
@ -143,12 +155,11 @@ To target a class, simply create an instance of the `dev.w1zzrd.asm.Combine` cla
|
|||||||
annotated MethodNodes. For example, using the example code described earlier, we could do as follows:
|
annotated MethodNodes. For example, using the example code described earlier, we could do as follows:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
import dev.w1zzrd.asm.Combine;
|
import dev.w1zzrd.asm.*;
|
||||||
import dev.w1zzrd.asm.GraftSource;
|
import jdk.internal.org.objectweb.asm.tree.MethodNode;
|
||||||
import dev.w1zzrd.asm.Loader;
|
|
||||||
|
|
||||||
public class Run {
|
public class Run {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) throws Exception {
|
||||||
// This is the class we would like to inject code into
|
// This is the class we would like to inject code into
|
||||||
Combine target = new Combine(Loader.getClassNode("Foo"));
|
Combine target = new Combine(Loader.getClassNode("Foo"));
|
||||||
|
|
||||||
@ -184,7 +195,7 @@ available to the default ClassLoader's classpath. In this case, we can simply do
|
|||||||
import dev.w1zzrd.asm.Injector;
|
import dev.w1zzrd.asm.Injector;
|
||||||
|
|
||||||
public class Run {
|
public class Run {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) throws Exception {
|
||||||
// Locates all necessary tweaks and injects them into Foo
|
// Locates all necessary tweaks and injects them into Foo
|
||||||
Injector.injectAll("Foo").compile();
|
Injector.injectAll("Foo").compile();
|
||||||
|
|
||||||
@ -200,9 +211,7 @@ The only caveat is that classes injected this way must have an `@InjectClass` an
|
|||||||
In our example, this would mean that the `TweakFoo` class would look as follows:
|
In our example, this would mean that the `TweakFoo` class would look as follows:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
import dev.w1zzrd.asm.InPlaceInjection;
|
import dev.w1zzrd.asm.*;
|
||||||
import dev.w1zzrd.asm.Inject;
|
|
||||||
import dev.w1zzrd.asm.InjectClass;
|
|
||||||
|
|
||||||
// This marks the class as targeting Foo
|
// This marks the class as targeting Foo
|
||||||
@InjectClass(Foo.class)
|
@InjectClass(Foo.class)
|
||||||
@ -217,4 +226,32 @@ public class TweakFoo {
|
|||||||
|
|
||||||
Additionally, method resolution is relatively intelligent, so one can omit the `target` parameter of the `@Inject`
|
Additionally, method resolution is relatively intelligent, so one can omit the `target` parameter of the `@Inject`
|
||||||
annotation in cases where the target is unambiguous. As long as the tweak method has the same name as the targeted
|
annotation in cases where the target is unambiguous. As long as the tweak method has the same name as the targeted
|
||||||
method, the target should never be ambiguous.
|
method, the target should never be ambiguous. In fact, the resolution is intelligent enough that if the method signature
|
||||||
|
is unambiguous for the targeted method name in the targeted class, the `acceptOriginalReturn` value can even be omitted.
|
||||||
|
This means that a minimal example implementation of the `TweakFoo` class could look as follows:
|
||||||
|
|
||||||
|
#### A simple example:
|
||||||
|
```java
|
||||||
|
import dev.w1zzrd.asm.*;
|
||||||
|
import static dev.w1zzrd.asm.InPlaceInjection.AFTER;
|
||||||
|
|
||||||
|
@InjectClass(Foo.class)
|
||||||
|
public class TweakFoo {
|
||||||
|
@Inject(AFTER)
|
||||||
|
public int addMyNumber(int addTo, int ret) {
|
||||||
|
System.out.println(addTo);
|
||||||
|
return ret / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Priority
|
||||||
|
|
||||||
|
In the case where multiple injections are to be made into one method (e.g. `BEFORE` and `AFTER`), it may be useful to
|
||||||
|
have a clear order in which the injections should occur. As such, the `priority` value of an `@Inject` annotation can be
|
||||||
|
passed to specify which value to inject first. Methods with a lower priority value will be injected earlier than ones
|
||||||
|
with higher values. By default, methods have a priority of hex 0x7FFFFFFF (maximum int value). Methods with the same
|
||||||
|
target and same priority have no guarantees on injection order. I.e. if two methods annotated with `@Inject` target the
|
||||||
|
same method and declare the same priority, there are no guarantees on which one will be injected first. Thus, if a
|
||||||
|
method is targeted for injection by multiple tweak methods, it is highly recommended that an explicit priority be
|
||||||
|
declared for at least all except one tweak method.
|
@ -11,8 +11,6 @@ import jdk.internal.org.objectweb.asm.Handle;
|
|||||||
import jdk.internal.org.objectweb.asm.Opcodes;
|
import jdk.internal.org.objectweb.asm.Opcodes;
|
||||||
import jdk.internal.org.objectweb.asm.Type;
|
import jdk.internal.org.objectweb.asm.Type;
|
||||||
import jdk.internal.org.objectweb.asm.tree.*;
|
import jdk.internal.org.objectweb.asm.tree.*;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@ -21,6 +19,10 @@ import java.util.stream.Collectors;
|
|||||||
import static jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_MAXS;
|
import static jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_MAXS;
|
||||||
|
|
||||||
public class Combine {
|
public class Combine {
|
||||||
|
public static final String VAR_ASSERT_NAME = "$assertionsDisabled";
|
||||||
|
public static final int VAR_ASSERT_FLAGS = Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_FINAL;
|
||||||
|
|
||||||
|
|
||||||
private final ArrayList<DynamicSourceUnit> graftSources = new ArrayList<>();
|
private final ArrayList<DynamicSourceUnit> graftSources = new ArrayList<>();
|
||||||
|
|
||||||
private final ClassNode target;
|
private final ClassNode target;
|
||||||
@ -33,8 +35,6 @@ public class Combine {
|
|||||||
public void inject(MethodNode node, GraftSource source) {
|
public void inject(MethodNode node, GraftSource source) {
|
||||||
final AsmAnnotation<Inject> annotation = source.getMethodInjectAnnotation(node);
|
final AsmAnnotation<Inject> annotation = source.getMethodInjectAnnotation(node);
|
||||||
|
|
||||||
final boolean acceptReturn = annotation.getEntry("acceptOriginalReturn");
|
|
||||||
|
|
||||||
switch ((InPlaceInjection)annotation.getEnumEntry("value")) {
|
switch ((InPlaceInjection)annotation.getEnumEntry("value")) {
|
||||||
case INSERT: // Explicitly insert a *new* method
|
case INSERT: // Explicitly insert a *new* method
|
||||||
insert(node, source);
|
insert(node, source);
|
||||||
@ -51,7 +51,7 @@ public class Combine {
|
|||||||
finishGrafting(node, source);
|
finishGrafting(node, source);
|
||||||
break;
|
break;
|
||||||
case AFTER: // Inject a method's instructions after the original instructions in a given method
|
case AFTER: // Inject a method's instructions after the original instructions in a given method
|
||||||
append(node, source, acceptReturn);
|
append(node, source);
|
||||||
break;
|
break;
|
||||||
case BEFORE: // Inject a method's instructions before the original instructions in a given method
|
case BEFORE: // Inject a method's instructions before the original instructions in a given method
|
||||||
prepend(node, source);
|
prepend(node, source);
|
||||||
@ -65,22 +65,22 @@ public class Combine {
|
|||||||
* return value from the original node as the "argument" to the grafted node.
|
* return value from the original node as the "argument" to the grafted node.
|
||||||
* @param extension Node to extend method with
|
* @param extension Node to extend method with
|
||||||
* @param source The {@link GraftSource} from which the method node will be adapted
|
* @param source The {@link GraftSource} from which the method node will be adapted
|
||||||
* @param acceptReturn Whether or not the grafted method should "receive" the original method's return value as an "argument"
|
|
||||||
*/
|
*/
|
||||||
public void append(MethodNode extension, GraftSource source, boolean acceptReturn) {
|
public void append(MethodNode extension, GraftSource source) {
|
||||||
if (initiateGrafting(extension, source))
|
if (initiateGrafting(extension, source))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
final MethodNode target = resolveMethod(extension, source, true);
|
final MethodResolution resolution = resolveMethod(extension, source, true);
|
||||||
|
boolean acceptReturn = resolution.acceptReturn;
|
||||||
adaptMethod(extension, source);
|
adaptMethod(extension, source);
|
||||||
|
|
||||||
// Get the method signatures so we know what we're working with local-variable-wise ;)
|
// Get the method signatures so we know what we're working with local-variable-wise ;)
|
||||||
final MethodSignature msig = new MethodSignature(target.desc);
|
final MethodSignature msig = new MethodSignature(resolution.node.desc);
|
||||||
final MethodSignature xsig = new MethodSignature(extension.desc);
|
final MethodSignature xsig = new MethodSignature(extension.desc);
|
||||||
|
|
||||||
// Get total argument count, including implicit "this" argument
|
// Get total argument count, including implicit "this" argument
|
||||||
final int graftArgCount = xsig.getArgCount() + (isStatic(extension) ? 0 : 1);
|
final int graftArgCount = xsig.getArgCount() + (isStatic(extension) ? 0 : 1);
|
||||||
final int targetArgCount = msig.getArgCount() + (isStatic(target) ? 0 : 1);
|
final int targetArgCount = msig.getArgCount() + (isStatic(resolution.node) ? 0 : 1);
|
||||||
|
|
||||||
// If graft method cares about the return value of the original method, i.e. accepts it as an extra "argument"
|
// If graft method cares about the return value of the original method, i.e. accepts it as an extra "argument"
|
||||||
if (acceptReturn && !msig.getRet().isVoidType()) {
|
if (acceptReturn && !msig.getRet().isVoidType()) {
|
||||||
@ -92,49 +92,53 @@ public class Combine {
|
|||||||
.get();
|
.get();
|
||||||
|
|
||||||
// Inject return variable
|
// Inject return variable
|
||||||
adjustArgument(target, retVar, true, true);
|
adjustArgument(resolution.node, retVar, true, true);
|
||||||
|
|
||||||
// Handle retvar specially
|
// Handle retvar specially
|
||||||
extension.localVariables.remove(retVar);
|
extension.localVariables.remove(retVar);
|
||||||
|
|
||||||
// Make space in the original frames for the return var
|
// Make space in the original frames for the return var
|
||||||
// This isn't an optimal solution, but it works for now
|
// This isn't an optimal solution, but it works for now
|
||||||
adjustFramesForRetVar(target.instructions, targetArgCount);
|
adjustFramesForRetVar(resolution.node.instructions, targetArgCount);
|
||||||
|
|
||||||
// Replace return instructions with GOTOs to the last instruction in the list
|
// Replace return instructions with GOTOs to the last instruction in the list
|
||||||
// Return values are stored in retVar
|
// Return values are stored in retVar
|
||||||
storeAndGotoFromReturn(target, target.instructions, retVar.index, xsig);
|
storeAndGotoFromReturn(resolution.node, resolution.node.instructions, retVar.index, xsig);
|
||||||
} else {
|
} else {
|
||||||
// If we don't care about the return value from the original, we can replace returns with pops
|
// If we don't care about the return value from the original, we can replace returns with pops
|
||||||
popAndGotoFromReturn(target, target.instructions, xsig);
|
popAndGotoFromReturn(resolution.node, resolution.node.instructions, xsig);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<LocalVariableNode> extVars = getVarsOver(extension.localVariables, xsig.getArgCount());
|
List<LocalVariableNode> extVars = getVarsOver(extension.localVariables, xsig.getArgCount());
|
||||||
|
|
||||||
// Add extension vars to target
|
// Add extension vars to target
|
||||||
target.localVariables.addAll(extVars);
|
resolution.node.localVariables.addAll(extVars);
|
||||||
|
|
||||||
// Add extension instructions to instruction list
|
// Add extension instructions to instruction list
|
||||||
target.instructions.add(extension.instructions);
|
resolution.node.instructions.add(extension.instructions);
|
||||||
|
|
||||||
// Make sure we extend the scope of the original method arguments
|
// Make sure we extend the scope of the original method arguments
|
||||||
for (int i = 0; i < targetArgCount; ++i)
|
for (int i = 0; i < targetArgCount; ++i)
|
||||||
adjustArgument(target, getVarAt(target.localVariables, i), false, false);
|
adjustArgument(resolution.node, getVarAt(resolution.node.localVariables, i), false, false);
|
||||||
|
|
||||||
// Recompute maximum variable count
|
// Recompute maximum variable count
|
||||||
target.maxLocals = Math.max(
|
resolution.node.maxLocals = Math.max(
|
||||||
Math.max(
|
Math.max(
|
||||||
targetArgCount + 1,
|
targetArgCount + 1,
|
||||||
graftArgCount + 1
|
graftArgCount + 1
|
||||||
),
|
),
|
||||||
target.localVariables
|
resolution.node.localVariables
|
||||||
.stream()
|
.stream()
|
||||||
.map(it -> it.index)
|
.map(it -> it.index)
|
||||||
.max(Comparator.comparingInt(a -> a)).orElse(0) + 1
|
.max(Comparator.comparingInt(a -> a)).orElse(0) + 1
|
||||||
);
|
);
|
||||||
|
|
||||||
// Recompute maximum stack size
|
// Recompute maximum stack size
|
||||||
target.maxStack = Math.max(target.maxStack, extension.maxStack);
|
resolution.node.maxStack = Math.max(resolution.node.maxStack, extension.maxStack);
|
||||||
|
|
||||||
|
// Merge try-catch blocks
|
||||||
|
resolution.node.tryCatchBlocks.addAll(extension.tryCatchBlocks);
|
||||||
|
// Exception list not merged to maintain original signature
|
||||||
|
|
||||||
finishGrafting(extension, source);
|
finishGrafting(extension, source);
|
||||||
}
|
}
|
||||||
@ -143,7 +147,7 @@ public class Combine {
|
|||||||
if (initiateGrafting(extension, source))
|
if (initiateGrafting(extension, source))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
final MethodNode target = resolveMethod(extension, source, false);
|
final MethodNode target = resolveMethod(extension, source, false).node;
|
||||||
adaptMethod(extension, source);
|
adaptMethod(extension, source);
|
||||||
|
|
||||||
MethodSignature sig = new MethodSignature(extension.desc);
|
MethodSignature sig = new MethodSignature(extension.desc);
|
||||||
@ -157,6 +161,9 @@ public class Combine {
|
|||||||
for (int i = 0; i < sig.getArgCount(); ++i)
|
for (int i = 0; i < sig.getArgCount(); ++i)
|
||||||
adjustArgument(target, getVarAt(target.localVariables, i), true, false);
|
adjustArgument(target, getVarAt(target.localVariables, i), true, false);
|
||||||
|
|
||||||
|
target.tryCatchBlocks.addAll(extension.tryCatchBlocks);
|
||||||
|
// Exception list not merged to maintain original signature
|
||||||
|
|
||||||
finishGrafting(extension, source);
|
finishGrafting(extension, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,6 +323,92 @@ public class Combine {
|
|||||||
return target.name;
|
return target.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ClassNode getClassNode() {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void ensureLoadClassAssertionState() {
|
||||||
|
if (!hasDeclaredAssertionState())
|
||||||
|
target.fields.add(new FieldNode(
|
||||||
|
VAR_ASSERT_FLAGS,
|
||||||
|
VAR_ASSERT_NAME,
|
||||||
|
"Z",
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
));
|
||||||
|
|
||||||
|
// Check if state is loaded
|
||||||
|
if (target.methods.stream().noneMatch(it -> it.name.equals("<clinit>"))) {
|
||||||
|
MethodNode mnode = new MethodNode(Opcodes.ACC_STATIC, "<clinit>", "()V", null, null);
|
||||||
|
injectAssertionLoad(mnode, true);
|
||||||
|
target.methods.add(mnode);
|
||||||
|
} else {
|
||||||
|
MethodNode clinit = target.methods.stream().filter(it -> it.name.equals("<clinit>")).findAny().get();
|
||||||
|
|
||||||
|
for (AbstractInsnNode node = clinit.instructions.getFirst(); node != null; node = node.getNext())
|
||||||
|
if (node instanceof FieldInsnNode &&
|
||||||
|
node.getOpcode() == Opcodes.PUTSTATIC &&
|
||||||
|
VAR_ASSERT_NAME.equals(((FieldInsnNode) node).name))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Assertion state not loaded in the current clinit. Add it to the start of the clinit
|
||||||
|
injectAssertionLoad(clinit, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For internal use. Adds instructions to the given method to load assertion flag to a static final field
|
||||||
|
* @param node Method to inject instructions into
|
||||||
|
*/
|
||||||
|
private void injectAssertionLoad(MethodNode node, boolean insertReturn) {
|
||||||
|
if (node.instructions == null) {
|
||||||
|
node.instructions = new InsnList();
|
||||||
|
insertReturn = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
AbstractInsnNode current;
|
||||||
|
if (node.instructions.getFirst() == null || !(node.instructions.getFirst() instanceof LabelNode))
|
||||||
|
node.instructions.insert(current = new LabelNode());
|
||||||
|
else
|
||||||
|
current = node.instructions.getFirst();
|
||||||
|
|
||||||
|
node.instructions.insert(current, current = new LdcInsnNode(Type.getType("L"+target.name+";")));
|
||||||
|
node.instructions.insert(current, current = new MethodInsnNode(
|
||||||
|
Opcodes.INVOKEVIRTUAL,
|
||||||
|
"java/lang/Class",
|
||||||
|
"desiredAssertionStatus",
|
||||||
|
"()Z",
|
||||||
|
false
|
||||||
|
));
|
||||||
|
|
||||||
|
final LabelNode jumpNE = new LabelNode();
|
||||||
|
final LabelNode jumpGOTO = new LabelNode();
|
||||||
|
|
||||||
|
node.instructions.insert(current, current = new JumpInsnNode(Opcodes.IFNE, jumpNE));
|
||||||
|
node.instructions.insert(current, current = new InsnNode(Opcodes.ICONST_1));
|
||||||
|
node.instructions.insert(current, current = new JumpInsnNode(Opcodes.GOTO, jumpGOTO));
|
||||||
|
node.instructions.insert(current, current = jumpNE);
|
||||||
|
node.instructions.insert(current, current = new FrameNode(Opcodes.F_SAME, 0, new Object[0], 0, new Object[0]));
|
||||||
|
node.instructions.insert(current, current = new InsnNode(Opcodes.ICONST_0));
|
||||||
|
node.instructions.insert(current, current = jumpGOTO);
|
||||||
|
node.instructions.insert(current, current = new FrameNode(
|
||||||
|
Opcodes.F_SAME1,
|
||||||
|
0,
|
||||||
|
new Object[0],
|
||||||
|
1,
|
||||||
|
new Object[]{ Opcodes.INTEGER }
|
||||||
|
));
|
||||||
|
|
||||||
|
node.instructions.insert(current, current = new FieldInsnNode(Opcodes.PUTSTATIC, target.name, VAR_ASSERT_NAME, "Z"));
|
||||||
|
|
||||||
|
if (insertReturn)
|
||||||
|
node.instructions.insert(current, new InsnNode(Opcodes.RETURN));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean hasDeclaredAssertionState() {
|
||||||
|
return target.fields.stream().anyMatch(it -> VAR_ASSERT_NAME.equals(it.name));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares a {@link MethodNode} for grafting on to a given method and into the targeted {@link ClassNode}
|
* Prepares a {@link MethodNode} for grafting on to a given method and into the targeted {@link ClassNode}
|
||||||
* @param node Node to adapt
|
* @param node Node to adapt
|
||||||
@ -327,8 +420,14 @@ public class Combine {
|
|||||||
if (insn instanceof MethodInsnNode) insn = adaptMethodInsn((MethodInsnNode) insn, source, node);
|
if (insn instanceof MethodInsnNode) insn = adaptMethodInsn((MethodInsnNode) insn, source, node);
|
||||||
else if (insn instanceof LdcInsnNode) adaptLdcInsn((LdcInsnNode) insn, source.getTypeName());
|
else if (insn instanceof LdcInsnNode) adaptLdcInsn((LdcInsnNode) insn, source.getTypeName());
|
||||||
else if (insn instanceof FrameNode) adaptFrameNode((FrameNode) insn, source);
|
else if (insn instanceof FrameNode) adaptFrameNode((FrameNode) insn, source);
|
||||||
else if (insn instanceof FieldInsnNode) adaptFieldInsn((FieldInsnNode) insn, source);
|
|
||||||
else if (insn instanceof InvokeDynamicInsnNode) adaptInvokeDynamicInsn((InvokeDynamicInsnNode) insn, source);
|
else if (insn instanceof InvokeDynamicInsnNode) adaptInvokeDynamicInsn((InvokeDynamicInsnNode) insn, source);
|
||||||
|
else if (insn instanceof FieldInsnNode) {
|
||||||
|
adaptFieldInsn((FieldInsnNode) insn, source);
|
||||||
|
|
||||||
|
// If a method is trying to access the assertion state of the class, ensure the target class declares the state
|
||||||
|
if (VAR_ASSERT_NAME.equals(((FieldInsnNode) insn).name) && insn.getOpcode() == Opcodes.GETSTATIC)
|
||||||
|
ensureLoadClassAssertionState();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adapt variable types
|
// Adapt variable types
|
||||||
@ -372,8 +471,13 @@ public class Combine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void storeAndGotoFromReturn(MethodNode source, InsnList nodes, int storeIndex, MethodSignature sig) {
|
private void storeAndGotoFromReturn(MethodNode source, InsnList nodes, int storeIndex, MethodSignature sig) {
|
||||||
|
// The last (injected) GOTO can always be removed
|
||||||
|
AbstractInsnNode lastGoto = null;
|
||||||
|
int jumpCount = 0;
|
||||||
|
|
||||||
// If we already have a final frame, there's no need to add one
|
// If we already have a final frame, there's no need to add one
|
||||||
LabelNode endLabel = hasEndJumpFrame(nodes) ? findOrMakeEndLabel(nodes) : makeEndJumpFrame(nodes, sig, source);
|
boolean hadEJF = hasEndJumpFrame(nodes);
|
||||||
|
LabelNode endLabel = hadEJF ? findOrMakeEndLabel(nodes) : makeEndJumpFrame(nodes, sig, source);
|
||||||
|
|
||||||
INSTRUCTION_LOOP:
|
INSTRUCTION_LOOP:
|
||||||
for (AbstractInsnNode current = nodes.getFirst(); current != null; current = current.getNext()) {
|
for (AbstractInsnNode current = nodes.getFirst(); current != null; current = current.getNext()) {
|
||||||
@ -399,19 +503,33 @@ public class Combine {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case Opcodes.RETURN:
|
case Opcodes.RETURN:
|
||||||
nodes.set(current, current = new JumpInsnNode(Opcodes.GOTO, endLabel));
|
nodes.set(current, lastGoto = current = new JumpInsnNode(Opcodes.GOTO, endLabel));
|
||||||
|
++jumpCount;
|
||||||
// Fallthrough
|
// Fallthrough
|
||||||
|
|
||||||
default:
|
default:
|
||||||
continue INSTRUCTION_LOOP;
|
continue INSTRUCTION_LOOP;
|
||||||
}
|
}
|
||||||
nodes.insert(current, current = new JumpInsnNode(Opcodes.GOTO, endLabel));
|
nodes.insert(current, lastGoto = current = new JumpInsnNode(Opcodes.GOTO, endLabel));
|
||||||
|
++jumpCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastGoto != null) {
|
||||||
|
nodes.remove(lastGoto);
|
||||||
|
|
||||||
|
// The final jump frame and label can be removed
|
||||||
|
if (jumpCount == 1 && !hadEJF)
|
||||||
|
nodes.remove(endLabel.getNext());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void popAndGotoFromReturn(MethodNode source, InsnList nodes, MethodSignature sig) {
|
private void popAndGotoFromReturn(MethodNode source, InsnList nodes, MethodSignature sig) {
|
||||||
|
AbstractInsnNode lastGoto = null;
|
||||||
|
int jumpCount = 0;
|
||||||
|
|
||||||
// If we already have a final frame, there's no need to add one
|
// If we already have a final frame, there's no need to add one
|
||||||
LabelNode endLabel = hasEndJumpFrame(nodes) ? findOrMakeEndLabel(nodes) : makeEndJumpFrame(nodes, sig, source);
|
boolean hadEJF = hasEndJumpFrame(nodes);
|
||||||
|
LabelNode endLabel = hadEJF ? findOrMakeEndLabel(nodes) : makeEndJumpFrame(nodes, sig, source);
|
||||||
|
|
||||||
INSTRUCTION_LOOP:
|
INSTRUCTION_LOOP:
|
||||||
for (AbstractInsnNode current = nodes.getFirst(); current != null; current = current.getNext()) {
|
for (AbstractInsnNode current = nodes.getFirst(); current != null; current = current.getNext()) {
|
||||||
@ -428,14 +546,25 @@ public class Combine {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case Opcodes.RETURN:
|
case Opcodes.RETURN:
|
||||||
nodes.set(current, current = new JumpInsnNode(Opcodes.GOTO, endLabel));
|
nodes.set(current, lastGoto = current = new JumpInsnNode(Opcodes.GOTO, endLabel));
|
||||||
|
++jumpCount;
|
||||||
// Fallthrough
|
// Fallthrough
|
||||||
|
|
||||||
default:
|
default:
|
||||||
continue INSTRUCTION_LOOP;
|
continue INSTRUCTION_LOOP;
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes.insert(current, current = new JumpInsnNode(Opcodes.GOTO, endLabel));
|
nodes.insert(current, lastGoto = current = new JumpInsnNode(Opcodes.GOTO, endLabel));
|
||||||
|
++jumpCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (lastGoto != null) {
|
||||||
|
nodes.remove(lastGoto);
|
||||||
|
|
||||||
|
// The final jump frame and label can be removed
|
||||||
|
if (jumpCount == 1 && !hadEJF)
|
||||||
|
nodes.remove(endLabel.getNext());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -748,7 +877,7 @@ public class Combine {
|
|||||||
* @return The located method node
|
* @return The located method node
|
||||||
* @throws MethodNodeResolutionException If no method node matching the given description could be found
|
* @throws MethodNodeResolutionException If no method node matching the given description could be found
|
||||||
*/
|
*/
|
||||||
protected final @NotNull MethodNode checkMethodExists(String name, MethodSignature descriptor) {
|
protected final MethodNode checkMethodExists(String name, MethodSignature descriptor) {
|
||||||
final MethodNode target = findMethodNode(name, descriptor);
|
final MethodNode target = findMethodNode(name, descriptor);
|
||||||
|
|
||||||
if (target == null)
|
if (target == null)
|
||||||
@ -766,7 +895,7 @@ public class Combine {
|
|||||||
* @param desc Descriptor of the method node to find
|
* @param desc Descriptor of the method node to find
|
||||||
* @return A matching {@link MethodNode} if one exists, else null
|
* @return A matching {@link MethodNode} if one exists, else null
|
||||||
*/
|
*/
|
||||||
protected @Nullable MethodNode findMethodNode(String name, MethodSignature desc) {
|
protected MethodNode findMethodNode(String name, MethodSignature desc) {
|
||||||
return target.methods
|
return target.methods
|
||||||
.stream()
|
.stream()
|
||||||
.filter(it -> it.name.equals(name) && new MethodSignature(it.desc).equals(desc))
|
.filter(it -> it.name.equals(name) && new MethodSignature(it.desc).equals(desc))
|
||||||
@ -836,7 +965,7 @@ public class Combine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected static @Nullable LabelNode findLabelBeforeReturn(AbstractInsnNode start, INodeTraversal traverse) {
|
protected static LabelNode findLabelBeforeReturn(AbstractInsnNode start, INodeTraversal traverse) {
|
||||||
for (AbstractInsnNode cur = start; cur != null; cur = traverse.traverse(cur))
|
for (AbstractInsnNode cur = start; cur != null; cur = traverse.traverse(cur))
|
||||||
if (cur instanceof LabelNode) // Traversal hit label
|
if (cur instanceof LabelNode) // Traversal hit label
|
||||||
return (LabelNode) cur;
|
return (LabelNode) cur;
|
||||||
@ -846,7 +975,7 @@ public class Combine {
|
|||||||
return null; // Nothing was found
|
return null; // Nothing was found
|
||||||
}
|
}
|
||||||
|
|
||||||
protected MethodNode resolveMethod(MethodNode inject, GraftSource source, boolean allowAcceptRet) {
|
protected MethodResolution resolveMethod(MethodNode inject, GraftSource source, boolean allowAcceptRet) {
|
||||||
AsmAnnotation<Inject> annot = AsmAnnotation.getAnnotation(Inject.class, inject.visibleAnnotations);
|
AsmAnnotation<Inject> annot = AsmAnnotation.getAnnotation(Inject.class, inject.visibleAnnotations);
|
||||||
if (!allowAcceptRet && (Boolean)annot.getEntry("acceptOriginalReturn"))
|
if (!allowAcceptRet && (Boolean)annot.getEntry("acceptOriginalReturn"))
|
||||||
throw new MethodNodeResolutionException(String.format(
|
throw new MethodNodeResolutionException(String.format(
|
||||||
@ -876,10 +1005,6 @@ public class Combine {
|
|||||||
inject.desc
|
inject.desc
|
||||||
));
|
));
|
||||||
|
|
||||||
// We have a unique candidate
|
|
||||||
if (candidates.size() == 1)
|
|
||||||
return candidates.get(0);
|
|
||||||
|
|
||||||
// If we accept original return value, target with not contain final argument
|
// If we accept original return value, target with not contain final argument
|
||||||
if (acceptRet) {
|
if (acceptRet) {
|
||||||
if (!mSig.getRet().equals(mSig.getArg(mSig.getArgCount() - 1)))
|
if (!mSig.getRet().equals(mSig.getArg(mSig.getArgCount() - 1)))
|
||||||
@ -892,19 +1017,36 @@ public class Combine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final String findSig = sig;
|
final String findSig = sig;
|
||||||
|
final List<MethodNode> cand = candidates;
|
||||||
candidates = candidates.stream().filter(it -> it.desc.equals(findSig)).collect(Collectors.toList());
|
candidates = candidates.stream().filter(it -> it.desc.equals(findSig)).collect(Collectors.toList());
|
||||||
|
|
||||||
// We have no candidates
|
// We have no candidates
|
||||||
if (candidates.isEmpty())
|
if (candidates.isEmpty()) {
|
||||||
|
// If no candidates were found for the explicitly declared signature,
|
||||||
|
// check if accepting original return value was implied
|
||||||
|
if (!acceptRet &&
|
||||||
|
allowAcceptRet &&
|
||||||
|
mSig.getArgCount() > 0 &&
|
||||||
|
mSig.getRet().equals(mSig.getArg(mSig.getArgCount() - 1))) {
|
||||||
|
// Search for method without the implied return value argument
|
||||||
|
final String fSig = mSig.withoutLastArg().toString();
|
||||||
|
candidates = cand.stream().filter(it -> it.desc.equals(fSig)).collect(Collectors.toList());
|
||||||
|
|
||||||
|
// Do we have a match?
|
||||||
|
if (candidates.size() == 1)
|
||||||
|
return new MethodResolution(candidates.get(0), true);
|
||||||
|
}
|
||||||
|
|
||||||
throw new MethodNodeResolutionException(String.format(
|
throw new MethodNodeResolutionException(String.format(
|
||||||
"Cannot find and target candidates for method %s%s",
|
"Cannot find and target candidates for method %s%s",
|
||||||
inject.name,
|
inject.name,
|
||||||
inject.desc
|
inject.desc
|
||||||
));
|
));
|
||||||
|
}
|
||||||
|
|
||||||
// If we have a candidate, it will have a specific name and signature
|
// If we have a candidate, it will have a specific name and signature
|
||||||
// Therefore there cannot be more than one candidate by JVM convention
|
// Therefore there cannot be more than one candidate by JVM convention
|
||||||
return candidates.get(0);
|
return new MethodResolution(candidates.get(0), acceptRet && allowAcceptRet);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static boolean isStatic(MethodNode node) {
|
protected static boolean isStatic(MethodNode node) {
|
||||||
@ -937,4 +1079,14 @@ public class Combine {
|
|||||||
return Objects.hash(source, node);
|
return Objects.hash(source, node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class MethodResolution {
|
||||||
|
public final MethodNode node;
|
||||||
|
public final boolean acceptReturn;
|
||||||
|
|
||||||
|
public MethodResolution(MethodNode node, boolean acceptReturn) {
|
||||||
|
this.node = node;
|
||||||
|
this.acceptReturn = acceptReturn;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,6 @@ import jdk.internal.org.objectweb.asm.tree.AnnotationNode;
|
|||||||
import jdk.internal.org.objectweb.asm.tree.ClassNode;
|
import jdk.internal.org.objectweb.asm.tree.ClassNode;
|
||||||
import jdk.internal.org.objectweb.asm.tree.FieldNode;
|
import jdk.internal.org.objectweb.asm.tree.FieldNode;
|
||||||
import jdk.internal.org.objectweb.asm.tree.MethodNode;
|
import jdk.internal.org.objectweb.asm.tree.MethodNode;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -57,8 +55,12 @@ public final class GraftSource {
|
|||||||
public String getMethodTarget(MethodNode node) {
|
public String getMethodTarget(MethodNode node) {
|
||||||
if (methodAnnotations.containsKey(node)) {
|
if (methodAnnotations.containsKey(node)) {
|
||||||
String target = getInjectionDirective(methodAnnotations.get(node)).getEntry("target");
|
String target = getInjectionDirective(methodAnnotations.get(node)).getEntry("target");
|
||||||
if (target != null && target.length() != 0)
|
if (target != null && target.length() != 0) {
|
||||||
return target;
|
if (target.indexOf('(') != -1)
|
||||||
|
return target;
|
||||||
|
else
|
||||||
|
return target + node.desc;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return node.name + node.desc;
|
return node.name + node.desc;
|
||||||
@ -87,7 +89,7 @@ public final class GraftSource {
|
|||||||
return getInjectedMethod(name, desc) != null;
|
return getInjectedMethod(name, desc) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable MethodNode getInjectedMethod(String name, String desc) {
|
public MethodNode getInjectedMethod(String name, String desc) {
|
||||||
return methodAnnotations
|
return methodAnnotations
|
||||||
.entrySet()
|
.entrySet()
|
||||||
.stream()
|
.stream()
|
||||||
|
@ -31,5 +31,5 @@ public @interface Inject {
|
|||||||
*/
|
*/
|
||||||
boolean acceptOriginalReturn() default false;
|
boolean acceptOriginalReturn() default false;
|
||||||
|
|
||||||
int priority() default Integer.MIN_VALUE;
|
int priority() default Integer.MAX_VALUE;
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,8 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@ -25,7 +27,10 @@ public class Injector {
|
|||||||
public static void injectAll(ClassLoader loader, Combine merger) throws IOException {
|
public static void injectAll(ClassLoader loader, Combine merger) throws IOException {
|
||||||
Enumeration<URL> resources = loader.getResources("");
|
Enumeration<URL> resources = loader.getResources("");
|
||||||
while (resources.hasMoreElements())
|
while (resources.hasMoreElements())
|
||||||
injectDirectory(new File(resources.nextElement().getPath()), merger);
|
injectDirectory(new File(URLDecoder.decode(
|
||||||
|
resources.nextElement().getFile(),
|
||||||
|
StandardCharsets.UTF_8.name())
|
||||||
|
), merger);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void injectAll(Combine merger) throws IOException {
|
public static void injectAll(Combine merger) throws IOException {
|
||||||
|
@ -7,9 +7,6 @@ import jdk.internal.org.objectweb.asm.Label;
|
|||||||
import jdk.internal.org.objectweb.asm.Opcodes;
|
import jdk.internal.org.objectweb.asm.Opcodes;
|
||||||
import jdk.internal.org.objectweb.asm.Type;
|
import jdk.internal.org.objectweb.asm.Type;
|
||||||
import jdk.internal.org.objectweb.asm.tree.*;
|
import jdk.internal.org.objectweb.asm.tree.*;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -192,7 +189,7 @@ public class FrameState {
|
|||||||
* @param node {@link LabelNode} to find jumps referencing
|
* @param node {@link LabelNode} to find jumps referencing
|
||||||
* @return A jump instruction node earlier in the instruction list or null if none could be found
|
* @return A jump instruction node earlier in the instruction list or null if none could be found
|
||||||
*/
|
*/
|
||||||
private static @Nullable JumpInsnNode findEarliestJump(LabelNode node) {
|
private static JumpInsnNode findEarliestJump(LabelNode node) {
|
||||||
JumpInsnNode jump = null;
|
JumpInsnNode jump = null;
|
||||||
|
|
||||||
// Traverse backward until we hit the beginning of the list
|
// Traverse backward until we hit the beginning of the list
|
||||||
@ -643,7 +640,7 @@ public class FrameState {
|
|||||||
private static List<String> getOpsByComplexity(
|
private static List<String> getOpsByComplexity(
|
||||||
boolean complexPush,
|
boolean complexPush,
|
||||||
boolean complexPop,
|
boolean complexPop,
|
||||||
@Nullable Predicate<Integer> insnP
|
Predicate<Integer> insnP
|
||||||
) {
|
) {
|
||||||
ArrayList<Integer> opcodes = new ArrayList<>();
|
ArrayList<Integer> opcodes = new ArrayList<>();
|
||||||
|
|
||||||
@ -687,9 +684,9 @@ public class FrameState {
|
|||||||
* @return Negative values for instructions previous to the current instruction, positive values for instructions
|
* @return Negative values for instructions previous to the current instruction, positive values for instructions
|
||||||
* after the current instruction. Null if instruction could not be found
|
* after the current instruction. Null if instruction could not be found
|
||||||
*/
|
*/
|
||||||
private static @Nullable Integer relativeIndexOf(
|
private static Integer relativeIndexOf(
|
||||||
@NotNull AbstractInsnNode current,
|
AbstractInsnNode current,
|
||||||
@NotNull AbstractInsnNode find
|
AbstractInsnNode find
|
||||||
) {
|
) {
|
||||||
// Check backward
|
// Check backward
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
|
@ -2,8 +2,6 @@ package dev.w1zzrd.asm.signature;
|
|||||||
|
|
||||||
import dev.w1zzrd.asm.exception.SignatureInstanceMismatchException;
|
import dev.w1zzrd.asm.exception.SignatureInstanceMismatchException;
|
||||||
import dev.w1zzrd.asm.exception.TypeSignatureParseException;
|
import dev.w1zzrd.asm.exception.TypeSignatureParseException;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@ -86,7 +84,7 @@ public class TypeSignature {
|
|||||||
* @param primitive Primitive type internal name (V, J or D for Top types)
|
* @param primitive Primitive type internal name (V, J or D for Top types)
|
||||||
* @param isTop Whether or not this is a Top type (only valid for 64-bit types J and D or as delimiter type V)
|
* @param isTop Whether or not this is a Top type (only valid for 64-bit types J and D or as delimiter type V)
|
||||||
*/
|
*/
|
||||||
public TypeSignature(@Nullable Character primitive, boolean isTop) {
|
public TypeSignature(Character primitive, boolean isTop) {
|
||||||
if (primitive != null) {
|
if (primitive != null) {
|
||||||
switch (Character.toUpperCase(primitive)) {
|
switch (Character.toUpperCase(primitive)) {
|
||||||
case 'J':
|
case 'J':
|
||||||
|
@ -22,10 +22,12 @@ public class MergeInject extends MergeTest implements Runnable {
|
|||||||
Directives.callSuper();
|
Directives.callSuper();
|
||||||
s = "Hello";
|
s = "Hello";
|
||||||
number = 10;
|
number = 10;
|
||||||
|
|
||||||
|
assert false : "Test";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Inject(value = BEFORE, target = "stackTest()I")
|
@Inject(value = BEFORE, target = "stackTest")
|
||||||
public int beforeStackTest() {
|
public int beforeStackTest() {
|
||||||
System.out.println("This is before stack test");
|
System.out.println("This is before stack test");
|
||||||
if (ThreadLocalRandom.current().nextBoolean()) {
|
if (ThreadLocalRandom.current().nextBoolean()) {
|
||||||
@ -42,22 +44,32 @@ public class MergeInject extends MergeTest implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Inject(value = AFTER, acceptOriginalReturn = true)
|
@Inject(AFTER)
|
||||||
public int stackTest(int arg) {
|
public int stackTest(int arg) {
|
||||||
Runnable r = () -> {
|
Runnable r = () -> {
|
||||||
System.out.println(arg / 15);
|
System.out.println(arg / 15);
|
||||||
System.out.println("Heyo");
|
System.out.println("Heyo");
|
||||||
};
|
};
|
||||||
r.run();
|
r.run();
|
||||||
return 69;
|
return 69;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Inject(value = AFTER, acceptOriginalReturn = true)
|
@Inject(AFTER)
|
||||||
public String test(String retVal){
|
public String test(String retVal) throws Exception {
|
||||||
|
|
||||||
System.out.println(retVal + "Cringe");
|
System.out.println(retVal + "Cringe");
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (ThreadLocalRandom.current().nextBoolean())
|
||||||
|
throw new Exception("Hello from exception");
|
||||||
|
}catch (Exception e) {
|
||||||
|
System.out.println("Hello from catch");
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
System.out.println("Hello from finally");
|
||||||
|
}
|
||||||
|
|
||||||
return "Modified";
|
return "Modified";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,4 +82,4 @@ public class MergeInject extends MergeTest implements Runnable {
|
|||||||
System.out.println(test()+'\n');
|
System.out.println(test()+'\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -14,7 +14,7 @@ public class MergeTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String test(){
|
public String test(){
|
||||||
Class<?> c = Combine.class;
|
final Class<?> c = Combine.class;
|
||||||
Runnable r = () -> {
|
Runnable r = () -> {
|
||||||
System.out.println("Sick");
|
System.out.println("Sick");
|
||||||
System.out.println(c.getName());
|
System.out.println(c.getName());
|
||||||
@ -57,4 +57,4 @@ public class MergeTest {
|
|||||||
if (bool)
|
if (bool)
|
||||||
System.out.println(k + a + b * getNumber() + i);
|
System.out.println(k + a + b * getNumber() + i);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,10 +1,16 @@
|
|||||||
|
import dev.w1zzrd.asm.Combine;
|
||||||
import dev.w1zzrd.asm.Injector;
|
import dev.w1zzrd.asm.Injector;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class Test {
|
public class Test {
|
||||||
public static void main(String... args) throws IOException {
|
public static void main(String... args) throws IOException {
|
||||||
|
ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(false);
|
||||||
|
|
||||||
// Load target class, inject all annotated classes and load compiled bytecode into JVM
|
// Load target class, inject all annotated classes and load compiled bytecode into JVM
|
||||||
Injector.injectAll("MergeTest").compile();
|
dumpFile(Injector.injectAll("MergeTest"), "MergeTest").compile();
|
||||||
|
|
||||||
// Run simple injection tests
|
// Run simple injection tests
|
||||||
new MergeTest().test();
|
new MergeTest().test();
|
||||||
@ -17,4 +23,21 @@ public class Test {
|
|||||||
r.run();
|
r.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Combine dumpFile(Combine comb, String name) {
|
||||||
|
File f = new File(name + ".class");
|
||||||
|
try {
|
||||||
|
if ((f.isFile() && !f.delete()) || !f.createNewFile())
|
||||||
|
System.err.printf("Could not dump file %s.class%n", name);
|
||||||
|
else {
|
||||||
|
FileOutputStream fos = new FileOutputStream(f);
|
||||||
|
fos.write(comb.toByteArray());
|
||||||
|
fos.close(); // Implicit flush if underlying stream is buffered
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return comb;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user