/******************************************************************************* * Copyright (c) 2002,2006 IBM Corporation. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package com.ibm.wala.shrikeBT; import java.util.ArrayList; import java.util.IdentityHashMap; /** * The MethodEditor is the core of the ShrikeBT code rewriting mechanism. To rewrite code, construct a MethodEditor initialized with * the intial code for the method. Then perform a series of passes. In each pass you call beginPass(), insert a number of patches * using the insert...() or replace...() methods, then optionally call applyPatches() to update the code with your changes, then * call endPass(). The end of each pass updates the code in the MethodData that you passed in, so the new code can be extracted from * that MethodData object. Note that if applyPatches() is not called, or it is called but no patches had been inserted, then the * code will not be updated and that pass is essentially aborted. * * A patch is simply a subclass of MethodEditor.Patch, representing a code sequence to insert into the method. Each patch class * implements one method, emitTo(), which writes the patch code into the code stream using the provided MethodEditor.Output * instance. Anonymous inner classes are very useful for writing patches. * * Patches can be inserted at the following points: * <ul> * <li>Before the start of the method * <li>Before or after a particular instruction * <li>Replacing a particular instruction * <li>Handling an exception on a particular instruction * <li>Handling an exception anywhere in the method * <li>After the end of the method, where code will not normally be executed, but can be branched to by another patch * </ul> * Patch application is deterministic; if two patches are applied at the same point, then the order of application determines the * order of code generation, in a way specified by the particular application point. See the patch application methods below. * * MethodEditor relies on labels. A label is an integer representing a point in the code. Labels are valid only during a single * pass; at the end of each pass, instructions are reordered and old labels become invalid. At the beginning of a pass every * instruction in the instructions array is labelled with the index of that instruction in the array. During instrumentation new * labels can be allocated by calling MethodEditor.allocateLabel(); control instructions can be created referring to these new * labels or the existing labels. At the end of a pass, as patch code is spliced into the method body, all instructions are updated * to refer to the new labels which are simply the indices of instructions in the instruction array. */ public final class MethodEditor { private static final ExceptionHandler[] noHandlers = new ExceptionHandler[0]; /** Records which original bytecode instruction each Instruction belongs to. */ private int[] instructionsToBytecodes; private IInstruction[] instructions; private ExceptionHandler[][] handlers; final private MethodData methodInfo; // working private static final int BEFORE_PASS = 0x01; private static final int DURING_PASS = 0x02; private static final int EMITTING_CODE = 0x04; private static final int BEFORE_END_PASS = 0x08; private int state = BEFORE_PASS; private int patchCount; private Patch[] beforePatches; private Patch[] afterPatches; private Patch[] lastAfterPatches; private Patch[] replacementPatches; private Patch methodStartPatches; private Patch afterMethodPatches; private HandlerPatch[] instructionHandlerPatches; private HandlerPatch methodHandlerPatches; private int nextLabel; /** * This patch lets us stuff an exception handler into the code. */ private static class HandlerPatch { final HandlerPatch next; final String catchClass; final int label; final Patch patch; HandlerPatch(HandlerPatch next, String catchClass, int label, Patch patch) { this.next = next; this.catchClass = catchClass; this.label = label; this.patch = patch; } } /** * Build an editor for the given method. This editor will write back its changes to the method info. * * @throws IllegalArgumentException if info is null */ public MethodEditor(MethodData info) { if (info == null) { throw new IllegalArgumentException("info is null"); } methodInfo = info; instructionsToBytecodes = info.getInstructionsToBytecodes(); instructions = info.getInstructions(); handlers = info.getHandlers(); } /** * Build an editor for specific method data. After patching the code you can retrieve the new code, handlers and * instructions-to-bytecode-offsets map. */ public MethodEditor(Instruction[] instructions, ExceptionHandler[][] handlers, int[] instructionsToBytecodes) { if (instructions == null) { throw new IllegalArgumentException("null instructions"); } if (handlers == null) { throw new IllegalArgumentException("null handlers"); } methodInfo = null; this.instructionsToBytecodes = instructionsToBytecodes; this.instructions = instructions; this.handlers = handlers; } private void verifyState(int state) { if ((state & this.state) == 0) { throw new IllegalArgumentException(getStateMessage(state)); } } private String getStateMessage(int state) { switch (state) { case BEFORE_PASS: return "This operation can only be performed before or after an editing pass"; case DURING_PASS: return "This operation can only be performed during an editing pass"; case EMITTING_CODE: return "This operation can only be performed while applying patches and emitting code"; case BEFORE_END_PASS: return "This operation can only be performed after applying patches"; default: return "This operation cannot be performed in this state"; } } /** * @return the current handler array */ public ExceptionHandler[][] getHandlers() { verifyState(BEFORE_PASS | DURING_PASS); return handlers; } /** * @return the current instruction array */ public IInstruction[] getInstructions() { verifyState(BEFORE_PASS | DURING_PASS); return instructions; } /** * @return the current instructions-to-bytecode-offsets map */ public int[] getInstructionsToBytecodes() { verifyState(BEFORE_PASS | DURING_PASS); return instructionsToBytecodes; } static ExceptionHandler[] mergeHandlers(ExceptionHandler[] h1, ExceptionHandler[] h2) { if (h1.length == 0) { return h2; } else if (h2.length == 0) { return h1; } else { ExceptionHandler[] r = new ExceptionHandler[h1.length + h2.length]; System.arraycopy(h1, 0, r, 0, h1.length); System.arraycopy(h2, 0, r, h1.length, h2.length); return r; } } /** * Output is the interface that patches use to emit their code into a method body. */ public final static class Output { final ArrayList<IInstruction> newInstructions = new ArrayList<IInstruction>(); final ArrayList<ExceptionHandler[]> newInstructionHandlers = new ArrayList<ExceptionHandler[]>(); int[] instructionsToBytecodes = new int[10]; final int[] labelDefs; ExceptionHandler[] additionalHandlers; int originalBytecode; boolean codeChanged = false; Output(int numLabels) { labelDefs = new int[numLabels]; } /** * Emit a label definition at the current point in the code. The label must have been previously allocated using * MethodEditor.allocateLabel. */ public void emitLabel(int label) { labelDefs[label] = newInstructions.size(); } /** * Emit an instruction at the current point in the code. */ public void emit(Instruction i) { codeChanged = true; internalEmitInstruction(i); } /** * Emit an instruction with some exception handlers at the current point in the code. */ public void emit(Instruction i, ExceptionHandler[] handlers) { codeChanged = true; int s = newInstructions.size(); if (s + 1 > instructionsToBytecodes.length) { int[] t = new int[instructionsToBytecodes.length * 2]; System.arraycopy(instructionsToBytecodes, 0, t, 0, instructionsToBytecodes.length); instructionsToBytecodes = t; } instructionsToBytecodes[s] = originalBytecode; newInstructions.add(i); newInstructionHandlers.add(mergeHandlers(handlers, additionalHandlers)); } void internalEmitInstruction(IInstruction i) { int s = newInstructions.size(); if (s + 1 > instructionsToBytecodes.length) { int[] t = new int[instructionsToBytecodes.length * 2]; System.arraycopy(instructionsToBytecodes, 0, t, 0, instructionsToBytecodes.length); instructionsToBytecodes = t; } instructionsToBytecodes[s] = originalBytecode; newInstructions.add(i); newInstructionHandlers.add(additionalHandlers); } /** * Emit a list of instructions at the current point in the code. */ public void emit(Instruction[] instrs) { emit(instrs, noHandlers); } /** * Emit a list of instructions with some exception handlers at the current point in the code. All the instructions are covered * by all the handlers. */ public void emit(Instruction[] instrs, ExceptionHandler[] handlers) { if (instrs.length == 0) { return; } codeChanged = true; int s = newInstructions.size(); if (s + instrs.length > instructionsToBytecodes.length) { int[] t = new int[instructionsToBytecodes.length * 2 + instrs.length]; System.arraycopy(instructionsToBytecodes, 0, t, 0, instructionsToBytecodes.length); instructionsToBytecodes = t; } ExceptionHandler[] hs = mergeHandlers(handlers, additionalHandlers); for (int i = 0; i < instrs.length; i++) { instructionsToBytecodes[s + i] = originalBytecode; newInstructions.add(instrs[i]); newInstructionHandlers.add(hs); } } } /** * This class is subclassed for each kind of patch that you want to apply. */ public static abstract class Patch { Patch next; public Patch() { } final Patch insert(Patch next) { this.next = next; return this; } /** * Override this method to emit the code for your patch. */ public abstract void emitTo(Output w); } /** * This must be called before inserting any patches. */ public void beginPass() { verifyState(BEFORE_PASS); state = DURING_PASS; nextLabel = instructions.length; beforePatches = new Patch[instructions.length]; afterPatches = new Patch[instructions.length]; lastAfterPatches = new Patch[instructions.length]; replacementPatches = new Patch[instructions.length]; instructionHandlerPatches = new HandlerPatch[instructions.length]; methodStartPatches = null; afterMethodPatches = null; methodHandlerPatches = null; patchCount = 0; } /** * This must be called after inserting any patches. */ public void endPass() { state = BEFORE_PASS; // lose references to the stored patches so they can be garbage collected beforePatches = null; afterPatches = null; lastAfterPatches = null; replacementPatches = null; instructionHandlerPatches = null; methodStartPatches = null; afterMethodPatches = null; methodHandlerPatches = null; } /** * Allocate a fresh label. This must be called during a pass and not during code emission. */ public int allocateLabel() throws IllegalArgumentException { verifyState(DURING_PASS); return nextLabel++; } /** * Insert code to be executed whenever the method is entered. This code is not protected by any exception handlers (other than * handlers declared in the patch). * * When multiple 'start' patches are given, the last one added is first in execution order. * * @throws IllegalArgumentException if p is null */ public void insertAtStart(Patch p) { if (p == null) { throw new IllegalArgumentException("p is null"); } verifyState(DURING_PASS); methodStartPatches = p.insert(methodStartPatches); patchCount++; } /** * Insert code to be executed before the instruction. Branches to the instruction will branch to this code. Exception handlers * that cover the instruction will be extended to cover the patch. * * When multiple 'before' patches are given, the last one added is first in execution order. * * @throws IllegalArgumentException if p is null */ public void insertBefore(int i, Patch p) { if (p == null) { throw new IllegalArgumentException("p is null"); } verifyState(DURING_PASS); beforePatches[i] = p.insert(beforePatches[i]); patchCount++; } /** * Insert code to be executed after the instruction. This code will only execute if the instruction "falls through". For example, * code inserted after a "goto" will never be executed. Likewise if the instruction throws an execution the 'after' code will not * be executed. Exception handlers that cover the instruction will be extended to cover the patch. * * When multiple 'after' patches are given, the last one added is LAST in execution order. */ public void insertAfter(int i, Patch p) { verifyState(DURING_PASS); try { if (afterPatches[i] == null) { lastAfterPatches[i] = afterPatches[i] = p.insert(null); } else { lastAfterPatches[i].next = p; lastAfterPatches[i] = p.insert(null); } patchCount++; } catch (ArrayIndexOutOfBoundsException e) { throw new IllegalArgumentException("invalid i"); } } /** * Insert code to replace the instruction. Exception handlers that cover the instruction will cover the patch. * * Multiple replacements are not allowed. * * @throws NullPointerException if p is null * @throws IllegalArgumentException if p is null */ public void replaceWith(int i, Patch p) throws NullPointerException { if (p == null) { throw new IllegalArgumentException("p is null"); } verifyState(DURING_PASS); if (replacementPatches[i] != null) { throw new IllegalArgumentException("Instruction " + i + " cannot be replaced more than once"); } replacementPatches[i] = p.insert(null); patchCount++; } /** * An "instruction exception handler" handles exceptions generated by a specific instruction (including patch code that may be * inserted before, after, or instead of the instruction in this pass). If the patch code falls through, control resumes with the * next instruction. This exception handler handles exceptions before any exception handler already attached to the instruction. * Furthermore, the patch itself is covered by the exception handlers already attached to the instruction. * * If multiple instruction exception handlers are given, then the last one added handles the exception first; if an exception is * rethrown, then the next-to-last one added handles that exception, etc. */ public void addInstructionExceptionHandler(int i, String catchClass, Patch p) { verifyState(DURING_PASS); try { instructionHandlerPatches[i] = new HandlerPatch(instructionHandlerPatches[i], catchClass, allocateLabel(), p); patchCount++; } catch (ArrayIndexOutOfBoundsException e) { throw new IllegalArgumentException("invalid i: " + i); } } /** * A "method exception handler" handles exceptions generated anywhere in the method. The patch code must not fall through; it must * return or throw an exception. * * If multiple method exception handlers are given, then the last one added handles the exception first; if an exception is * rethrown, then the next-to-last one added handles that exception, etc. */ public void addMethodExceptionHandler(String catchClass, Patch p) { verifyState(DURING_PASS); if (p == null) { throw new IllegalArgumentException("null p"); } methodHandlerPatches = new HandlerPatch(methodHandlerPatches, catchClass, allocateLabel(), p); patchCount++; } /** * This method inserts code that will be placed after the method body. This code will not be executed normally, but it can emit * label definitions that other patches can branch to. No exception handlers cover this code (other than exception handlers * emitted by patch p itself). * * @throws IllegalArgumentException if p is null */ public void insertAfterBody(Patch p) { if (p == null) { throw new IllegalArgumentException("p is null"); } verifyState(DURING_PASS); afterMethodPatches = p.insert(afterMethodPatches); patchCount++; } /** * @return the MethodData used to create this editor, or null if no MethodData is linked to this editor */ public MethodData getData() { return methodInfo; } private static ExceptionHandler[] makeExceptionArray(HandlerPatch hp) { if (hp == null) { return noHandlers; } else { int count = 0; for (HandlerPatch hpIterator = hp; hpIterator != null; hpIterator = hpIterator.next) { count++; } ExceptionHandler[] patchedHandlers = new ExceptionHandler[count]; count = 0; for (HandlerPatch hpIterator = hp; hpIterator != null; hpIterator = hpIterator.next) { patchedHandlers[count] = new ExceptionHandler(hpIterator.label, hpIterator.catchClass); count++; } return patchedHandlers; } } /** * This method finishes a pass. All code is updated; instructions are reordered and old labels may not be valid. * * If no patches were issued, we don't need to do anything at all; this case is detected quickly and no updates are made. * * @return true iff non-trivial patches were applied */ public boolean applyPatches() throws IllegalArgumentException { verifyState(DURING_PASS); state = EMITTING_CODE; if (patchCount == 0) { state = BEFORE_END_PASS; // no patches issued; drop out return false; } Output w = new Output(nextLabel); int[] oldInstructionsToNew = new int[instructions.length]; w.additionalHandlers = noHandlers; w.originalBytecode = 0; for (Patch p = methodStartPatches; p != null; p = p.next) { p.emitTo(w); } ExceptionHandler[] methodHandlers = makeExceptionArray(methodHandlerPatches); if (methodHandlers.length > 0) { // if a method handler is added, the code has changed even though // the patch might not emit any actual code w.codeChanged = true; } for (int i = 0; i < instructions.length; i++) { ExceptionHandler[] basicHandlers = mergeHandlers(handlers[i], methodHandlers); HandlerPatch hp = instructionHandlerPatches[i]; w.emitLabel(i); w.originalBytecode = instructionsToBytecodes[i]; w.additionalHandlers = basicHandlers; for (Patch p = beforePatches[i]; p != null; p = p.next) { p.emitTo(w); } w.additionalHandlers = mergeHandlers(makeExceptionArray(hp), basicHandlers); Patch replace = replacementPatches[i]; if (replace == null) { oldInstructionsToNew[i] = w.newInstructions.size(); w.internalEmitInstruction(instructions[i]); } else { // a patch might delete an instruction, in which case the // code is definitely modified w.codeChanged = true; oldInstructionsToNew[i] = -1; replace.emitTo(w); } w.additionalHandlers = basicHandlers; for (Patch p = afterPatches[i]; p != null; p = p.next) { p.emitTo(w); } if (hp != null) { // if an instruction handler is added, the code has changed even though // the patch might not emit any actual code w.codeChanged = true; GotoInstruction branchOver = GotoInstruction.make(i + 1); w.internalEmitInstruction(branchOver); for (HandlerPatch hpIterator = hp; hpIterator != null; hpIterator = hpIterator.next) { w.additionalHandlers = mergeHandlers(makeExceptionArray(hpIterator.next), basicHandlers); w.emitLabel(hpIterator.label); hpIterator.patch.emitTo(w); w.internalEmitInstruction(branchOver); } } } w.originalBytecode = 0; for (HandlerPatch hpIterator = methodHandlerPatches; hpIterator != null; hpIterator = hpIterator.next) { w.additionalHandlers = makeExceptionArray(hpIterator.next); w.emitLabel(hpIterator.label); hpIterator.patch.emitTo(w); } w.additionalHandlers = noHandlers; for (Patch p = afterMethodPatches; p != null; p = p.next) { p.emitTo(w); } state = BEFORE_END_PASS; if (!w.codeChanged) { return false; } instructions = new Instruction[w.newInstructions.size()]; handlers = new ExceptionHandler[instructions.length][]; instructionsToBytecodes = new int[instructions.length]; w.newInstructions.toArray(instructions); w.newInstructionHandlers.toArray(handlers); System.arraycopy(w.instructionsToBytecodes, 0, instructionsToBytecodes, 0, instructionsToBytecodes.length); int[] labelDefs = w.labelDefs; int[] newInstructionsToOld = new int[instructions.length]; for (int i = 0; i < instructions.length; i++) { instructions[i] = instructions[i].redirectTargets(labelDefs); newInstructionsToOld[i] = -1; } // We want to update each exception handler array exactly once IdentityHashMap<ExceptionHandler, Object> adjustedHandlers = null; for (int i = 0; i < handlers.length; i++) { ExceptionHandler[] hs = handlers[i]; if (hs.length > 0 && (i == 0 || hs != handlers[i - 1])) { if (adjustedHandlers == null) { adjustedHandlers = new IdentityHashMap<ExceptionHandler, Object>(); } for (int j = 0; j < hs.length; j++) { ExceptionHandler h = hs[j]; if (!adjustedHandlers.containsKey(h)) { adjustedHandlers.put(h, null); h.handler = labelDefs[h.handler]; // breaks invariant of ExceptionHandler: immutable! } } } } if (methodInfo != null) { for (int i = 0; i < oldInstructionsToNew.length; i++) { if (oldInstructionsToNew[i] != -1) { newInstructionsToOld[oldInstructionsToNew[i]] = i; } } methodInfo.update(instructions, handlers, newInstructionsToOld, instructionsToBytecodes); } return true; } /** * Apply Visitor v to each instruction in the code, for the purpose of patching the code. */ public void visitInstructions(Visitor v) { verifyState(DURING_PASS); for (int i = 0; i < instructions.length; i++) { v.setIndex(this, i); instructions[i].visit(v); } } /** * A specialized Instruction.Visitor providing convenience methods for inserting patches. In particular it maintains a notion of * the "current position" in the code array. */ public static class Visitor extends IInstruction.Visitor { private int index; private MethodEditor editor; /** * Set the current editor and instruction index for this visitor. */ final public void setIndex(MethodEditor e, int i) { index = i; editor = e; } /** * @return the index of the current instruction in the code array */ final public int getIndex() { return index; } /** * Insert a patch after the current instruction in the code. */ final public void insertAfter(Patch p) { editor.insertAfter(index, p); } /** * Insert a patch before the current instruction in the code. */ final public void insertBefore(Patch p) { editor.insertBefore(index, p); } /** * Replace the current instruction in the code with a patch. */ final public void replaceWith(Patch p) { editor.replaceWith(index, p); } /** * Add an exception handler to the current instruction. * * @param catchClass the JVM type for the exception to be caught (e.g., Ljava.io.IOException;), or null to catch all exceptions * @param p the code to handle the exception */ final public void addInstructionExceptionHandler(String catchClass, Patch p) { editor.addInstructionExceptionHandler(index, catchClass, p); } } }