Commit cf1109d1 authored by Lubomir Bulej's avatar Lubomir Bulej

CodeTransformer: new utility interface for simple code transformers.

UnprocessedCode, SnippetUnprocessedCode: extracted code transformation into separate classes implementing the CodeTransformer interface.
SnippetUnprocessedCode: add transformation to reclaim local variable slots taken by snippet context parameters.
SnippetUnprocessedCode: add support to query default snippet location.
SnippetUnprocessedCode, ReflectionHelper: moved getMethod() and getField() methods to ReflectionHelper.
Code: add support to query the number of method parameter slots.
parent 506743fb
......@@ -123,6 +123,15 @@ public class Code {
return _containsHandledException;
}
/**
* @return the number of local slots occupied by parameters of the method
* representing this code.
*/
public int getParameterSlotCount () {
return AsmHelper.getParameterSlotCount (__method);
}
//
/**
......
package ch.usi.dag.disl.coderep;
import java.util.List;
import java.util.stream.Collectors;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.LabelNode;
import ch.usi.dag.disl.util.AsmHelper;
import ch.usi.dag.disl.util.CodeTransformer;
import ch.usi.dag.disl.util.AsmHelper.Insns;
/**
* Replaces premature return from method with an unconditional jump at the end
* of the instruction list. This is done by adding a label at the end of the
* given instruction list, and replacing all kinds of RETURN instructions with a
* GOTO instruction to jump to the label at the end of the instruction list.
*/
final class ReplaceReturnsWithGotoCodeTransformer implements CodeTransformer {
/*
* @see CodeProcessor#process(InsnList)
*/
@Override
public void transform (final InsnList insns) {
//
// Collect all RETURN instructions.
//
final List <AbstractInsnNode> returnInsns = Insns.asList (insns)
.stream ()
.filter (insn -> AsmHelper.isReturn (insn))
.collect (Collectors.toList ());
if (returnInsns.size () > 1) {
//
// Replace all RETURN instructions with a GOTO instruction
// that jumps to a label at the end of the instruction list.
//
final LabelNode targetLabel = new LabelNode ();
returnInsns.forEach (insn -> {
insns.insertBefore (insn, AsmHelper.jumpTo (targetLabel));
insns.remove (insn);
});
insns.add (targetLabel);
} else if (returnInsns.size () == 1) {
// Remove the RETURN at the end of a method. No need for GOTO.
insns.remove (returnInsns.get (0));
}
}
}
package ch.usi.dag.disl.coderep;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import ch.usi.dag.disl.localvar.ThreadLocalVar;
import ch.usi.dag.disl.util.AsmHelper;
import ch.usi.dag.disl.util.CodeTransformer;
import ch.usi.dag.disl.util.AsmHelper.Insns;
import ch.usi.dag.disl.util.Insn;
final class RewriteThreadLocalVarAccessesCodeTransformer implements CodeTransformer {
private final Set <String> __tlvIds;
//
public RewriteThreadLocalVarAccessesCodeTransformer (final Set <ThreadLocalVar> tlvs) {
//
// Generate a set of TLV identifiers for faster lookup.
//
// TODO LB: We do this for every class - make LocalVars support the check.
//
__tlvIds = tlvs.stream ().unordered ()
.map (tlv -> tlv.getID ())
.collect (Collectors.toSet ());
}
//
@Override
public void transform (final InsnList insns) {
//
// Scan the method code for GETSTATIC/PUTSTATIC instructions accessing
// the static fields marked to be thread locals. Replace all the
// static accesses with thread variable accesses.
//
// First select the instructions, then modify the instruction list.
//
final List <FieldInsnNode> fieldInsns = Insns.asList (insns)
.stream ().unordered ()
.filter (AsmHelper::isStaticFieldAccess)
.map (insn -> (FieldInsnNode) insn)
.filter (insn -> {
final String fieldName = ThreadLocalVar.fqFieldNameFor (insn.owner, insn.name);
return __tlvIds.contains (fieldName);
})
.collect (Collectors.toList ());
fieldInsns.forEach (insn -> __rewriteThreadLocalVarAccess (insn, insns));
}
//
private static final Type threadType = Type.getType (Thread.class);
private static final String currentThreadName = "currentThread";
private static final Type currentThreadType = Type.getMethodType (threadType);
private static void __rewriteThreadLocalVarAccess (
final FieldInsnNode fieldInsn, final InsnList insns
) {
//
// Issue a call to Thread.currentThread() and access a field
// in the current thread corresponding to the thread-local
// variable.
//
insns.insertBefore (fieldInsn, AsmHelper.invokeStatic (
threadType, currentThreadName, currentThreadType
));
if (Insn.GETSTATIC.matches (fieldInsn)) {
insns.insertBefore (fieldInsn, AsmHelper.getField (
threadType, fieldInsn.name, fieldInsn.desc
));
} else if (Insn.PUTSTATIC.matches (fieldInsn)) {
//
// We need to execute a PUTFIELD instruction, which requires
// two operands, but the current thread reference that we
// currently have on the top of the stack needs to come after
// the value that is to be stored.
//
// We therefore need to swap the two operands on the stack.
// There is no easier way, unless we want to track where the
// value to be stored was pushed on the stack and put the
// currentThread() method invocation before it.
//
// For primitive operands, we just swap the values. For wide
// operands, we need to rearrange 3 slots in total, with the
// slot 0 becoming slot 2, and slots 1 and 2 becoming 0 and 1.
//
if (Type.getType (fieldInsn.desc).getSize () == 1) {
insns.insertBefore (fieldInsn, new InsnNode (Opcodes.SWAP));
} else {
insns.insertBefore (fieldInsn, new InsnNode (Opcodes.DUP_X2));
insns.insertBefore (fieldInsn, new InsnNode (Opcodes.POP));
}
insns.insertBefore (fieldInsn, AsmHelper.putField (
threadType, fieldInsn.name, fieldInsn.desc
));
} else {
throw new AssertionError ("field insn is GETSTATIC/PUTSTATIC");
}
//
// Remove the static field access instruction.
//
insns.remove (fieldInsn);
}
}
......@@ -11,13 +11,10 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TryCatchBlockNode;
......@@ -30,8 +27,8 @@ import ch.usi.dag.disl.localvar.SyntheticLocalVar;
import ch.usi.dag.disl.localvar.ThreadLocalVar;
import ch.usi.dag.disl.util.AsmHelper;
import ch.usi.dag.disl.util.AsmHelper.Insns;
import ch.usi.dag.disl.util.CodeTransformer;
import ch.usi.dag.disl.util.Constants;
import ch.usi.dag.disl.util.Insn;
import ch.usi.dag.disl.util.JavaNames;
import ch.usi.dag.disl.util.ReflectionHelper;
import ch.usi.dag.disl.util.cfg.CtrlFlowGraph;
......@@ -71,6 +68,10 @@ public class UnprocessedCode {
return __method.name;
}
public String location () {
return location (__method.instructions.getFirst ());
}
public String location (final AbstractInsnNode insn) {
return String.format (
"snippet %s.%s%s", __className, __method.name,
......@@ -113,15 +114,19 @@ public class UnprocessedCode {
//
// Process code:
//
// Clone the method code so that we can transform it, then replace all
// RETURN instructions with a GOTO to the end of a method, and rewrite
// accesses to thread-local variables.
// Clone the method code so that we can transform it, then:
//
// - replace all RETURN instructions with a GOTO to the end of a method
// - rewrite accesses to thread-local variables
//
// Finally create an instance of processed code.
//
final MethodNode method = AsmHelper.cloneMethod (__method);
__replaceReturnsWithGoto (method.instructions);
__rewriteThreadLocalVarAccesses (method.instructions, tlvs);
CodeTransformer.apply (method.instructions,
new ReplaceReturnsWithGotoCodeTransformer (),
new RewriteThreadLocalVarAccessesCodeTransformer (tlvs)
);
return new Code (
method, slvs, tlvs, scms, handlesExceptions
......@@ -319,136 +324,4 @@ public class UnprocessedCode {
return false;
}
//
/**
* Adds a label to the end of the given instruction list and replaces all
* types of RETURN instructions in the list with a GOTO instruction to jump
* to the label at the end of the instruction list.
*
* @param insns
* list of instructions to perform the replacement on
*/
private static void __replaceReturnsWithGoto (final InsnList insns) {
//
// Collect all RETURN instructions.
//
final List <AbstractInsnNode> returnInsns = Insns.asList (insns)
.parallelStream ().unordered ()
.filter (insn -> AsmHelper.isReturn (insn))
.collect (Collectors.toList ());
if (returnInsns.size () > 1) {
//
// Replace all RETURN instructions with a GOTO instruction
// that jumps to a label at the end of the instruction list.
//
final LabelNode targetLabel = new LabelNode ();
returnInsns.forEach (insn -> {
insns.insertBefore (insn, AsmHelper.jumpTo (targetLabel));
insns.remove (insn);
});
insns.add (targetLabel);
} else if (returnInsns.size () == 1) {
// there is only the return at the end of a method
insns.remove (returnInsns.get (0));
}
}
//
private static void __rewriteThreadLocalVarAccesses (
final InsnList insns, final Set <ThreadLocalVar> tlvs
) {
//
// Generate a set of TLV identifiers for faster lookup.
//
// TODO LB: We do this for every class - make LocalVars support the check.
//
final Set <String> tlvIds = tlvs.parallelStream ().unordered ()
.map (tlv -> tlv.getID ())
.collect (Collectors.toSet ());
//
// Scan the method code for GETSTATIC/PUTSTATIC instructions accessing
// the static fields marked to be thread locals. Replace all the
// static accesses with thread variable accesses.
//
// First select the instructions, then modify the instruction list.
//
final List <FieldInsnNode> fieldInsns = Insns.asList (insns)
.parallelStream ().unordered ()
.filter (insn -> AsmHelper.isStaticFieldAccess (insn))
.map (insn -> (FieldInsnNode) insn)
.filter (insn -> {
final String fieldName = ThreadLocalVar.fqFieldNameFor (insn.owner, insn.name);
return tlvIds.contains (fieldName);
})
.collect (Collectors.toList ());
fieldInsns.forEach (insn -> __rewriteThreadLocalVarAccess (insn, insns));
}
//
private static final Type threadType = Type.getType (Thread.class);
private static final String currentThreadName = "currentThread";
private static final Type currentThreadType = Type.getMethodType (threadType);
private static void __rewriteThreadLocalVarAccess (
final FieldInsnNode fieldInsn, final InsnList insns
) {
//
// Issue a call to Thread.currentThread() and access a field
// in the current thread corresponding to the thread-local
// variable.
//
insns.insertBefore (fieldInsn, AsmHelper.invokeStatic (
threadType, currentThreadName, currentThreadType
));
if (Insn.GETSTATIC.matches (fieldInsn)) {
insns.insertBefore (fieldInsn, AsmHelper.getField (
threadType, fieldInsn.name, fieldInsn.desc
));
} else {
//
// We need to execute a PUTFIELD instruction, which requires
// two operands, but the current thread reference that we
// currently have on the top of the stack needs to come after
// the value that is to be stored.
//
// We therefore need to swap the two operands on the stack.
// There is no easier way, unless we want to track where the
// value to be stored was pushed on the stack and put the
// currentThread() method invocation before it.
//
// For primitive operands, we just swap the values. For wide
// operands, we need to rearrange 3 slots in total, with the
// slot 0 becoming slot 2, and slots 1 and 2 becoming 0 and 1.
//
if (Type.getType (fieldInsn.desc).getSize () == 1) {
insns.insertBefore (fieldInsn, new InsnNode (Opcodes.SWAP));
} else {
insns.insertBefore (fieldInsn, new InsnNode (Opcodes.DUP_X2));
insns.insertBefore (fieldInsn, new InsnNode (Opcodes.POP));
}
insns.insertBefore (fieldInsn, AsmHelper.putField (
threadType, fieldInsn.name, fieldInsn.desc
));
}
//
// Remove the static field access instruction.
//
insns.remove (fieldInsn);
}
}
package ch.usi.dag.disl.snippet;
import java.lang.reflect.Method;
import org.objectweb.asm.tree.InsnList;
import ch.usi.dag.disl.dynamicbypass.DynamicBypass;
import ch.usi.dag.disl.util.AsmHelper;
import ch.usi.dag.disl.util.CodeTransformer;
import ch.usi.dag.disl.util.ReflectionHelper;
/**
* Wraps a sequence of instructions with code that controls the dynamic bypass.
* The bypass is enabled before the first instruction and disabled again after
* the last instruction:
*
* <pre>
* DynamicBypass.activate();
* ... original snippet code ...
* DynamicBypass.deactivate();
* </pre>
*/
class InsertDynamicBypassControlCodeTransformer implements CodeTransformer {
private static final Method __dbActivate__ = ReflectionHelper.getMethod (DynamicBypass.class, "activate");
private static final Method __dbDeactivate__ = ReflectionHelper.getMethod (DynamicBypass.class, "deactivate");
//
@Override
public void transform (final InsnList insns) {
insns.insert (AsmHelper.invokeStatic (__dbActivate__));
insns.add (AsmHelper.invokeStatic (__dbDeactivate__));
}
}
package ch.usi.dag.disl.snippet;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.TryCatchBlockNode;
import ch.usi.dag.disl.util.AsmHelper;
import ch.usi.dag.disl.util.CodeTransformer;
import ch.usi.dag.disl.util.ReflectionHelper;
/**
* Wraps the code sequence with a try-catch block that fails immediately if a
* snippet produces an exception.
*/
final class InsertExceptionHandlerCodeTransformer implements CodeTransformer {
private static final Method __printlnMethod__ = ReflectionHelper.getMethod (PrintStream.class, "println", String.class);
private static final Method __printStackTraceMethod__ = ReflectionHelper.getMethod (Throwable.class, "printStackTrace");
private static final Method __exitMethod__ = ReflectionHelper.getMethod (System.class, "exit", int.class);
private static final Field __errField__ = ReflectionHelper.getField (System.class, "err");
//
final String __location;
final List <TryCatchBlockNode> __tcbs;
/**
* Initializes this transformer with a location and an output list to which
* to add a newly created try-catch block.
*
* @param location
* the location to which this code corresponds.
* @param tcbs
* the list of {@link TryCatchBlockNode} instances to which to add
* the newly created try-catch block.
*/
public InsertExceptionHandlerCodeTransformer (final String location, final List <TryCatchBlockNode> tcbs) {
__location = Objects.requireNonNull (location);
__tcbs = Objects.requireNonNull (tcbs);
}
//
@Override
public void transform (final InsnList insns) {
//
// The inserted code:
//
// TRY_BEGIN: try {
// ... original snippet code ...
// goto HANDLER_END;
// TRY_END: } finally (e) {
// HANDLER_BEGIN: System.err.println(...);
// e.printStackTrace();
// System.exit(666);
// throw e;
// HANDLER_END: }
//
// In the finally block, the exception will be at the top of the stack.
//
final LabelNode tryBegin = new LabelNode();
insns.insert (tryBegin);
final LabelNode handlerEnd = new LabelNode ();
insns.add (AsmHelper.jumpTo (handlerEnd));
final LabelNode tryEnd = new LabelNode ();
insns.add (tryEnd);
final LabelNode handlerBegin = new LabelNode ();
insns.add (handlerBegin);
// System.err.println(...);
insns.add (AsmHelper.getStatic (__errField__));
insns.add (AsmHelper.loadConst (String.format (
"%s: failed to handle an exception", __location
)));
insns.add (AsmHelper.invokeVirtual (__printlnMethod__));
// e.printStackTrace();
insns.add (new InsnNode (Opcodes.DUP));
insns.add (AsmHelper.invokeVirtual (__printStackTraceMethod__));
// System.exit(666)
insns.add (AsmHelper.loadConst (666));
insns.add (AsmHelper.invokeStatic (__exitMethod__));
// Re-throw the exception (just for proper stack frame calculation)
insns.add (new InsnNode (Opcodes.ATHROW));
insns.add (handlerEnd);
// Add the exception handler to the list.
__tcbs.add (new TryCatchBlockNode (tryBegin, tryEnd, handlerBegin, null));
}
}
package ch.usi.dag.disl.snippet;
import org.objectweb.asm.tree.IincInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.VarInsnNode;
import ch.usi.dag.disl.util.CodeTransformer;
import ch.usi.dag.disl.util.AsmHelper.Insns;
/**
* Shifts access to local variable slots by a given offset.
*/
final class ShiftLocalVarSlotCodeTransformer implements CodeTransformer {
private final int __offset;
public ShiftLocalVarSlotCodeTransformer (final int offset) {
__offset = offset;
}
//
@Override
public void transform (final InsnList insns) {
Insns.asList (insns).stream ().forEach (insn -> {
if (insn instanceof VarInsnNode) {
((VarInsnNode) insn).var += __offset;
} else if (insn instanceof IincInsnNode) {
((IincInsnNode) insn).var += __offset;
}
});
}
}
package ch.usi.dag.disl.snippet;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
......@@ -13,18 +11,13 @@ import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TryCatchBlockNode;
import ch.usi.dag.disl.DiSL.CodeOption;
import ch.usi.dag.disl.coderep.Code;
import ch.usi.dag.disl.coderep.UnprocessedCode;
import ch.usi.dag.disl.dynamicbypass.DynamicBypass;
import ch.usi.dag.disl.exception.DiSLFatalException;
import ch.usi.dag.disl.exception.ProcessorException;
import ch.usi.dag.disl.exception.ReflectionException;
import ch.usi.dag.disl.localvar.LocalVars;
......@@ -33,8 +26,8 @@ import ch.usi.dag.disl.marker.Marker;
import ch.usi.dag.disl.processor.ArgProcessor;
import ch.usi.dag.disl.processorcontext.ArgumentProcessorContext;
import ch.usi.dag.disl.processorcontext.ArgumentProcessorMode;
import ch.usi.dag.disl.util.AsmHelper;
import ch.usi.dag.disl.util.AsmHelper.Insns;
import ch.usi.dag.disl.util.CodeTransformer;
/**
......@@ -69,8 +62,6 @@ public class SnippetUnprocessedCode {
__snippetDynamicBypass = snippetDynamicBypass;
}
//
public String className () {
......@@ -99,23 +90,42 @@ public class SnippetUnprocessedCode {
//
// Process code:
//
// If required, insert dynamic bypass control around the snippet,
// or code to catch all exceptions to avoid disrupting program flow.
// - reclaim local variable slots taken by snippet context parameters
//
// The context parameters to a snippet take up local variable slots.
// These are not used at runtime, needlessly increasing the stack
// frame. To reclaim these slots, we shift all local variable
// accesses by the amount of slots occupied by the context
// parameters.
//
// - If required, insert dynamic bypass control around the snippet.
// - If required, catch all exceptions around the snippet.
//
// Code processing has to be done before looking for argument processor
// invocations, otherwise the analysis will produce wrong instruction
// references.
//
final InsnList insns = code.getInstructions ();
final List <CodeTransformer> transformers = new ArrayList <> ();
transformers.add (
new ShiftLocalVarSlotCodeTransformer (-code.getParameterSlotCount ())
);
if (options.contains (CodeOption.DYNAMIC_BYPASS) && __snippetDynamicBypass) {
__insertDynamicBypassControl (insns);
transformers.add (new InsertDynamicBypassControlCodeTransformer ());
}
final List <TryCatchBlockNode> tcbs = code.getTryCatchBlocks ();
if (options.contains (CodeOption.CATCH_EXCEPTIONS)) {
__insertExceptionHandler (insns, tcbs);
transformers.add (
new InsertExceptionHandlerCodeTransformer (
__template.location (), code.getTryCatchBlocks ()
)
);
}
final InsnList insns = code.getInstructions ();
CodeTransformer.apply (insns, transformers);
//
// Analyze code:
//
......@@ -256,114 +266,4 @@ public class SnippetUnprocessedCode {
);
}
//
private static final Method __printlnMethod__ = __getMethod (PrintStream.class, "println", String.class);
private static final Method __printStackTraceMethod__ = __getMethod (Throwable.class, "printStackTrace");
private static final Method __exitMethod__ = __getMethod (System.class, "exit", int.class);
private static final Field __errField__ = __getField (System.class, "err");
/**
* Inserts a try-finally block for each snippet and fails immediately if a
* snippet produces an exception.
*/
private void __insertExceptionHandler (
final InsnList insns, final List <TryCatchBlockNode> tcbs