Home | History | Annotate | Download | only in preverify
      1 /*
      2  * ProGuard -- shrinking, optimization, obfuscation, and preverification
      3  *             of Java bytecode.
      4  *
      5  * Copyright (c) 2002-2009 Eric Lafortune (eric (at) graphics.cornell.edu)
      6  *
      7  * This program is free software; you can redistribute it and/or modify it
      8  * under the terms of the GNU General Public License as published by the Free
      9  * Software Foundation; either version 2 of the License, or (at your option)
     10  * any later version.
     11  *
     12  * This program is distributed in the hope that it will be useful, but WITHOUT
     13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     14  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
     15  * more details.
     16  *
     17  * You should have received a copy of the GNU General Public License along
     18  * with this program; if not, write to the Free Software Foundation, Inc.,
     19  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
     20  */
     21 package proguard.preverify;
     22 
     23 import proguard.classfile.*;
     24 import proguard.classfile.attribute.*;
     25 import proguard.classfile.attribute.visitor.*;
     26 import proguard.classfile.editor.CodeAttributeComposer;
     27 import proguard.classfile.instruction.*;
     28 import proguard.classfile.instruction.visitor.InstructionVisitor;
     29 import proguard.classfile.util.SimplifiedVisitor;
     30 import proguard.classfile.visitor.*;
     31 import proguard.optimize.peephole.BranchTargetFinder;
     32 
     33 /**
     34  * This AttributeVisitor inlines local subroutines (jsr/ret) in the code
     35  * attributes that it visits.
     36  *
     37  * @author Eric Lafortune
     38  */
     39 public class CodeSubroutineInliner
     40 extends      SimplifiedVisitor
     41 implements   AttributeVisitor,
     42              InstructionVisitor,
     43              ExceptionInfoVisitor
     44 {
     45     //*
     46     private static final boolean DEBUG = false;
     47     /*/
     48     private static       boolean DEBUG = true;
     49     //*/
     50 
     51 
     52     private final BranchTargetFinder    branchTargetFinder    = new BranchTargetFinder();
     53     private final CodeAttributeComposer codeAttributeComposer = new CodeAttributeComposer(true);
     54 
     55     private ExceptionInfoVisitor subroutineExceptionInliner = this;
     56     private int                  clipStart                  = 0;
     57     private int                  clipEnd                    = Integer.MAX_VALUE;
     58 
     59 
     60     // Implementations for AttributeVisitor.
     61 
     62     public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
     63 
     64 
     65     public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
     66     {
     67 //        DEBUG =
     68 //            clazz.getName().equals("abc/Def") &&
     69 //            method.getName(clazz).equals("abc");
     70 
     71         // TODO: Remove this when the subroutine inliner has stabilized.
     72         // Catch any unexpected exceptions from the actual visiting method.
     73         try
     74         {
     75             // Process the code.
     76             visitCodeAttribute0(clazz, method, codeAttribute);
     77         }
     78         catch (RuntimeException ex)
     79         {
     80             System.err.println("Unexpected error while inlining subroutines:");
     81             System.err.println("  Class       = ["+clazz.getName()+"]");
     82             System.err.println("  Method      = ["+method.getName(clazz)+method.getDescriptor(clazz)+"]");
     83             System.err.println("  Exception   = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")");
     84 
     85             if (DEBUG)
     86             {
     87                 method.accept(clazz, new ClassPrinter());
     88             }
     89 
     90             throw ex;
     91         }
     92     }
     93 
     94 
     95     public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute)
     96     {
     97         branchTargetFinder.visitCodeAttribute(clazz, method, codeAttribute);
     98 
     99         // Don't bother if there aren't any subroutines anyway.
    100         if (!containsSubroutines(codeAttribute))
    101         {
    102             return;
    103         }
    104 
    105         if (DEBUG)
    106         {
    107             System.out.println("SubroutineInliner: processing ["+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"]");
    108         }
    109 
    110         // Append the body of the code.
    111         codeAttributeComposer.reset();
    112         codeAttributeComposer.beginCodeFragment(codeAttribute.u4codeLength);
    113 
    114         // Copy the non-subroutine instructions.
    115         int offset  = 0;
    116         while (offset < codeAttribute.u4codeLength)
    117         {
    118             Instruction instruction = InstructionFactory.create(codeAttribute.code, offset);
    119             int instructionLength = instruction.length(offset);
    120 
    121             // Is this returning subroutine?
    122             if (branchTargetFinder.isSubroutine(offset) &&
    123                 branchTargetFinder.isSubroutineReturning(offset))
    124             {
    125                 // Skip the subroutine.
    126                 if (DEBUG)
    127                 {
    128                     System.out.println("  Skipping original subroutine instruction "+instruction.toString(offset));
    129                 }
    130 
    131                 // Append a label at this offset instead.
    132                 codeAttributeComposer.appendLabel(offset);
    133             }
    134             else
    135             {
    136                 // Copy the instruction, inlining any subroutine call recursively.
    137                 instruction.accept(clazz, method, codeAttribute, offset, this);
    138             }
    139 
    140             offset += instructionLength;
    141         }
    142 
    143         // Copy the exceptions. Note that exceptions with empty try blocks
    144         // are automatically removed.
    145         codeAttribute.exceptionsAccept(clazz,
    146                                        method,
    147                                        subroutineExceptionInliner);
    148 
    149         if (DEBUG)
    150         {
    151             System.out.println("  Appending label after code at ["+offset+"]");
    152         }
    153 
    154         // Append a label just after the code.
    155         codeAttributeComposer.appendLabel(codeAttribute.u4codeLength);
    156 
    157         // End and update the code attribute.
    158         codeAttributeComposer.endCodeFragment();
    159         codeAttributeComposer.visitCodeAttribute(clazz, method, codeAttribute);
    160     }
    161 
    162 
    163     /**
    164      * Returns whether the given code attribute contains any subroutines.
    165      */
    166     private boolean containsSubroutines(CodeAttribute codeAttribute)
    167     {
    168         for (int offset = 0; offset < codeAttribute.u4codeLength; offset++)
    169         {
    170             if (branchTargetFinder.isSubroutineInvocation(offset))
    171             {
    172                 return true;
    173             }
    174         }
    175 
    176         return false;
    177     }
    178 
    179 
    180     /**
    181      * Appends the specified subroutine.
    182      */
    183     private void inlineSubroutine(Clazz         clazz,
    184                                   Method        method,
    185                                   CodeAttribute codeAttribute,
    186                                   int           subroutineInvocationOffset,
    187                                   int           subroutineStart)
    188     {
    189         int subroutineEnd = branchTargetFinder.subroutineEnd(subroutineStart);
    190 
    191         if (DEBUG)
    192         {
    193             System.out.println("  Inlining subroutine ["+subroutineStart+" -> "+subroutineEnd+"] at ["+subroutineInvocationOffset+"]");
    194         }
    195 
    196         // Don't go inlining exceptions that are already applicable to this
    197         // subroutine invocation.
    198         ExceptionInfoVisitor oldSubroutineExceptionInliner = subroutineExceptionInliner;
    199         int                  oldClipStart                  = clipStart;
    200         int                  oldClipEnd                    = clipEnd;
    201 
    202         subroutineExceptionInliner =
    203             new ExceptionExcludedOffsetFilter(subroutineInvocationOffset,
    204                                               subroutineExceptionInliner);
    205         clipStart = subroutineStart;
    206         clipEnd   = subroutineEnd;
    207 
    208         codeAttributeComposer.beginCodeFragment(codeAttribute.u4codeLength);
    209 
    210         // Copy the subroutine instructions, inlining any subroutine calls
    211         // recursively.
    212         codeAttribute.instructionsAccept(clazz,
    213                                          method,
    214                                          subroutineStart,
    215                                          subroutineEnd,
    216                                          this);
    217 
    218         if (DEBUG)
    219         {
    220             System.out.println("    Appending label after inlined subroutine at ["+subroutineEnd+"]");
    221         }
    222 
    223         // Append a label just after the code.
    224         codeAttributeComposer.appendLabel(subroutineEnd);
    225 
    226         // Inline the subroutine exceptions.
    227         codeAttribute.exceptionsAccept(clazz,
    228                                        method,
    229                                        subroutineStart,
    230                                        subroutineEnd,
    231                                        subroutineExceptionInliner);
    232 
    233         // We can again inline exceptions that are applicable to this
    234         // subroutine invocation.
    235         subroutineExceptionInliner = oldSubroutineExceptionInliner;
    236         clipStart                  = oldClipStart;
    237         clipEnd                    = oldClipEnd;
    238 
    239         codeAttributeComposer.endCodeFragment();
    240     }
    241 
    242 
    243     // Implementations for InstructionVisitor.
    244 
    245     public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction)
    246     {
    247         // Append the instruction.
    248         codeAttributeComposer.appendInstruction(offset, instruction.shrink());
    249     }
    250 
    251 
    252     public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
    253     {
    254         byte opcode = variableInstruction.opcode;
    255         if (opcode == InstructionConstants.OP_RET)
    256         {
    257             // Is the return instruction the last instruction of the subroutine?
    258             if (branchTargetFinder.subroutineEnd(offset) == offset + variableInstruction.length(offset))
    259             {
    260                 if (DEBUG)
    261                 {
    262                     System.out.println("    Replacing subroutine return at ["+offset+"] by a label");
    263                 }
    264 
    265                 // Append a label at this offset instead of the subroutine return.
    266                 codeAttributeComposer.appendLabel(offset);
    267             }
    268             else
    269             {
    270                 if (DEBUG)
    271                 {
    272                     System.out.println("    Replacing subroutine return at ["+offset+"] by a simple branch");
    273                 }
    274 
    275                 // Replace the instruction by a branch.
    276                 Instruction replacementInstruction =
    277                     new BranchInstruction(InstructionConstants.OP_GOTO,
    278                                           branchTargetFinder.subroutineEnd(offset) - offset).shrink();
    279 
    280                 codeAttributeComposer.appendInstruction(offset, replacementInstruction);
    281             }
    282         }
    283         else if (branchTargetFinder.isSubroutineStart(offset))
    284         {
    285             if (DEBUG)
    286             {
    287                 System.out.println("    Replacing first subroutine instruction at ["+offset+"] by a label");
    288             }
    289 
    290             // Append a label at this offset instead of saving the subroutine
    291             // return address.
    292             codeAttributeComposer.appendLabel(offset);
    293         }
    294         else
    295         {
    296             // Append the instruction.
    297             codeAttributeComposer.appendInstruction(offset, variableInstruction);
    298         }
    299     }
    300 
    301 
    302     public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
    303     {
    304         byte opcode = branchInstruction.opcode;
    305         if (opcode == InstructionConstants.OP_JSR ||
    306             opcode == InstructionConstants.OP_JSR_W)
    307         {
    308             int branchOffset = branchInstruction.branchOffset;
    309             int branchTarget = offset + branchOffset;
    310 
    311             // Is the subroutine ever returning?
    312             if (branchTargetFinder.isSubroutineReturning(branchTarget))
    313             {
    314                 // Append a label at this offset instead of the subroutine invocation.
    315                 codeAttributeComposer.appendLabel(offset);
    316 
    317                 // Inline the invoked subroutine.
    318                 inlineSubroutine(clazz,
    319                                  method,
    320                                  codeAttribute,
    321                                  offset,
    322                                  branchTarget);
    323             }
    324             else
    325             {
    326                 if (DEBUG)
    327                 {
    328                     System.out.println("Replacing subroutine invocation at ["+offset+"] by a simple branch");
    329                 }
    330 
    331                 // Replace the subroutine invocation by a simple branch.
    332                 Instruction replacementInstruction =
    333                     new BranchInstruction(InstructionConstants.OP_GOTO,
    334                                           branchOffset).shrink();
    335 
    336                 codeAttributeComposer.appendInstruction(offset, replacementInstruction);
    337             }
    338         }
    339         else
    340         {
    341             // Append the instruction.
    342             codeAttributeComposer.appendInstruction(offset, branchInstruction);
    343         }
    344     }
    345 
    346 
    347     // Implementations for ExceptionInfoVisitor.
    348 
    349     public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
    350     {
    351         int startPC   = Math.max(exceptionInfo.u2startPC, clipStart);
    352         int endPC     = Math.min(exceptionInfo.u2endPC,   clipEnd);
    353         int handlerPC = exceptionInfo.u2handlerPC;
    354         int catchType = exceptionInfo.u2catchType;
    355 
    356         // Exclude any subroutine invocations that jump out of the try block,
    357         // by adding a try block before (and later on, after) each invocation.
    358         for (int offset = startPC; offset < endPC; offset++)
    359         {
    360             if (branchTargetFinder.isSubroutineInvocation(offset))
    361             {
    362                 Instruction instruction = InstructionFactory.create(codeAttribute.code, offset);
    363                 int instructionLength = instruction.length(offset);
    364 
    365                 // Is it a subroutine invocation?
    366                 if (!exceptionInfo.isApplicable(offset + ((BranchInstruction)instruction).branchOffset))
    367                 {
    368                     if (DEBUG)
    369                     {
    370                         System.out.println("  Appending extra exception ["+startPC+" -> "+offset+"] -> "+handlerPC);
    371                     }
    372 
    373                     // Append a try block that ends before the subroutine invocation.
    374                     codeAttributeComposer.appendException(new ExceptionInfo(startPC,
    375                                                                             offset,
    376                                                                             handlerPC,
    377                                                                             catchType));
    378 
    379                     // The next try block will start after the subroutine invocation.
    380                     startPC = offset + instructionLength;
    381                 }
    382             }
    383         }
    384 
    385         if (DEBUG)
    386         {
    387             if (startPC == exceptionInfo.u2startPC &&
    388                 endPC   == exceptionInfo.u2endPC)
    389             {
    390                 System.out.println("  Appending exception ["+startPC+" -> "+endPC+"] -> "+handlerPC);
    391             }
    392             else
    393             {
    394                 System.out.println("  Appending clipped exception ["+exceptionInfo.u2startPC+" -> "+exceptionInfo.u2endPC+"] ~> ["+startPC+" -> "+endPC+"] -> "+handlerPC);
    395             }
    396         }
    397 
    398         // Append the exception. Note that exceptions with empty try blocks
    399         // are automatically ignored.
    400         codeAttributeComposer.appendException(new ExceptionInfo(startPC,
    401                                                                 endPC,
    402                                                                 handlerPC,
    403                                                                 catchType));
    404     }
    405 }
    406