Home | History | Annotate | Download | only in evaluation
      1 /*
      2  * ProGuard -- shrinking, optimization, obfuscation, and preverification
      3  *             of Java bytecode.
      4  *
      5  * Copyright (c) 2002-2014 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.optimize.evaluation;
     22 
     23 import proguard.classfile.*;
     24 import proguard.classfile.attribute.*;
     25 import proguard.classfile.attribute.visitor.*;
     26 import proguard.classfile.instruction.*;
     27 import proguard.classfile.instruction.visitor.InstructionVisitor;
     28 import proguard.classfile.util.SimplifiedVisitor;
     29 import proguard.evaluation.value.*;
     30 
     31 /**
     32  * This AttributeVisitor analyzes the liveness of the variables in the code
     33  * attributes that it visits, based on partial evaluation.
     34  *
     35  * @author Eric Lafortune
     36  */
     37 public class LivenessAnalyzer
     38 extends      SimplifiedVisitor
     39 implements   AttributeVisitor,
     40              InstructionVisitor,
     41              ExceptionInfoVisitor
     42 {
     43     //*
     44     private static final boolean DEBUG = false;
     45     /*/
     46     private static       boolean DEBUG = true;
     47     //*/
     48 
     49     private static final int MAX_VARIABLES_SIZE = 64;
     50 
     51     private final PartialEvaluator partialEvaluator;
     52 
     53     private long[] isAliveBefore = new long[ClassConstants.TYPICAL_CODE_LENGTH];
     54     private long[] isAliveAfter  = new long[ClassConstants.TYPICAL_CODE_LENGTH];
     55     private long[] isCategory2   = new long[ClassConstants.TYPICAL_CODE_LENGTH];
     56 
     57     // Fields acting as global temporary variables.
     58     private boolean checkAgain;
     59     private long    alive;
     60 
     61 
     62     /**
     63      * Creates a new LivenessAnalyzer.
     64      */
     65     public LivenessAnalyzer()
     66     {
     67         this(new PartialEvaluator());
     68     }
     69 
     70 
     71     /**
     72      * Creates a new LivenessAnalyzer that will use the given partial evaluator.
     73      * It will run this evaluator on every code attribute that it visits.
     74      */
     75     public LivenessAnalyzer(PartialEvaluator partialEvaluator)
     76     {
     77         this.partialEvaluator = partialEvaluator;
     78     }
     79 
     80 
     81     /**
     82      * Returns whether the instruction at the given offset has ever been
     83      * executed during the partial evaluation.
     84      */
     85     public boolean isTraced(int instructionOffset)
     86     {
     87         return partialEvaluator.isTraced(instructionOffset);
     88     }
     89 
     90 
     91     /**
     92      * Returns whether the specified variable is alive before the instruction
     93      * at the given offset.
     94      */
     95     public boolean isAliveBefore(int instructionOffset, int variableIndex)
     96     {
     97         return variableIndex >= MAX_VARIABLES_SIZE ||
     98                (isAliveBefore[instructionOffset] & (1L << variableIndex)) != 0;
     99     }
    100 
    101 
    102     /**
    103      * Sets whether the specified variable is alive before the instruction
    104      * at the given offset.
    105      */
    106     public void setAliveBefore(int instructionOffset, int variableIndex, boolean alive)
    107     {
    108         if (variableIndex < MAX_VARIABLES_SIZE)
    109         {
    110             if (alive)
    111             {
    112                 isAliveBefore[instructionOffset] |= 1L << variableIndex;
    113             }
    114             else
    115             {
    116                 isAliveBefore[instructionOffset] &= ~(1L << variableIndex);
    117             }
    118         }
    119     }
    120 
    121 
    122     /**
    123      * Returns whether the specified variable is alive after the instruction
    124      * at the given offset.
    125      */
    126     public boolean isAliveAfter(int instructionOffset, int variableIndex)
    127     {
    128         return variableIndex >= MAX_VARIABLES_SIZE ||
    129                (isAliveAfter[instructionOffset] & (1L << variableIndex)) != 0;
    130     }
    131 
    132 
    133     /**
    134      * Sets whether the specified variable is alive after the instruction
    135      * at the given offset.
    136      */
    137     public void setAliveAfter(int instructionOffset, int variableIndex, boolean alive)
    138     {
    139         if (variableIndex < MAX_VARIABLES_SIZE)
    140         {
    141             if (alive)
    142             {
    143                 isAliveAfter[instructionOffset] |= 1L << variableIndex;
    144             }
    145             else
    146             {
    147                 isAliveAfter[instructionOffset] &= ~(1L << variableIndex);
    148             }
    149         }
    150     }
    151 
    152 
    153     /**
    154      * Returns whether the specified variable takes up two entries after the
    155      * instruction at the given offset.
    156      */
    157     public boolean isCategory2(int instructionOffset, int variableIndex)
    158     {
    159         return variableIndex < MAX_VARIABLES_SIZE &&
    160                (isCategory2[instructionOffset] & (1L << variableIndex)) != 0;
    161     }
    162 
    163 
    164     /**
    165      * Sets whether the specified variable takes up two entries after the
    166      * instruction at the given offset.
    167      */
    168     public void setCategory2(int instructionOffset, int variableIndex, boolean category2)
    169     {
    170         if (variableIndex < MAX_VARIABLES_SIZE)
    171         {
    172             if (category2)
    173             {
    174                 isCategory2[instructionOffset] |= 1L << variableIndex;
    175             }
    176             else
    177             {
    178                 isCategory2[instructionOffset] &= ~(1L << variableIndex);
    179             }
    180         }
    181     }
    182 
    183 
    184     // Implementations for AttributeVisitor.
    185 
    186     public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
    187 
    188 
    189     public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
    190     {
    191 //        DEBUG =
    192 //            clazz.getName().equals("abc/Def") &&
    193 //            method.getName(clazz).equals("abc");
    194 
    195         if (DEBUG)
    196         {
    197             System.out.println();
    198             System.out.println("Liveness analysis: "+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz));
    199         }
    200 
    201         // Initialize the global arrays.
    202         initializeArrays(codeAttribute);
    203 
    204         // Evaluate the method.
    205         partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute);
    206 
    207         int codeLength    = codeAttribute.u4codeLength;
    208         int variablesSize = codeAttribute.u2maxLocals;
    209 
    210         // We'll only really analyze the first 64 variables.
    211         if (variablesSize > MAX_VARIABLES_SIZE)
    212         {
    213             variablesSize = MAX_VARIABLES_SIZE;
    214         }
    215 
    216         // Mark liveness blocks, as many times as necessary.
    217         do
    218         {
    219             checkAgain = false;
    220             alive      = 0L;
    221 
    222             // Loop over all traced instructions, backward.
    223             for (int offset = codeLength - 1; offset >= 0; offset--)
    224             {
    225                 if (partialEvaluator.isTraced(offset))
    226                 {
    227                     // Update the liveness based on the branch targets.
    228                     InstructionOffsetValue branchTargets = partialEvaluator.branchTargets(offset);
    229                     if (branchTargets != null)
    230                     {
    231                         // Update the liveness right after the branch instruction.
    232                         alive = combinedLiveness(branchTargets);
    233                     }
    234 
    235                     // Merge the current liveness.
    236                     alive |= isAliveAfter[offset];
    237 
    238                     // Update the liveness after the instruction.
    239                     isAliveAfter[offset] = alive;
    240 
    241                     // Update the current liveness based on the instruction.
    242                     codeAttribute.instructionAccept(clazz, method, offset, this);
    243 
    244                     // Merge the current liveness.
    245                     alive |= isAliveBefore[offset];
    246 
    247                     // Update the liveness before the instruction.
    248                     if ((~isAliveBefore[offset] & alive) != 0L)
    249                     {
    250                         isAliveBefore[offset] = alive;
    251 
    252                         // Do we have to check again after this loop?
    253                         checkAgain |= offset < maxOffset(partialEvaluator.branchOrigins(offset));
    254                     }
    255                 }
    256             }
    257 
    258             // Account for the liveness at the start of the exception handlers.
    259             codeAttribute.exceptionsAccept(clazz, method, this);
    260         }
    261         while (checkAgain);
    262 
    263         // Loop over all instructions, to mark variables that take up two entries.
    264         for (int offset = 0; offset < codeLength; offset++)
    265         {
    266             if (partialEvaluator.isTraced(offset))
    267             {
    268                 // Loop over all variables.
    269                 for (int variableIndex = 0; variableIndex < variablesSize; variableIndex++)
    270                 {
    271                     // Is the variable alive and a category 2 type?
    272                     if (isAliveBefore(offset, variableIndex))
    273                     {
    274                         Value value = partialEvaluator.getVariablesBefore(offset).getValue(variableIndex);
    275                         if (value != null && value.isCategory2())
    276                         {
    277                             // Mark it as such.
    278                             setCategory2(offset, variableIndex, true);
    279 
    280                             // Mark the next variable as well.
    281                             setAliveBefore(offset, variableIndex + 1, true);
    282                             setCategory2(  offset, variableIndex + 1, true);
    283                         }
    284                     }
    285 
    286                     // Is the variable alive and a category 2 type?
    287                     if (isAliveAfter(offset, variableIndex))
    288                     {
    289                         Value value = partialEvaluator.getVariablesAfter(offset).getValue(variableIndex);
    290                         if (value != null && value.isCategory2())
    291                         {
    292                             // Mark it as such.
    293                             setCategory2(offset, variableIndex, true);
    294 
    295                             // Mark the next variable as well.
    296                             setAliveAfter(offset, variableIndex + 1, true);
    297                             setCategory2( offset, variableIndex + 1, true);
    298                         }
    299                     }
    300                 }
    301             }
    302         }
    303 
    304         if (DEBUG)
    305         {
    306             // Loop over all instructions.
    307             for (int offset = 0; offset < codeLength; offset++)
    308             {
    309                 if (partialEvaluator.isTraced(offset))
    310                 {
    311                     long aliveBefore = isAliveBefore[offset];
    312                     long aliveAfter  = isAliveAfter[offset];
    313                     long category2   = isCategory2[offset];
    314 
    315                     // Print out the liveness of all variables before the instruction.
    316                     for (int variableIndex = 0; variableIndex < variablesSize; variableIndex++)
    317                     {
    318                         long variableMask = (1L << variableIndex);
    319                         System.out.print((aliveBefore & variableMask) == 0L ? '.' :
    320                                          (category2   & variableMask) == 0L ? 'x' :
    321                                                                               '*');
    322                     }
    323 
    324                     // Print out the instruction itself.
    325                     System.out.println(" "+ InstructionFactory.create(codeAttribute.code, offset).toString(offset));
    326 
    327                     // Print out the liveness of all variables after the instruction.
    328                     for (int variableIndex = 0; variableIndex < variablesSize; variableIndex++)
    329                     {
    330                         long variableMask = (1L << variableIndex);
    331                         System.out.print((aliveAfter & variableMask) == 0L ? '.' :
    332                                          (category2  & variableMask) == 0L ? 'x' :
    333                                                                              '=');
    334                     }
    335 
    336                     System.out.println();
    337                 }
    338             }
    339         }
    340     }
    341 
    342 
    343     // Implementations for InstructionVisitor.
    344 
    345     public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
    346 
    347 
    348     public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
    349     {
    350         int variableIndex = variableInstruction.variableIndex;
    351         if (variableIndex < MAX_VARIABLES_SIZE)
    352         {
    353             long livenessMask = 1L << variableIndex;
    354 
    355             // Is it a load instruction or a store instruction?
    356             if (variableInstruction.isLoad())
    357             {
    358                 // Start marking the variable before the load instruction.
    359                 alive |= livenessMask;
    360             }
    361             else
    362             {
    363                 // Stop marking the variable before the store instruction.
    364                 alive &= ~livenessMask;
    365 
    366                 // But do mark the variable right after the store instruction.
    367                 isAliveAfter[offset] |= livenessMask;
    368             }
    369         }
    370     }
    371 
    372 
    373     public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
    374     {
    375         // Special case: variable 0 ('this') in an initializer has to be alive
    376         // as long as it hasn't been initialized.
    377          if (offset == partialEvaluator.superInitializationOffset())
    378         {
    379             alive |= 1L;
    380         }
    381     }
    382 
    383 
    384     // Implementations for ExceptionInfoVisitor.
    385 
    386     public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
    387     {
    388         // Are any variables alive at the start of the handler?
    389         long alive = isAliveBefore[exceptionInfo.u2handlerPC];
    390         if (alive != 0L)
    391         {
    392             // Set the same liveness flags for the entire try block.
    393             int startOffset = exceptionInfo.u2startPC;
    394             int endOffset   = exceptionInfo.u2endPC;
    395 
    396             for (int offset = startOffset; offset < endOffset; offset++)
    397             {
    398                 if (partialEvaluator.isTraced(offset))
    399                 {
    400                     if ((~(isAliveBefore[offset] & isAliveAfter[offset]) & alive) != 0L)
    401                     {
    402                         isAliveBefore[offset] |= alive;
    403                         isAliveAfter[offset]  |= alive;
    404 
    405                         // Check again after having marked this try block.
    406                         checkAgain = true;
    407                     }
    408                 }
    409             }
    410         }
    411     }
    412 
    413 
    414     // Small utility methods.
    415 
    416     /**
    417      * Initializes the global arrays.
    418      */
    419     private void initializeArrays(CodeAttribute codeAttribute)
    420     {
    421         int codeLength = codeAttribute.u4codeLength;
    422 
    423         // Create new arrays for storing information at each instruction offset.
    424         if (isAliveBefore.length < codeLength)
    425         {
    426             isAliveBefore = new long[codeLength];
    427             isAliveAfter  = new long[codeLength];
    428             isCategory2   = new long[codeLength];
    429         }
    430         else
    431         {
    432             for (int index = 0; index < codeLength; index++)
    433             {
    434                 isAliveBefore[index] = 0L;
    435                 isAliveAfter[index]  = 0L;
    436                 isCategory2[index]   = 0L;
    437             }
    438         }
    439     }
    440 
    441 
    442     /**
    443      * Returns the combined liveness mask of the variables right before the
    444      * specified instruction offsets.
    445      */
    446     private long combinedLiveness(InstructionOffsetValue instructionOffsetValue)
    447     {
    448         long alive = 0L;
    449 
    450         int count = instructionOffsetValue.instructionOffsetCount();
    451         for (int index = 0; index < count; index++)
    452         {
    453             alive |= isAliveBefore[instructionOffsetValue.instructionOffset(index)];
    454         }
    455 
    456         return alive;
    457     }
    458 
    459 
    460     /**
    461      * Returns the minimum offset from the given instruction offsets.
    462      */
    463     private int minOffset(Value instructionOffsets)
    464     {
    465         return minOffset(instructionOffsets, Integer.MAX_VALUE);
    466     }
    467 
    468 
    469     /**
    470      * Returns the minimum offset from the given instruction offsets.
    471      */
    472     private int minOffset(Value instructionOffsets, int minOffset)
    473     {
    474         if (instructionOffsets != null)
    475         {
    476             InstructionOffsetValue instructionOffsetValue =
    477                 instructionOffsets.instructionOffsetValue();
    478 
    479             int count = instructionOffsetValue.instructionOffsetCount();
    480             for (int index = 0; index < count; index++)
    481             {
    482                 int offset = instructionOffsetValue.instructionOffset(index);
    483                 if (minOffset > offset)
    484                 {
    485                     minOffset = offset;
    486                 }
    487             }
    488         }
    489 
    490         return minOffset;
    491     }
    492 
    493 
    494     /**
    495      * Returns the maximum offset from the given instruction offsets.
    496      */
    497     private int maxOffset(Value instructionOffsets)
    498     {
    499         return maxOffset(instructionOffsets, Integer.MIN_VALUE);
    500     }
    501 
    502 
    503     /**
    504      * Returns the maximum offset from the given instruction offsets.
    505      */
    506     private int maxOffset(Value instructionOffsets, int maxOffset)
    507     {
    508         if (instructionOffsets != null)
    509         {
    510             InstructionOffsetValue instructionOffsetValue =
    511                 instructionOffsets.instructionOffsetValue();
    512 
    513             int count = instructionOffsetValue.instructionOffsetCount();
    514             for (int index = 0; index < count; index++)
    515             {
    516                 int offset = instructionOffsetValue.instructionOffset(index);
    517                 if (maxOffset < offset)
    518                 {
    519                     maxOffset = offset;
    520                 }
    521             }
    522         }
    523 
    524         return maxOffset;
    525     }
    526 }
    527