/* Software Name : AsmDex * Version : 1.0 * * Copyright © 2012 France Télécom * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ package org.ow2.asmdex; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.ow2.asmdex.instruction.IOffsetInstruction; import org.ow2.asmdex.instruction.IPseudoInstruction; import org.ow2.asmdex.instruction.Instruction; import org.ow2.asmdex.instruction.InstructionFormat10X; import org.ow2.asmdex.instruction.InstructionFormat20T; import org.ow2.asmdex.instruction.InstructionFormat30T; import org.ow2.asmdex.instruction.PseudoInstructionFillArrayData; import org.ow2.asmdex.instruction.PseudoInstructionPackedSwitch; import org.ow2.asmdex.instruction.PseudoInstructionSparseSwitch; import org.ow2.asmdex.lowLevelUtils.InstructionEncoder; import org.ow2.asmdex.structureCommon.Label; import org.ow2.asmdex.structureCommon.LocalVariable; import org.ow2.asmdex.structureWriter.AnnotationItem; import org.ow2.asmdex.structureWriter.CodeItem; import org.ow2.asmdex.structureWriter.ConstantPool; import org.ow2.asmdex.structureWriter.ExceptionHandler; import org.ow2.asmdex.structureWriter.Field; import org.ow2.asmdex.structureWriter.Method; import org.ow2.asmdex.structureWriter.Prototype; import org.ow2.asmdex.structureWriter.TryCatch; /** * Class that takes care of visiting all the elements of a method. * <br /><br /> * RegisterSize, Incoming Arguments Size, Outgoing Arguments Size<br /> * --------------------------------------------------------------<br /> * The RegisterSize should be calculated like that : * <ul> * <li> First, the local registers (watching the pair registers according to the opcodes !).</li> * <li> Then, "this" if the method is non-static AND not a constructor.</li> * <li> Then, the parameters (1 (word) for int... 2 for double, long, references).</li> * </ul> * But we can't calculate it because we don't know if "this" or the parameters are used. * So we have to rely on visitMaxs() to give us the RegisterSize. * <br /><br /> * The Incoming Argument Size : calculating it consists in parsing the descriptor, minus the * return type, and counting how many bytes are allocated to each type (I = 2 (bytes), D = 4...). * Add 2 for "this", unless it's a Static method. * <br /><br /> * The Outgoing Arguments Size : a bit the same. Each time a Method is called inside the current * Method, calculates how many bytes are allocated to each type (I = 2 bytes, D = 4, L = 4...) in the * descriptor, minus the return type. Add 2 for "this", unless the called method is Static. * <br /><br /> * Labels Management<br /> * -----------------<br /> * <ul> * <li>After the first pass, at the end of the visit of the Method (visitEnd), we know where all the labels * are, IN THE ORIGINAL CODE.</li> * <li> The unconditional Jump instructions are encoded as they are given. They will of course be * extended if needed.</li> * <li> We must know what are the instructions that refer to the Labels, in order to check if their * range is enough. So each Label has a list of the instructions making reference to it and that are * subject to be too far (Unconditional Jumps only. Conditional jumps are always 16 bits which should * be enough, if not, there is no 32 bits jump, this case is NOT handled for now. Switch cases are 32 bits).</li> * <li> So such instruction must know what is its offset inside the code. They derive from OffsetInstruction * which declares the offsetInstruction field.</li> * <li> We scan all the OffsetInstructions inside each Label, and checks if the range to their target Label * is valid. If not, we transform the Instruction to a one with a bigger range (if possible), shift all * the Labels and OffsetInstructions that are beyond this Instruction accordingly, and make the scan * once again.</li> * </ul> * * @author Julien Névo */ public class MethodWriter extends MethodVisitor { static final int MAXIMUM_SIGNED_VALUE_8_BITS = 0x7f; static final int MAXIMUM_SIGNED_VALUE_16_BITS = 0x7fff; static final int MINIMUM_SIGNED_VALUE_16_BITS = -0x8000; static final int MAXIMUM_SIGNED_VALUE_32_BITS = 0x7fffffff; /** * The Class Writer of this Method Writer. */ private ClassWriter classWriter; /** * The Constant Pool of the Application. */ private ConstantPool constantPool; /** * Contains all the information about the Method. */ private Method method; /** * List of the Pseudo Instruction encountered, linked with a Label whose offset must be set. * Elements are added as we encounter switches and arrays to fill, and will be parsed at * the end of the Method, in order to actually write the structures and resolve the Labels. */ private ArrayList<Instruction> pseudoInstructionList = new ArrayList<Instruction>(); /** * Indicates the next Line Number. It is assigned to the next Instruction found, then set to 0 at * this moment, as a Line Number must be superior to 0. This Line Number is set when visiting a * Line Number. */ private int nextLineNumber = 0; /** * Constructor of the Method Writer. Requires the data of the Method invocation. * @param classWriter the classWriter of the method. * @param access the access flags of the method. * @param name the name of the method. * @param desc the descriptor of the method. * @param signature the signature of the method. May be Null. * @param exceptions the exceptions of the method. May be Null. */ public MethodWriter(ClassWriter classWriter, int access, String name, String desc, String[] signature, String[] exceptions) { super(Opcodes.ASM4); this.classWriter = classWriter; constantPool = classWriter.getConstantPool(); // Adds the method to the constant pool. method = constantPool.addMethodToConstantPool(name, classWriter.getName(), desc, access, signature, exceptions); CodeItem codeItem = getCodeItem(); if (codeItem != null) { // Calculate the Incoming Argument Size. This consists in parsing the descriptor, minus the // return type, and counting how many bytes are allocated to each type (I = 2, D = 4...). // Static methods don't have a "this" argument (in bytes). int ias = (access & Opcodes.ACC_STATIC) > 0 ? 0 : 2; // We now parse the descriptor, minus the return type. ias += Prototype.getSizeOfDescriptor(desc, true); codeItem.setIncomingArgumentsSizeInWord(ias / 2); } } // ----------------------------------------------------- // Getters and Setters. // ----------------------------------------------------- /** * Returns the ClassWriter of this MethodWriter. */ public ClassWriter getClassWriter() { return classWriter; } /** * Sets the start of the bytecode to copy from the input Dex file to the output. * This is only useful when using the optimization that consists in copying part of * the Constant Pool and the bytecode of methods that doesn't change, if the Reader is linked * to the Writer with no Adapter to modify the methods in between. * @param start start in bytes from the beginning of the Dex file where the bytecode is. This * includes the code_item header. */ public void setStartBytecodeToCopy(int start) { method.setStartBytecodeToCopy(start); } /** * Returns the Method linked to this Writer. * @return the Method linked to this Writer. */ public Method getMethod() { return method; } /** * Returns the Code Item linked to this Writer. May be Null if it hasn't any (if abstract or interface). * @return the Code Item linked to this Writer, or Null. */ public CodeItem getCodeItem() { return method.getCodeItem(); } // ----------------------------------------------------- // Public Methods. // ----------------------------------------------------- /** * Adds a label to the set of used labels. If Null, nothing is done. * @param label the label to add. */ public void addLabel(Label label) { getCodeItem().addLabel(label); } /** * Adds an instruction to the current Code Item. Also adds the Line Number to the * Instruction, and resets it. * @param insn instruction to add to the Code Item. */ public void addInstruction(Instruction insn) { getCodeItem().addInstruction(insn); if (nextLineNumber > 0) { insn.setLineNumber(nextLineNumber); nextLineNumber = 0; } } /** * Adds padding at the end of the file, using an alignment of 4, if necessary. * The padding is done using a NOP Instruction. * @return the padding added, in bytes. */ public int addPadding() { CodeItem codeItem = getCodeItem(); int padding = codeItem.getSize() % 4; if (padding != 0) { if (padding == 2) { codeItem.addInstruction(new InstructionFormat10X(0)); } else { throw new RuntimeException("Padding can only be 0 or 2 ! (" + method.getMethodName() + " " + method.getClassName() + " " + codeItem.getSize() + " " + padding + ")"); } } return padding; } // ----------------------------------------------------- // Visitor Methods. // ----------------------------------------------------- @Override public void visitCode() { if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.println(" MethodWriter : visitCode."); } } @Override public void visitEnd() { if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.println(" MethodWriter : visitEnd."); } // Now that the annotations are fully known, we can register them to the Constant Pool. closeAnnotations(); // There's no need to check the references, parse the pseudo-instructions and generate bytecode // if there aren't any code. // This can also be the case when the ConstantPool optimization is on, as no bytecode is parsed. // We can free the code_item and debug_item from their stored instructions, labels and so on, in // order to save memory. CodeItem codeItem = getCodeItem(); if (codeItem == null) { return; } if (codeItem.getSize() == 0) { method.free(); return; } // We check if the Instructions referring the Labels all have a valid range. // If not, the Instruction are modified. checkAndCorrectLabelReferences(); // Then, we may have Pseudo Instructions to add at the end. // We do that after the Labels correction on purpose : the Pseudo Instructions may need // padding. It is simpler to have the final code structure before adding these Instructions, // so that we don't have to add/remove padding each time an Instruction is modified. //CodeItem codeItem = getCodeItem(); for (Instruction insn : pseudoInstructionList) { addPadding(); IPseudoInstruction ps = (IPseudoInstruction)insn; // Resolves the Label. Gets it from the Instruction that is linked to the Pseudo // Instruction and sets its offset now that we know it. Label label = ps.getSourceInstruction().getLabel(); label.setOffset(codeItem.getSize()); addInstruction(insn); } // We generate the bytecode now, so that we can free the code_item and debug_item from their // stored instructions, labels and so on, in order to save memory. method.generateCodeItemCode(); method.free(); } @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, int[] arguments) { if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.print(" MethodWriter : visitMethodInsn(). Opcode = 0x" + Integer.toHexString(opcode) + ", owner = " + owner + ", name = " + name + ", desc = " + desc + ", parameters = "); for (int parameter : arguments) { System.out.print(parameter + ", "); } System.out.println(); } // We found an instruction related to a Method (call...). This Method could have been found before, or // not, in which case Constant Pool adds it. In both cases, we make the newly found Instruction // refer to this Method. Method newMethod = constantPool.addMethodToConstantPool(name, owner, desc, Opcodes.ACC_UNKNOWN, null, null); Instruction insn = InstructionEncoder.encodeMethodInsn(opcode, newMethod, arguments); addInstruction(insn); CodeItem codeItem = getCodeItem(); // Calculates the Outgoing Arguments Size. int storedOutgoingSize = codeItem.getOutgoingArgumentsSizeInWord(); // We now parse the descriptor, minus the return type. int outgoingSize = (Prototype.getSizeOfDescriptor(desc, true) / 2); // If the opcode of the call shows a non-static call, we have to count "this" in // the current Outgoing Arguments size. if (opcode != Opcodes.INSN_INVOKE_STATIC && opcode != Opcodes.INSN_INVOKE_STATIC_RANGE) { outgoingSize++; } if (outgoingSize > storedOutgoingSize) { codeItem.setOutgoingArgumentsSizeInWord(outgoingSize); } // We add the Prototype to the Prototype pool. Prototype prototype = constantPool.addPrototypeToConstantPool(desc); // Adds the Types to the constant pool. Then can be found in the newly created Prototype. constantPool.addTypeListToConstantPool(prototype.getParameterTypes()); } @Override public void visitParameters(String[] parameters) { if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.print(" MethodWriter : visitParameters(). Parameters = "); if (parameters != null) { for (String parameter : parameters) { System.out.print(parameter + ", "); } } System.out.println(); } // Adds the Parameters as Strings in the constant pool, but ONLY if there are not empty. // As we don't refer to them in the Debug Item, it is not useful to encode an empty String. if (parameters != null) { for (String parameter : parameters) { if (!parameter.equals("")) { constantPool.addStringToConstantPool(parameter); } } } method.setParameters(parameters); } @Override public void visitVarInsn(int opcode, int destinationRegister, int var) { if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.println(" MethodWriter : visitVarInsn(). Opcode = 0x" + Integer.toHexString(opcode) + ", destinationRegister = " + destinationRegister + ", var = 0x" + Integer.toHexString(var) ); } Instruction insn = InstructionEncoder.encodeVarInsn(opcode, destinationRegister, var); addInstruction(insn); } @Override public void visitVarInsn(int opcode, int destinationRegister, long var) { if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.println(" MethodWriter : visitVarInsn(). Opcode = 0x" + Integer.toHexString(opcode) + ", destinationRegister = " + destinationRegister + ", var (long) = 0x" + Long.toHexString(var) ); } Instruction insn = InstructionEncoder.encodeVarInsn(opcode, destinationRegister, var); addInstruction(insn); } @Override public void visitInsn(int opcode) { if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.println(" MethodWriter : visitInsn(). Opcode = 0x" + Integer.toHexString(opcode)); } addInstruction(InstructionEncoder.encodeInsn(opcode)); } @Override public void visitLabel(Label label) { CodeItem codeItem = getCodeItem(); label.setOffset(codeItem.getSize()); addLabel(label); if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.println(" MethodWriter : visitLabel(). Label = " + label); } } @Override public void visitOperationInsn(int opcode, int destinationRegister, int firstSourceRegister, int secondSourceRegister, int value) { if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.println(" MethodWriter : visitOperationInsn(). Opcode = 0x" + Integer.toHexString(opcode) + ", destinationRegister = " + destinationRegister + ", firstSourceRegister = " + firstSourceRegister + ", secondSourceRegister = " + secondSourceRegister + ", value = 0x" + Integer.toHexString(value) ); } Instruction insn = InstructionEncoder.encodeOperationInsn(opcode, destinationRegister, firstSourceRegister, secondSourceRegister, value); addInstruction(insn); } @Override public void visitIntInsn(int opcode, int register) { if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.println(" MethodWriter : visitIntInsn(). Opcode = 0x" + Integer.toHexString(opcode) + ", operand = " + register); } Instruction insn = InstructionEncoder.encodeIntInsn(opcode, register); addInstruction(insn); } @Override public void visitJumpInsn(int opcode, Label label, int registerA, int registerB) { if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.println(" MethodWriter : visitJumpInsn(). Opcode = 0x" + Integer.toHexString(opcode) + ", label = " + label + ", destinationRegister = " + registerA + ", firstSourceRegister = " + registerB); } // Note that we also provide the instruction offset so that the instruction knows its offset, // to help calculate later if the Label it refers is within range or not. CodeItem codeItem = getCodeItem(); Instruction insn = InstructionEncoder.encodeJumpInsn(opcode, label, registerA, registerB, codeItem.getSize()); // Make the label refer to the instruction, at this specific offset. label.addReferringInstruction(insn); addInstruction(insn); addLabel(label); } @Override public void visitFillArrayDataInsn(int arrayReference, Object[] arrayData) { if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.print(" MethodWriter : visitFillArrayDataInsn(). " + "ArrayReference = " + Integer.toHexString(arrayReference) + ", arrayData = "); for (Object data : arrayData) { System.out.print(data + ", "); } System.out.println(); } // The array data are given by the parameters. However, they are not encoded directly in the // Dalvik instruction now, but as a reference. First we encode the fill-array-data instruction, // with a link to a Label where we will encode the array. CodeItem codeItem = getCodeItem(); Label arrayLabel = new Label(); Instruction insn = InstructionEncoder.encodeFillArrayDataInsn(0x26, arrayReference, arrayLabel, codeItem.getSize()); addInstruction(insn); // Adds the fill-array-data pseudo instruction in a List to be parsed at the end of the // method. We'll encode the array here. Instruction ps = new PseudoInstructionFillArrayData(arrayData, (IOffsetInstruction)insn); pseudoInstructionList.add(ps); } @Override public void visitTypeInsn(int opcode, int destinationRegister, int referenceBearingRegister, int sizeRegister, String type) { if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.println(" MethodWriter : visitTypeInsn(). Opcode = 0x" + Integer.toHexString(opcode) + ", destinationRegister = " + destinationRegister + ", refBearingRegister = " + referenceBearingRegister + ", sizeRegister = " + sizeRegister + ", type = " + type); } // Registers the given Type. constantPool.addTypeToConstantPool(type); Instruction insn = InstructionEncoder.encodeTypeInsn(opcode, destinationRegister, referenceBearingRegister, sizeRegister, type); addInstruction(insn); } @Override public void visitMultiANewArrayInsn(String desc, int[] registers) { if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.print(" MethodWriter : visitMultiANewArrayInsn(). " + "Desc = " + desc + ", registers = "); for (int register : registers) { System.out.print("0x" + Integer.toHexString(register) + ", "); } System.out.println(); } // Registers the given Type. constantPool.addTypeToConstantPool(desc); Instruction insn = InstructionEncoder.encodeMultiANewArrayInsn(desc, registers); addInstruction(insn); } @Override public void visitTableSwitchInsn(int register, int min, int max, Label dflt, Label[] labels) { if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.print(" MethodWriter : visitTableSwitchInsn(). " + "Register = 0x" + Integer.toHexString(register) + ", min = " + min + ", max = " + max + ", default = " + dflt + ". Labels = " ); for (Label label : labels) { System.out.print(label + ", "); } System.out.println(); } // The max parameter is not used (can be calculated if needed). // The Default Label is also not used. It is not encoded in the Table (unless to fill the "holes" // in a Switch/Case, but it then appears as normal entries in the Table). The Default code is // encoded just after the Switch/Case. // The Table Switch is composed of the Instruction, plus the Table (the Pseudo Instruction). // However, this one is not encoded directly in the Instruction, but as a reference. // First we encode the Instruction, with a Label that has no offset for now. Label switchLabel = new Label(); CodeItem codeItem = getCodeItem(); Instruction insn = InstructionEncoder.encodeTableSwitchInsn(register, switchLabel, codeItem.getSize()); addInstruction(insn); // Adds the packed-switch pseudo instruction in a List to be parsed at the end of the // method. We'll encode the table here. Instruction ps = new PseudoInstructionPackedSwitch(min, labels, (IOffsetInstruction)insn); pseudoInstructionList.add(ps); } @Override public void visitLookupSwitchInsn(int register, Label dflt, int[] keys, Label[] labels) { if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.print(" MethodWriter : visitLookupSwitchInsn(). " + "Register = 0x" + Integer.toHexString(register) + ", default = " + dflt + ". Keys+Labels = " ); for (int i = 0; i < keys.length; i++) { System.out.print(keys[i] +"-->" + labels[i] + ", "); } System.out.println(); } // The Default Label is not used, as it is not encoded in the Table. // The Sparse Switch is composed of the Instruction, plus the Table. However, this one is not encoded // directly in the Instruction, but as a reference. First we encode the Instruction, // with a link to a Label where we will encode the Table. CodeItem codeItem = getCodeItem(); Label switchLabel = new Label(); Instruction insn = InstructionEncoder.encodeSparseSwitchInsn(register, switchLabel, codeItem.getSize()); addInstruction(insn); // Adds the sparse-switch pseudo instruction in a List to be parsed at the end of the // method. We'll encode the table here. Instruction ps = new PseudoInstructionSparseSwitch(keys, labels, (IOffsetInstruction)insn); pseudoInstructionList.add(ps); } @Override public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.println(" MethodWriter : visitTryCatchBlock(). " + "start = " + start + ", end = " + end + ", handler = " + handler); } // Registers the given Type. constantPool.addTypeToConstantPool(type); addLabel(end); addLabel(handler); // A Try/Catch block isn't encoded as an Instruction. We store it as is for it to be encoded // when the code_item has to be. The Try/Catch are encoded after the Instructions. CodeItem codeItem = getCodeItem(); codeItem.addTryCatch(new TryCatch(start, end, new ExceptionHandler(handler, type))); } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.println(" MethodWriter : visitAnnotation(). " + "Desc = " + desc + ", visible = " + visible ); } // Creates an AnnotationItem, incomplete for now (no Elements), and registers it to the Method. // We do not add it to the Constant Pool now because of this incompleteness. AnnotationWriter annotationWriter = AnnotationWriter.createAnnotationWriter(desc, visible, constantPool, null); method.addAnnotationItem(annotationWriter.getAnnotationItem()); return annotationWriter; } @Override public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.println(" MethodWriter : visitParameterAnnotation. Parameter = " + parameter + ", desc = " + desc + ", visible = " + visible ); } // Creates an AnnotationItem, incomplete for now (no Elements), and registers it to the Method. // We do not add it to the Constant Pool now because of this incompleteness. AnnotationItem annotationItem = new AnnotationItem(visible, desc); method.addParameterAnnotationItem(parameter, annotationItem); // Adds the desc to the Constant Pool. constantPool.addTypeToConstantPool(desc); return new AnnotationWriter(constantPool, annotationItem); } @Override public void visitArrayLengthInsn(int destinationRegister, int arrayReferenceBearing) { if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.println(" MethodWriter : visitArrayLength(). " + "destinationRegister = " + destinationRegister + ", arrayReferenceBearing = " + arrayReferenceBearing); } Instruction insn = InstructionEncoder.encodeArrayLength(destinationRegister, arrayReferenceBearing); addInstruction(insn); } @Override public void visitArrayOperationInsn(int opcode, int valueRegister, int arrayRegister, int indexRegister) { if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.println(" MethodWriter : visitArrayOperationInsn(). " + "opcode = " + Integer.toHexString(opcode) + ", valueRegister = " + valueRegister + ", arrayRegister = " + arrayRegister + ", indexRegister = " + indexRegister ); } Instruction insn = InstructionEncoder.encodeArrayOperation(opcode, valueRegister, arrayRegister, indexRegister); addInstruction(insn); } @Override public void visitStringInsn(int opcode, int destinationRegister, String string) { if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.println(" MethodWriter : visitStringInsn(). " + "opcode = " + Integer.toHexString(opcode) + ", destinationRegister = " + destinationRegister + ", string = " + string ); } // Adds the string to the Constant Pool. constantPool.addStringToConstantPool(string); Instruction insn = InstructionEncoder.encodeStringOperation(opcode, destinationRegister, string); addInstruction(insn); } @Override public void visitFieldInsn(int opcode, String owner, String name, String desc, int valueRegister, int objectRegister) { if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.println(" MethodWriter : visitFieldInsn(). " + "opcode = " + Integer.toHexString(opcode) + ", owner = " + owner + ", name = " + name + ", desc = " + desc + ", valueRegister = " + valueRegister + ", objectRegister = " + objectRegister ); } // We found an instruction related to a Field. This Field could have been found before, or not, in // which case the Constant Pool will add it. In both cases, we make the newly found instruction // refer to this Field. Field field = constantPool.addFieldToConstantPool(name, desc, owner, Opcodes.ACC_UNKNOWN, null, null); Instruction insn = InstructionEncoder.encodeFieldInsn(opcode, valueRegister, objectRegister, field); if (insn != null) addInstruction(insn); else throw new RuntimeException("MethodWriter.visitFieldInsn"); } @Override public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { List<Label> ends = null; if (end != null) { ends = new ArrayList<Label>(1); ends.add(end); } visitLocalVariable(name, desc, signature, start, ends, null, index); } @Override public void visitLocalVariable(String name, String desc, String signature, Label start, List<Label> ends, List<Label> restarts, int index) { if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.println(" MethodWriter : visitLocalVariable(). " + "Name = " + name + ", desc = " + desc + ", signature = " + signature + ", start = " + start + ", index = " + index ); System.out.print(" LabelEnds = "); if (ends != null) { for (Label l: ends) { System.out.print(l +" --- "); } } System.out.println(); System.out.print(" LabelRestarts = "); if (restarts != null) { for (Label l: restarts) { System.out.print(l +" --- "); } } System.out.println(); } constantPool.addStringToConstantPool(name); constantPool.addTypeToConstantPool(desc); constantPool.addStringToConstantPool(signature); LocalVariable localVariable = new LocalVariable(index, name, desc, signature, start, ends, restarts); method.addLocalVariable(localVariable); } @Override public void visitLineNumber(int line, Label start) { if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.println(" MethodWriter : visitLineNumber(). " + "Line = " + line + ", start = " + start ); } // We don't use the Start Label. There's always a visitLabel before... nextLineNumber = line; method.setFirstLineNumberIfNeeded(nextLineNumber); } @Override public void visitMaxs(int maxStack, int maxLocals) { if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.println(" MethodWriter : visitMaxs(). MaxStack = " + maxStack); } // maxLocals is ignored by AsmDex. method.getCodeItem().setRegisterSize(maxStack); } @Override public AnnotationVisitor visitAnnotationDefault() { if (ApplicationWriter.DISPLAY_WRITER_INFORMATION) { System.out.println(" MethodWriter : visitAnnotationDefault."); } return AnnotationWriter.createAnnotationWriter(Constants.ANNOTATION_DEFAULT_INTERNAL_NAME, false, constantPool, classWriter.getClassDefinitionItem()); } @Override public void visitAttribute(Object attr) { // Method ignored by AsmDex. Attributes are not supported. } @Override public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { // Method ignored by AsmDex. Attributes are not supported. } // --------------------------------------------------- // Private methods. // --------------------------------------------------- /** * Checks if the Instructions making references to each Label is valid. That is, that the range of * its instruction is enough. If not, corrects it by modifying the Instruction. */ private void checkAndCorrectLabelReferences() { boolean madeChange; CodeItem codeItem = getCodeItem(); List<Label> labels = codeItem.getLabels(); // Parses all the Labels, and all the OffsetInstructions inside the Labels. // We stop the parsing whenever a change of instruction has been made, to restart the parsing // once again till everything is valid. do { madeChange = false; Iterator<Label> labelIterator = labels.iterator(); while (!madeChange && labelIterator.hasNext()) { Label label = labelIterator.next(); ArrayList<Instruction> instructions = label.getReferringInstructions(); int insnIndex = 0; int nbInsn = instructions.size(); while (!madeChange && (insnIndex < nbInsn)) { Instruction insn = instructions.get(insnIndex); IOffsetInstruction offsetInsn = (IOffsetInstruction)insn; int instructionOffset = offsetInsn.getInstructionOffset(); // The offset have to be divided by two, because the encoded offset is word based. // The relativeOffset is the offset for the Instruction to reach the Label. // It is negative if the Label is before the Instruction. int relativeOffset = (label.getOffset() - instructionOffset) / 2; int opcode = insn.getOpcodeByte(); int maximumOffset = 0; // Check if the relative offset is valid for the instruction range. switch (opcode) { case 0x28: // Goto 8 bits. maximumOffset = MAXIMUM_SIGNED_VALUE_8_BITS; break; case 0x29: // Goto 16 bits. case 0x32: // If-test. case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: case 0x38: // If-testz. case 0x39: case 0x3a: case 0x3b: case 0x3c: case 0x3d: maximumOffset = MAXIMUM_SIGNED_VALUE_16_BITS; break; case 0x2a: // Goto 32 bits. case 0x2b: // Packed Switch. case 0x2c: // Sparse Switch. maximumOffset = MAXIMUM_SIGNED_VALUE_32_BITS; break; default: try { throw new Exception("Opcode error : 0x" + Integer.toHexString(opcode)); } catch (Exception e) { e.printStackTrace(); } } int minimumOffset = -maximumOffset - 1; if ((relativeOffset < minimumOffset) || (relativeOffset > maximumOffset)) { // Must change to an Instruction with a bigger range. This is only possible for // the GOTO 8/16 bits. if ((opcode == Opcodes.INSN_GOTO) || (opcode == Opcodes.INSN_GOTO_16)) { Instruction newInsn; // Change to 16 or 32 bits ? if ((relativeOffset > MAXIMUM_SIGNED_VALUE_16_BITS) || (relativeOffset < MINIMUM_SIGNED_VALUE_16_BITS)) { // 32 bits. newInsn = new InstructionFormat30T(Opcodes.INSN_GOTO_32, label, instructionOffset); } else { // 16 bits. newInsn = new InstructionFormat20T(Opcodes.INSN_GOTO_16, label, instructionOffset); } // Removes the instruction from the codeItem and replaces it with the new one. codeItem.replaceInstructions(insn, newInsn); // Replaces the old instruction with the new one in the Label Instructions list. instructions.remove(insnIndex); instructions.add(insnIndex, newInsn); // Shifts all the Jump related instructions and the labels AFTER this instruction. shiftOffsetInstructionsAndLabels(instructionOffset, newInsn.getSize() - insn.getSize()); madeChange = true; } else { try { throw new IllegalArgumentException("Instruction Range extension unhandled. Opcode : 0x" + Integer.toHexString(opcode)); } catch (Exception e) { e.printStackTrace(); } } } insnIndex++; } } } while (madeChange); } /** * Shifts all the Offset Instructions and Labels of a given number of bytes, from the <i>exclusive</i> * offset given. All labels and instructions below or equal are ignored. * @param shiftOffset <i>exclusive</i> offset from which the shift begins. * @param shiftSize shift in bytes to add. */ private void shiftOffsetInstructionsAndLabels(int shiftOffset, int shiftSize) { if (shiftSize != 0) { CodeItem codeItem = getCodeItem(); List<Label> labels = codeItem.getLabels(); for (Label label : labels) { // Shifts the Label offset if needed. int labelOffset = label.getOffset(); if (labelOffset > shiftOffset) { label.setOffset(labelOffset + shiftSize); } // Scans all its instructions and shifts them if needed. for (Instruction instruction : label.getReferringInstructions()) { IOffsetInstruction offsetInstruction = (IOffsetInstruction)instruction; int instructionOffset = offsetInstruction.getInstructionOffset(); if (instructionOffset > shiftOffset) { offsetInstruction.setInstructionOffset(instructionOffset + shiftSize); } } } } } /** * Closes and registers the annotation_set_items and annotation_set_ref_items of this Method. * This must only be done once when the Method has been fully visited. */ public void closeAnnotations() { method.closeAnnotations(constantPool); } }