initial commit

This commit is contained in:
vms 2019-09-05 17:45:42 +03:00
parent 4c65740d03
commit 23665fa16b
4 changed files with 115 additions and 30 deletions

View File

@ -81,9 +81,10 @@ public class SplitMethod {
localsMap.put(index, args.size() - 1);
});
// Create the new method
String name = orig.name.replace("<", "__").replace(">", "__") + "$split";
MethodNode newMethod = new MethodNode(api,
Opcodes.ACC_STATIC + Opcodes.ACC_PRIVATE + Opcodes.ACC_SYNTHETIC, orig.name + "$split",
Type.getMethodDescriptor(Type.getType(Object[].class), args.toArray(new Type[0])), null, null);
Opcodes.ACC_STATIC + Opcodes.ACC_PRIVATE + Opcodes.ACC_SYNTHETIC, name,
Type.getMethodDescriptor(Type.getType(Object[].class), args.toArray(new Type[0])), null, null);
// Add the written locals to the map that are not already there
int newLocalIndex = args.size();
for (Integer key : splitPoint.localsWritten.keySet()) {
@ -102,9 +103,13 @@ public class SplitMethod {
for (int i = 0; i < splitPoint.length; i++) {
AbstractInsnNode insn = orig.instructions.get(i + splitPoint.start);
// Skip frames
if (insn instanceof FrameNode) continue;
if (insn instanceof FrameNode) {
insn.accept(newMethod);
continue;
}
// Store the label
if (insn instanceof LabelNode) seenLabels.add(((LabelNode) insn).getLabel());
if (insn instanceof LabelNode)
seenLabels.add(((LabelNode) insn).getLabel());
// Change the local if needed
if (insn instanceof VarInsnNode) {
insn = insn.clone(Collections.emptyMap());
@ -168,13 +173,13 @@ public class SplitMethod {
}
protected MethodNode createTrimmedMethod(String owner, MethodNode orig,
MethodNode splitOff, Splitter.SplitPoint splitPoint) {
MethodNode splitOff, Splitter.SplitPoint splitPoint) {
// The trimmed method is the same as the original, yet the split area is replaced with a call to the split off
// portion. Before calling the split-off, we have to add locals to the stack part. Then afterwards, we have to
// replace the stack and written locals.
// Effectively clone the orig
MethodNode newMethod = new MethodNode(api, orig.access, orig.name, orig.desc,
orig.signature, orig.exceptions.toArray(new String[0]));
orig.signature, orig.exceptions.toArray(new String[0]));
orig.accept(newMethod);
// Remove all insns, we'll re-add the ones outside the split range
newMethod.instructions.clear();
@ -183,11 +188,18 @@ public class SplitMethod {
Set<Label> seenLabels = new HashSet<>();
// Also keep track of the locals that have been stored, need to know
Set<Integer> seenStoredLocals = new HashSet<>();
int paramOffset = 0;
// If this is an instance method, we consider "0" (i.e. "this") as seen
if ((orig.access & Opcodes.ACC_STATIC) == 0) seenStoredLocals.add(0);
if ((orig.access & Opcodes.ACC_STATIC) == 0) {
seenStoredLocals.add(0);
paramOffset = 1;
}
// We also consider parameters as seen
int paramCount = Type.getArgumentTypes(orig.desc).length;
for (int i = 0; i < paramCount; i++) seenStoredLocals.add(i + paramOffset);
// Add the insns before split
for (int i = 0; i < splitPoint.start; i++) {
AbstractInsnNode insn = orig.instructions.get(i + splitPoint.start);
AbstractInsnNode insn = orig.instructions.get(i);
// Skip frames
if (insn instanceof FrameNode) continue;
// Record label
@ -255,7 +267,7 @@ public class SplitMethod {
}
// Now we have restored all locals and all stack...add the rest of the insns after the split
for (int i = splitPoint.start + splitPoint.length; i < orig.instructions.size(); i++) {
AbstractInsnNode insn = orig.instructions.get(i + splitPoint.start);
AbstractInsnNode insn = orig.instructions.get(i);
// Skip frames
if (insn instanceof FrameNode) continue;
// Record label

View File

@ -67,7 +67,7 @@ public class Splitter implements Iterable<Splitter.SplitPoint> {
public final int length;
public SplitPoint(SortedMap<Integer, Type> localsRead, SortedMap<Integer, Type>localsWritten,
List<Type> neededFromStackAtStart, List<Type> putOnStackAtEnd, int start, int length) {
List<Type> neededFromStackAtStart, List<Type> putOnStackAtEnd, int start, int length) {
this.localsRead = localsRead;
this.localsWritten = localsWritten;
this.neededFromStackAtStart = neededFromStackAtStart;
@ -123,11 +123,22 @@ public class Splitter implements Iterable<Splitter.SplitPoint> {
return ret;
}
private int shiftIndexValue = 1;
protected SplitPoint nextOrNull() {
// Try for each index
while (++currIndex + minSize <= insns.length) {
if(shiftIndexValue > 0) {
currIndex += shiftIndexValue;
} else {
++currIndex;
}
SplitPoint longest = longestForCurrIndex();
if (longest != null) return longest;
if (longest != null) {
shiftIndexValue += longest.length;
return longest;
}
}
return null;
}
@ -139,6 +150,8 @@ public class Splitter implements Iterable<Splitter.SplitPoint> {
InsnTraverseInfo info = new InsnTraverseInfo();
info.startIndex = currIndex;
info.endIndex = Math.min(currIndex + maxSize - 1, insns.length - 1);
// Reduce the end by special calls
constrainEndByInvokeSpecial(info);
// Reduce the end based on try/catch blocks the start is in or that jump to
constrainEndByTryCatchBlocks(info);
// Reduce the end based on any jumps within
@ -146,12 +159,23 @@ public class Splitter implements Iterable<Splitter.SplitPoint> {
// Reduce the end based on any jumps into
constrainEndByExternalJumps(info);
// Make sure we didn't reduce the end too far
if (info.getSize() < minSize) return null;
//if (info.getSize() < minSize) return null;
// Now that we have our largest range from the start index, we can go over each updating the local refs and stack
// For the stack, we are going to use the
return splitPointFromInfo(info);
}
protected void constrainEndByInvokeSpecial(InsnTraverseInfo info) {
// Can't have an invoke special of <init>
for (int i = info.startIndex; i <= info.endIndex; i++) {
AbstractInsnNode node = insns[i];
if (node.getOpcode() == Opcodes.INVOKESPECIAL && ((MethodInsnNode) node).name.equals("<init>")) {
info.endIndex = Math.max(info.startIndex, i - 1);
return;
}
}
}
protected void constrainEndByTryCatchBlocks(InsnTraverseInfo info) {
// Go over all the try/catch blocks, sorted by earliest
for (TryCatchBlockNode block : tryCatchBlocks) {
@ -251,12 +275,53 @@ public class Splitter implements Iterable<Splitter.SplitPoint> {
}
}
// visits instructions with given adaptor
private void visitInstructions(int begin, int end, AnalyzerAdapter adapter) {
if(end - begin <= 0) {
return;
}
List<Object> stack = new ArrayList<>();
List<Object> locals = new ArrayList<>();
boolean gotoSeen = false;
for (int i = begin; i < end; ++i) {
if(insns[i].getOpcode() == Opcodes.GOTO || insns[i].getOpcode() == Opcodes.ATHROW ||
(insns[i].getOpcode() >= Opcodes.TABLESWITCH && insns[i].getOpcode() <= Opcodes.RETURN) ) {
stack = adapter.stack;
locals = adapter.locals;
gotoSeen = true;
} else if(gotoSeen) {
gotoSeen = false;
if(insns[i].getOpcode() >= Opcodes.IRETURN && insns[i].getOpcode() <= Opcodes.RETURN) {
stack.clear();
locals.clear();
}
adapter.visitFrame(Opcodes.F_NEW, locals.size(), locals.toArray(), stack.size(), stack.toArray());
}
insns[i].accept(adapter);
}
AbstractInsnNode lastInsn = insns[end - 1];
if(lastInsn.getOpcode() == Opcodes.GOTO || lastInsn.getOpcode() == Opcodes.ATHROW ||
(lastInsn.getOpcode() >= Opcodes.TABLESWITCH && lastInsn.getOpcode() <= Opcodes.RETURN) ) {
if(lastInsn.getOpcode() >= Opcodes.IRETURN && lastInsn.getOpcode() <= Opcodes.RETURN) {
stack.clear();
locals.clear();
}
adapter.visitFrame(Opcodes.F_NEW, locals.size(), locals.toArray(), stack.size(), stack.toArray());
}
}
protected SplitPoint splitPointFromInfo(InsnTraverseInfo info) {
// We're going to use the analyzer adapter and run it for the up until the end, a step at a time
StackAndLocalTrackingAdapter adapter = new StackAndLocalTrackingAdapter(Splitter.this);
// Visit all of the insns up our start.
// XXX: I checked the source of AnalyzerAdapter to confirm I don't need any of the surrounding stuff
for (int i = 0; i < info.startIndex; i++) insns[i].accept(adapter);
visitInstructions(0, info.startIndex, adapter);
// Take the stack at the start and copy it off
List<Object> stackAtStart = new ArrayList<>(adapter.stack);
// Reset some adapter state
@ -264,27 +329,34 @@ public class Splitter implements Iterable<Splitter.SplitPoint> {
adapter.localsRead.clear();
adapter.localsWritten.clear();
// Now go over the remaining range
for (int i = info.startIndex; i <= info.endIndex; i++) insns[i].accept(adapter);
visitInstructions(info.startIndex, info.endIndex + 1, adapter);
// Build the split point
return new SplitPoint(
localMapFromAdapterLocalMap(adapter.localsRead, adapter.uninitializedTypes),
localMapFromAdapterLocalMap(adapter.localsWritten, adapter.uninitializedTypes),
typesFromAdapterStackRange(stackAtStart, adapter.lowestStackSize, adapter.uninitializedTypes),
typesFromAdapterStackRange(adapter.stack, adapter.lowestStackSize, adapter.uninitializedTypes),
info.startIndex,
info.getSize()
localMapFromAdapterLocalMap(adapter.localsRead, adapter.uninitializedTypes),
localMapFromAdapterLocalMap(adapter.localsWritten, adapter.uninitializedTypes),
typesFromAdapterStackRange(stackAtStart, adapter.lowestStackSize, adapter.uninitializedTypes),
typesFromAdapterStackRange(adapter.stack, adapter.lowestStackSize, adapter.uninitializedTypes),
info.startIndex,
info.getSize()
);
}
protected SortedMap<Integer, Type> localMapFromAdapterLocalMap(
SortedMap<Integer, Object> map, Map<Object, Object> uninitializedTypes) {
SortedMap<Integer, Object> map, Map<Object, Object> uninitializedTypes) {
SortedMap<Integer, Type> ret = new TreeMap<>();
map.forEach((k, v) -> ret.put(k, typeFromAdapterStackItem(v, uninitializedTypes)));
map.forEach((k, v) ->
{
if(v != Opcodes.TOP ) {
ret.put(k, typeFromAdapterStackItem(v, uninitializedTypes));
}
}
);
return ret;
}
protected List<Type> typesFromAdapterStackRange(
List<Object> stack, int start, Map<Object, Object> uninitializedTypes) {
List<Object> stack, int start, Map<Object, Object> uninitializedTypes) {
List<Type> ret = new ArrayList<>();
for (int i = start; i < stack.size(); i++) {
Object item = stack.get(i);

View File

@ -19,7 +19,7 @@ class Util {
static boolean isStoreOp(int opcode) {
return opcode == Opcodes.ISTORE || opcode == Opcodes.LSTORE || opcode == Opcodes.FSTORE ||
opcode == Opcodes.DSTORE || opcode == Opcodes.ASTORE;
opcode == Opcodes.DSTORE || opcode == Opcodes.ASTORE;
}
static int storeOpFromType(Type type) {
@ -55,13 +55,13 @@ class Util {
static void unboxStackIfNecessary(Type type, MethodNode method) {
if (type == Type.INT_TYPE) method.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"java/lang/Integer", "intValue", Type.getMethodDescriptor(Type.INT_TYPE), false);
"java/lang/Integer", "intValue", Type.getMethodDescriptor(Type.INT_TYPE), false);
else if (type == Type.FLOAT_TYPE) method.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"java/lang/Float", "floatValue", Type.getMethodDescriptor(Type.FLOAT_TYPE), false);
"java/lang/Float", "floatValue", Type.getMethodDescriptor(Type.FLOAT_TYPE), false);
else if (type == Type.LONG_TYPE) method.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"java/lang/Long", "longValue", Type.getMethodDescriptor(Type.LONG_TYPE), false);
"java/lang/Long", "longValue", Type.getMethodDescriptor(Type.LONG_TYPE), false);
else if (type == Type.DOUBLE_TYPE) method.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"java/lang/Double", "doubleValue", Type.getMethodDescriptor(Type.DOUBLE_TYPE), false);
"java/lang/Double", "doubleValue", Type.getMethodDescriptor(Type.DOUBLE_TYPE), false);
}
static AbstractInsnNode intConst(int v) {
@ -79,6 +79,6 @@ class Util {
static MethodInsnNode boxCall(Class<?> boxType, Type primType) {
return new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getInternalName(boxType),
"valueOf", Type.getMethodDescriptor(Type.getType(boxType), primType), false);
"valueOf", Type.getMethodDescriptor(Type.getType(boxType), primType), false);
}
}

View File

@ -13,7 +13,7 @@ import org.objectweb.asm.tree.ClassNode
open class AsmToBinary(val splitMethod: SplitMethod? = SplitMethod(Opcodes.ASM6)) {
fun fromClassNode(
cn: ClassNode,
newClassWriter: () -> ClassWriter = { ClassWriter(ClassWriter.COMPUTE_FRAMES + ClassWriter.COMPUTE_MAXS) }
newClassWriter: () -> ClassWriter = { ClassWriter(ClassWriter.COMPUTE_FRAMES) }
): ByteArray {
while (true) {
val cw = newClassWriter()
@ -29,6 +29,7 @@ open class AsmToBinary(val splitMethod: SplitMethod? = SplitMethod(Opcodes.ASM6)
require(cn.name == e.className)
val tooLargeIndex = cn.methods.indexOfFirst { it.name == e.methodName && it.desc == e.descriptor }
require(tooLargeIndex >= 0)
val tt = cn.methods[tooLargeIndex]
val split = splitMethod.split(cn.name, cn.methods[tooLargeIndex])
split ?: throw IllegalStateException("Failed to split", e)
// Change the split off method's name if there's already one