• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * ProGuard -- shrinking, optimization, obfuscation, and preverification
3  *             of Java bytecode.
4  *
5  * Copyright (c) 2002-2013 Eric Lafortune (eric@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.constant.ClassConstant;
27 import proguard.classfile.instruction.*;
28 import proguard.classfile.util.*;
29 import proguard.classfile.visitor.*;
30 import proguard.evaluation.*;
31 import proguard.evaluation.value.*;
32 import proguard.optimize.peephole.BranchTargetFinder;
33 
34 import java.util.Arrays;
35 
36 /**
37  * This AttributeVisitor performs partial evaluation on the code attributes
38  * that it visits.
39  *
40  * @author Eric Lafortune
41  */
42 public class PartialEvaluator
43 extends      SimplifiedVisitor
44 implements   AttributeVisitor,
45              ExceptionInfoVisitor
46 {
47     //*
48     private static final boolean DEBUG         = false;
49     private static final boolean DEBUG_RESULTS = false;
50     /*/
51     private static boolean DEBUG         = true;
52     private static boolean DEBUG_RESULTS = true;
53     //*/
54 
55     private static final int MAXIMUM_EVALUATION_COUNT = 5;
56 
57     public static final int NONE            = -2;
58     public static final int AT_METHOD_ENTRY = -1;
59     public static final int AT_CATCH_ENTRY  = -1;
60 
61     private final ValueFactory   valueFactory;
62     private final InvocationUnit invocationUnit;
63     private final boolean        evaluateAllCode;
64 
65     private InstructionOffsetValue[] branchOriginValues   = new InstructionOffsetValue[ClassConstants.TYPICAL_CODE_LENGTH];
66     private InstructionOffsetValue[] branchTargetValues   = new InstructionOffsetValue[ClassConstants.TYPICAL_CODE_LENGTH];
67     private TracedVariables[]        variablesBefore      = new TracedVariables[ClassConstants.TYPICAL_CODE_LENGTH];
68     private TracedStack[]            stacksBefore         = new TracedStack[ClassConstants.TYPICAL_CODE_LENGTH];
69     private TracedVariables[]        variablesAfter       = new TracedVariables[ClassConstants.TYPICAL_CODE_LENGTH];
70     private TracedStack[]            stacksAfter          = new TracedStack[ClassConstants.TYPICAL_CODE_LENGTH];
71     private boolean[]                generalizedContexts  = new boolean[ClassConstants.TYPICAL_CODE_LENGTH];
72     private int[]                    evaluationCounts     = new int[ClassConstants.TYPICAL_CODE_LENGTH];
73     private boolean                  evaluateExceptions;
74 
75     private final BasicBranchUnit    branchUnit;
76     private final BranchTargetFinder branchTargetFinder;
77 
78     private final java.util.Stack callingInstructionBlockStack;
79     private final java.util.Stack instructionBlockStack = new java.util.Stack();
80 
81 
82     /**
83      * Creates a simple PartialEvaluator.
84      */
PartialEvaluator()85     public PartialEvaluator()
86     {
87         this(new ValueFactory(),
88              new BasicInvocationUnit(new ValueFactory()),
89              true);
90     }
91 
92 
93     /**
94      * Creates a new PartialEvaluator.
95      * @param valueFactory    the value factory that will create all values
96      *                        during evaluation.
97      * @param invocationUnit  the invocation unit that will handle all
98      *                        communication with other fields and methods.
99      * @param evaluateAllCode a flag that specifies whether all branch targets
100      *                        and exception handlers should be evaluated,
101      *                        even if they are unreachable.
102      */
PartialEvaluator(ValueFactory valueFactory, InvocationUnit invocationUnit, boolean evaluateAllCode)103     public PartialEvaluator(ValueFactory   valueFactory,
104                             InvocationUnit invocationUnit,
105                             boolean        evaluateAllCode)
106     {
107         this(valueFactory,
108              invocationUnit,
109              evaluateAllCode,
110              evaluateAllCode ?
111                  new BasicBranchUnit() :
112                  new TracedBranchUnit(),
113              new BranchTargetFinder(),
114              null);
115     }
116 
117 
118     /**
119      * Creates a new PartialEvaluator, based on an existing one.
120      * @param partialEvaluator the subroutine calling partial evaluator.
121      */
PartialEvaluator(PartialEvaluator partialEvaluator)122     private PartialEvaluator(PartialEvaluator partialEvaluator)
123     {
124         this(partialEvaluator.valueFactory,
125              partialEvaluator.invocationUnit,
126              partialEvaluator.evaluateAllCode,
127              partialEvaluator.branchUnit,
128              partialEvaluator.branchTargetFinder,
129              partialEvaluator.instructionBlockStack);
130     }
131 
132 
133     /**
134      * Creates a new PartialEvaluator.
135      * @param valueFactory            the value factory that will create all
136      *                                values during evaluation.
137      * @param invocationUnit          the invocation unit that will handle all
138      *                                communication with other fields and methods.
139      * @param evaluateAllCode         a flag that specifies whether all branch
140      *                                targets and exception handlers should be
141      *                                evaluated, even if they are unreachable.
142      * @param branchUnit              the branch unit that will handle all
143      *                                branches.
144      * @param branchTargetFinder      the utility class that will find all
145      *                                branches.
146      */
PartialEvaluator(ValueFactory valueFactory, InvocationUnit invocationUnit, boolean evaluateAllCode, BasicBranchUnit branchUnit, BranchTargetFinder branchTargetFinder, java.util.Stack callingInstructionBlockStack)147     private PartialEvaluator(ValueFactory       valueFactory,
148                              InvocationUnit     invocationUnit,
149                              boolean            evaluateAllCode,
150                              BasicBranchUnit    branchUnit,
151                              BranchTargetFinder branchTargetFinder,
152                              java.util.Stack    callingInstructionBlockStack)
153     {
154         this.valueFactory       = valueFactory;
155         this.invocationUnit     = invocationUnit;
156         this.evaluateAllCode    = evaluateAllCode;
157         this.branchUnit         = branchUnit;
158         this.branchTargetFinder = branchTargetFinder;
159         this.callingInstructionBlockStack = callingInstructionBlockStack == null ?
160             this.instructionBlockStack :
161             callingInstructionBlockStack;
162     }
163 
164 
165     // Implementations for AttributeVisitor.
166 
visitAnyAttribute(Clazz clazz, Attribute attribute)167     public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
168 
169 
visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)170     public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
171     {
172 //        DEBUG = DEBUG_RESULTS =
173 //            clazz.getName().equals("abc/Def") &&
174 //            method.getName(clazz).equals("abc");
175 
176         // TODO: Remove this when the partial evaluator has stabilized.
177         // Catch any unexpected exceptions from the actual visiting method.
178         try
179         {
180             // Process the code.
181             visitCodeAttribute0(clazz, method, codeAttribute);
182         }
183         catch (RuntimeException ex)
184         {
185             System.err.println("Unexpected error while performing partial evaluation:");
186             System.err.println("  Class       = ["+clazz.getName()+"]");
187             System.err.println("  Method      = ["+method.getName(clazz)+method.getDescriptor(clazz)+"]");
188             System.err.println("  Exception   = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")");
189 
190             if (DEBUG)
191             {
192                 method.accept(clazz, new ClassPrinter());
193 
194                 System.out.println("Evaluation results:");
195 
196                 int offset = 0;
197                 do
198                 {
199                     if (isBranchOrExceptionTarget(offset))
200                     {
201                         System.out.println("Branch target from ["+branchOriginValues[offset]+"]:");
202                         if (isTraced(offset))
203                         {
204                             System.out.println("  Vars:  "+variablesBefore[offset]);
205                             System.out.println("  Stack: "+stacksBefore[offset]);
206                         }
207                     }
208 
209                     Instruction instruction = InstructionFactory.create(codeAttribute.code,
210                                                                         offset);
211                     System.out.println(instruction.toString(offset));
212 
213                     if (isTraced(offset))
214                     {
215                         int initializationOffset = branchTargetFinder.initializationOffset(offset);
216                         if (initializationOffset != NONE)
217                         {
218                             System.out.println("     is to be initialized at ["+initializationOffset+"]");
219                         }
220 
221                         InstructionOffsetValue branchTargets = branchTargets(offset);
222                         if (branchTargets != null)
223                         {
224                             System.out.println("     has overall been branching to "+branchTargets);
225                         }
226 
227                         System.out.println("  Vars:  "+variablesAfter[offset]);
228                         System.out.println("  Stack: "+stacksAfter[offset]);
229                     }
230 
231                     offset += instruction.length(offset);
232                 }
233                 while (offset < codeAttribute.u4codeLength);
234             }
235 
236             throw ex;
237         }
238     }
239 
240 
visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute)241     public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute)
242     {
243         // Evaluate the instructions, starting at the entry point.
244         if (DEBUG)
245         {
246             System.out.println();
247             System.out.println("Partial evaluation: "+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz));
248             System.out.println("  Max locals = "+codeAttribute.u2maxLocals);
249             System.out.println("  Max stack  = "+codeAttribute.u2maxStack);
250         }
251 
252         // Reuse the existing variables and stack objects, ensuring the right size.
253         TracedVariables variables = new TracedVariables(codeAttribute.u2maxLocals);
254         TracedStack     stack     = new TracedStack(codeAttribute.u2maxStack);
255 
256         // Initialize the reusable arrays and variables.
257         initializeArrays(codeAttribute);
258         initializeParameters(clazz, method, codeAttribute, variables);
259 
260         // Find all instruction offsets,...
261         codeAttribute.accept(clazz, method, branchTargetFinder);
262 
263         // Start executing the first instruction block.
264         evaluateInstructionBlockAndExceptionHandlers(clazz,
265                                                      method,
266                                                      codeAttribute,
267                                                      variables,
268                                                      stack,
269                                                      0,
270                                                      codeAttribute.u4codeLength);
271 
272         if (DEBUG_RESULTS)
273         {
274             System.out.println("Evaluation results:");
275 
276             int offset = 0;
277             do
278             {
279                 if (isBranchOrExceptionTarget(offset))
280                 {
281                     System.out.println("Branch target from ["+branchOriginValues[offset]+"]:");
282                     if (isTraced(offset))
283                     {
284                         System.out.println("  Vars:  "+variablesBefore[offset]);
285                         System.out.println("  Stack: "+stacksBefore[offset]);
286                     }
287                 }
288 
289                 Instruction instruction = InstructionFactory.create(codeAttribute.code,
290                                                                     offset);
291                 System.out.println(instruction.toString(offset));
292 
293                 if (isTraced(offset))
294                 {
295                     int initializationOffset = branchTargetFinder.initializationOffset(offset);
296                     if (initializationOffset != NONE)
297                     {
298                         System.out.println("     is to be initialized at ["+initializationOffset+"]");
299                     }
300 
301                     InstructionOffsetValue branchTargets = branchTargets(offset);
302                     if (branchTargets != null)
303                     {
304                         System.out.println("     has overall been branching to "+branchTargets);
305                     }
306 
307                     System.out.println("  Vars:  "+variablesAfter[offset]);
308                     System.out.println("  Stack: "+stacksAfter[offset]);
309                 }
310 
311                 offset += instruction.length(offset);
312             }
313             while (offset < codeAttribute.u4codeLength);
314         }
315     }
316 
317 
318     /**
319      * Returns whether a block of instructions is ever used.
320      */
isTraced(int startOffset, int endOffset)321     public boolean isTraced(int startOffset, int endOffset)
322     {
323         for (int index = startOffset; index < endOffset; index++)
324         {
325             if (isTraced(index))
326             {
327                 return true;
328             }
329         }
330 
331         return false;
332     }
333 
334 
335     /**
336      * Returns whether the instruction at the given offset has ever been
337      * executed during the partial evaluation.
338      */
isTraced(int instructionOffset)339     public boolean isTraced(int instructionOffset)
340     {
341         return evaluationCounts[instructionOffset] > 0;
342     }
343 
344 
345     /**
346      * Returns whether there is an instruction at the given offset.
347      */
isInstruction(int instructionOffset)348     public boolean isInstruction(int instructionOffset)
349     {
350         return branchTargetFinder.isInstruction(instructionOffset);
351     }
352 
353 
354     /**
355      * Returns whether the instruction at the given offset is the target of a
356      * branch instruction or an exception.
357      */
isBranchOrExceptionTarget(int instructionOffset)358     public boolean isBranchOrExceptionTarget(int instructionOffset)
359     {
360         return branchTargetFinder.isBranchTarget(instructionOffset) ||
361                branchTargetFinder.isExceptionHandler(instructionOffset);
362     }
363 
364 
365     /**
366      * Returns whether the instruction at the given offset is the start of a
367      * subroutine.
368      */
isSubroutineStart(int instructionOffset)369     public boolean isSubroutineStart(int instructionOffset)
370     {
371         return branchTargetFinder.isSubroutineStart(instructionOffset);
372     }
373 
374 
375     /**
376      * Returns whether the instruction at the given offset is a subroutine
377      * invocation.
378      */
isSubroutineInvocation(int instructionOffset)379     public boolean isSubroutineInvocation(int instructionOffset)
380     {
381         return branchTargetFinder.isSubroutineInvocation(instructionOffset);
382     }
383 
384 
385     /**
386      * Returns whether the instruction at the given offset is part of a
387      * subroutine.
388      */
isSubroutine(int instructionOffset)389     public boolean isSubroutine(int instructionOffset)
390     {
391         return branchTargetFinder.isSubroutine(instructionOffset);
392     }
393 
394 
395     /**
396      * Returns whether the subroutine at the given offset is ever returning
397      * by means of a regular 'ret' instruction.
398      */
isSubroutineReturning(int instructionOffset)399     public boolean isSubroutineReturning(int instructionOffset)
400     {
401         return branchTargetFinder.isSubroutineReturning(instructionOffset);
402     }
403 
404 
405     /**
406      * Returns the offset after the subroutine that starts at the given
407      * offset.
408      */
subroutineEnd(int instructionOffset)409     public int subroutineEnd(int instructionOffset)
410     {
411         return branchTargetFinder.subroutineEnd(instructionOffset);
412     }
413 
414 
415     /**
416      * Returns the instruction offset at which the object instance that is
417      * created at the given 'new' instruction offset is initialized, or
418      * <code>NONE</code> if it is not being created.
419      */
initializationOffset(int instructionOffset)420     public int initializationOffset(int instructionOffset)
421     {
422         return branchTargetFinder.initializationOffset(instructionOffset);
423     }
424 
425 
426     /**
427      * Returns whether the method is an instance initializer.
428      */
isInitializer()429     public boolean isInitializer()
430     {
431         return branchTargetFinder.isInitializer();
432     }
433 
434 
435     /**
436      * Returns the instruction offset at which this initializer is calling
437      * the "super" or "this" initializer method, or <code>NONE</code> if it is
438      * not an initializer.
439      */
superInitializationOffset()440     public int superInitializationOffset()
441     {
442         return branchTargetFinder.superInitializationOffset();
443     }
444 
445 
446     /**
447      * Returns the offset of the 'new' instruction that corresponds to the
448      * invocation of the instance initializer at the given offset, or
449      * <code>AT_METHOD_ENTRY</code> if the invocation is calling the "super" or
450      * "this" initializer method, , or <code>NONE</code> if it is not a 'new'
451      * instruction.
452      */
creationOffset(int offset)453     public int creationOffset(int offset)
454     {
455         return branchTargetFinder.creationOffset(offset);
456     }
457 
458 
459     /**
460      * Returns the variables before execution of the instruction at the given
461      * offset.
462      */
getVariablesBefore(int instructionOffset)463     public TracedVariables getVariablesBefore(int instructionOffset)
464     {
465         return variablesBefore[instructionOffset];
466     }
467 
468 
469     /**
470      * Returns the variables after execution of the instruction at the given
471      * offset.
472      */
getVariablesAfter(int instructionOffset)473     public TracedVariables getVariablesAfter(int instructionOffset)
474     {
475         return variablesAfter[instructionOffset];
476     }
477 
478 
479     /**
480      * Returns the stack before execution of the instruction at the given
481      * offset.
482      */
getStackBefore(int instructionOffset)483     public TracedStack getStackBefore(int instructionOffset)
484     {
485         return stacksBefore[instructionOffset];
486     }
487 
488 
489     /**
490      * Returns the stack after execution of the instruction at the given
491      * offset.
492      */
getStackAfter(int instructionOffset)493     public TracedStack getStackAfter(int instructionOffset)
494     {
495         return stacksAfter[instructionOffset];
496     }
497 
498 
499     /**
500      * Returns the instruction offsets that branch to the given instruction
501      * offset.
502      */
branchOrigins(int instructionOffset)503     public InstructionOffsetValue branchOrigins(int instructionOffset)
504     {
505         return branchOriginValues[instructionOffset];
506     }
507 
508 
509     /**
510      * Returns the instruction offsets to which the given instruction offset
511      * branches.
512      */
branchTargets(int instructionOffset)513     public InstructionOffsetValue branchTargets(int instructionOffset)
514     {
515         return branchTargetValues[instructionOffset];
516     }
517 
518 
519     // Utility methods to evaluate instruction blocks.
520 
521     /**
522      * Pushes block of instructions to be executed in the calling partial
523      * evaluator.
524      */
pushCallingInstructionBlock(TracedVariables variables, TracedStack stack, int startOffset)525     private void pushCallingInstructionBlock(TracedVariables variables,
526                                              TracedStack     stack,
527                                              int             startOffset)
528     {
529         callingInstructionBlockStack.push(new MyInstructionBlock(variables,
530                                                                  stack,
531                                                                  startOffset));
532     }
533 
534 
535     /**
536      * Pushes block of instructions to be executed in this partial evaluator.
537      */
pushInstructionBlock(TracedVariables variables, TracedStack stack, int startOffset)538     private void pushInstructionBlock(TracedVariables variables,
539                                       TracedStack     stack,
540                                       int             startOffset)
541     {
542         instructionBlockStack.push(new MyInstructionBlock(variables,
543                                                           stack,
544                                                           startOffset));
545     }
546 
547 
548     /**
549      * Evaluates the instruction block and the exception handlers covering the
550      * given instruction range in the given code.
551      */
evaluateInstructionBlockAndExceptionHandlers(Clazz clazz, Method method, CodeAttribute codeAttribute, TracedVariables variables, TracedStack stack, int startOffset, int endOffset)552     private void evaluateInstructionBlockAndExceptionHandlers(Clazz           clazz,
553                                                               Method          method,
554                                                               CodeAttribute   codeAttribute,
555                                                               TracedVariables variables,
556                                                               TracedStack     stack,
557                                                               int             startOffset,
558                                                               int             endOffset)
559     {
560         evaluateInstructionBlock(clazz,
561                                  method,
562                                  codeAttribute,
563                                  variables,
564                                  stack,
565                                  startOffset);
566 
567         evaluateExceptionHandlers(clazz,
568                                   method,
569                                   codeAttribute,
570                                   startOffset,
571                                   endOffset);
572     }
573 
574 
575     /**
576      * Evaluates a block of instructions, starting at the given offset and ending
577      * at a branch instruction, a return instruction, or a throw instruction.
578      */
evaluateInstructionBlock(Clazz clazz, Method method, CodeAttribute codeAttribute, TracedVariables variables, TracedStack stack, int startOffset)579     private void evaluateInstructionBlock(Clazz           clazz,
580                                           Method          method,
581                                           CodeAttribute   codeAttribute,
582                                           TracedVariables variables,
583                                           TracedStack     stack,
584                                           int             startOffset)
585     {
586         // Execute the initial instruction block.
587         evaluateSingleInstructionBlock(clazz,
588                                        method,
589                                        codeAttribute,
590                                        variables,
591                                        stack,
592                                        startOffset);
593 
594         // Execute all resulting instruction blocks on the execution stack.
595         while (!instructionBlockStack.empty())
596         {
597             if (DEBUG) System.out.println("Popping alternative branch out of "+instructionBlockStack.size()+" blocks");
598 
599             MyInstructionBlock instructionBlock =
600                 (MyInstructionBlock)instructionBlockStack.pop();
601 
602             evaluateSingleInstructionBlock(clazz,
603                                            method,
604                                            codeAttribute,
605                                            instructionBlock.variables,
606                                            instructionBlock.stack,
607                                            instructionBlock.startOffset);
608         }
609     }
610 
611 
612     /**
613      * Evaluates a block of instructions, starting at the given offset and ending
614      * at a branch instruction, a return instruction, or a throw instruction.
615      * Instruction blocks that are to be evaluated as a result are pushed on
616      * the given stack.
617      */
evaluateSingleInstructionBlock(Clazz clazz, Method method, CodeAttribute codeAttribute, TracedVariables variables, TracedStack stack, int startOffset)618     private void evaluateSingleInstructionBlock(Clazz            clazz,
619                                                 Method           method,
620                                                 CodeAttribute    codeAttribute,
621                                                 TracedVariables  variables,
622                                                 TracedStack      stack,
623                                                 int              startOffset)
624     {
625         byte[] code = codeAttribute.code;
626 
627         if (DEBUG)
628         {
629              System.out.println("Instruction block starting at ["+startOffset+"] in "+
630                                 ClassUtil.externalFullMethodDescription(clazz.getName(),
631                                                                         0,
632                                                                         method.getName(clazz),
633                                                                         method.getDescriptor(clazz)));
634              System.out.println("Init vars:  "+variables);
635              System.out.println("Init stack: "+stack);
636         }
637 
638         Processor processor = new Processor(variables,
639                                             stack,
640                                             valueFactory,
641                                             branchUnit,
642                                             invocationUnit);
643 
644         int instructionOffset = startOffset;
645 
646         int maxOffset = startOffset;
647 
648         // Evaluate the subsequent instructions.
649         while (true)
650         {
651             if (maxOffset < instructionOffset)
652             {
653                 maxOffset = instructionOffset;
654             }
655 
656             // Maintain a generalized local variable frame and stack at this
657             // instruction offset, before execution.
658             int evaluationCount = evaluationCounts[instructionOffset];
659             if (evaluationCount == 0)
660             {
661                 // First time we're passing by this instruction.
662                 if (variablesBefore[instructionOffset] == null)
663                 {
664                     // There's not even a context at this index yet.
665                     variablesBefore[instructionOffset] = new TracedVariables(variables);
666                     stacksBefore[instructionOffset]    = new TracedStack(stack);
667                 }
668                 else
669                 {
670                     // Reuse the context objects at this index.
671                     variablesBefore[instructionOffset].initialize(variables);
672                     stacksBefore[instructionOffset].copy(stack);
673                 }
674 
675                 // We'll execute in the generalized context, because it is
676                 // the same as the current context.
677                 generalizedContexts[instructionOffset] = true;
678             }
679             else
680             {
681                 // Merge in the current context.
682                 boolean variablesChanged = variablesBefore[instructionOffset].generalize(variables, true);
683                 boolean stackChanged     = stacksBefore[instructionOffset].generalize(stack);
684 
685                 //System.out.println("GVars:  "+variablesBefore[instructionOffset]);
686                 //System.out.println("GStack: "+stacksBefore[instructionOffset]);
687 
688                 // Bail out if the current context is the same as last time.
689                 if (!variablesChanged &&
690                     !stackChanged     &&
691                     generalizedContexts[instructionOffset])
692                 {
693                     if (DEBUG) System.out.println("Repeated variables, stack, and branch targets");
694 
695                     break;
696                 }
697 
698                 // See if this instruction has been evaluated an excessive number
699                 // of times.
700                 if (evaluationCount >= MAXIMUM_EVALUATION_COUNT)
701                 {
702                     if (DEBUG) System.out.println("Generalizing current context after "+evaluationCount+" evaluations");
703 
704                     // Continue, but generalize the current context.
705                     // Note that the most recent variable values have to remain
706                     // last in the generalizations, for the sake of the ret
707                     // instruction.
708                     variables.generalize(variablesBefore[instructionOffset], false);
709                     stack.generalize(stacksBefore[instructionOffset]);
710 
711                     // We'll execute in the generalized context.
712                     generalizedContexts[instructionOffset] = true;
713                 }
714                 else
715                 {
716                     // We'll execute in the current context.
717                     generalizedContexts[instructionOffset] = false;
718                 }
719             }
720 
721             // We'll evaluate this instruction.
722             evaluationCounts[instructionOffset]++;
723 
724             // Remember this instruction's offset with any stored value.
725             Value storeValue = new InstructionOffsetValue(instructionOffset);
726             variables.setProducerValue(storeValue);
727             stack.setProducerValue(storeValue);
728 
729             // Reset the trace value.
730             InstructionOffsetValue traceValue = InstructionOffsetValue.EMPTY_VALUE;
731 
732             // Note that the instruction is only volatile.
733             Instruction instruction = InstructionFactory.create(code, instructionOffset);
734 
735             // By default, the next instruction will be the one after this
736             // instruction.
737             int nextInstructionOffset = instructionOffset +
738                                         instruction.length(instructionOffset);
739             InstructionOffsetValue nextInstructionOffsetValue = new InstructionOffsetValue(nextInstructionOffset);
740             branchUnit.resetCalled();
741             branchUnit.setTraceBranchTargets(nextInstructionOffsetValue);
742 
743             if (DEBUG)
744             {
745                 System.out.println(instruction.toString(instructionOffset));
746             }
747 
748             try
749             {
750                 // Process the instruction. The processor may modify the
751                 // variables and the stack, and it may call the branch unit
752                 // and the invocation unit.
753                 instruction.accept(clazz,
754                                    method,
755                                    codeAttribute,
756                                    instructionOffset,
757                                    processor);
758             }
759             catch (RuntimeException ex)
760             {
761                 System.err.println("Unexpected error while evaluating instruction:");
762                 System.err.println("  Class       = ["+clazz.getName()+"]");
763                 System.err.println("  Method      = ["+method.getName(clazz)+method.getDescriptor(clazz)+"]");
764                 System.err.println("  Instruction = "+instruction.toString(instructionOffset));
765                 System.err.println("  Exception   = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")");
766 
767                 throw ex;
768             }
769 
770             // Collect the branch targets from the branch unit.
771             InstructionOffsetValue branchTargets = branchUnit.getTraceBranchTargets();
772             int branchTargetCount = branchTargets.instructionOffsetCount();
773 
774             // Stop tracing.
775             branchUnit.setTraceBranchTargets(traceValue);
776 
777             if (DEBUG)
778             {
779                 if (branchUnit.wasCalled())
780                 {
781                     System.out.println("     is branching to "+branchTargets);
782                 }
783                 if (branchTargetValues[instructionOffset] != null)
784                 {
785                     System.out.println("     has up till now been branching to "+branchTargetValues[instructionOffset]);
786                 }
787 
788                 System.out.println(" Vars:  "+variables);
789                 System.out.println(" Stack: "+stack);
790             }
791 
792             // Maintain a generalized local variable frame and stack at this
793             // instruction offset, after execution.
794             if (evaluationCount == 0)
795             {
796                 // First time we're passing by this instruction.
797                 if (variablesAfter[instructionOffset] == null)
798                 {
799                     // There's not even a context at this index yet.
800                     variablesAfter[instructionOffset] = new TracedVariables(variables);
801                     stacksAfter[instructionOffset]    = new TracedStack(stack);
802                 }
803                 else
804                 {
805                     // Reuse the context objects at this index.
806                     variablesAfter[instructionOffset].initialize(variables);
807                     stacksAfter[instructionOffset].copy(stack);
808                 }
809             }
810             else
811             {
812                 // Merge in the current context.
813                 variablesAfter[instructionOffset].generalize(variables, true);
814                 stacksAfter[instructionOffset].generalize(stack);
815             }
816 
817             // Did the branch unit get called?
818             if (branchUnit.wasCalled())
819             {
820                 // Accumulate the branch targets at this offset.
821                 branchTargetValues[instructionOffset] = branchTargetValues[instructionOffset] == null ?
822                     branchTargets :
823                     branchTargetValues[instructionOffset].generalize(branchTargets).instructionOffsetValue();
824 
825                 // Are there no branch targets at all?
826                 if (branchTargetCount == 0)
827                 {
828                     // Exit from this code block.
829                     break;
830                 }
831 
832                 // Accumulate the branch origins at the branch target offsets.
833                 InstructionOffsetValue instructionOffsetValue = new InstructionOffsetValue(instructionOffset);
834                 for (int index = 0; index < branchTargetCount; index++)
835                 {
836                     int branchTarget = branchTargets.instructionOffset(index);
837                     branchOriginValues[branchTarget] = branchOriginValues[branchTarget] == null ?
838                         instructionOffsetValue:
839                         branchOriginValues[branchTarget].generalize(instructionOffsetValue).instructionOffsetValue();
840                 }
841 
842                 // Are there multiple branch targets?
843                 if (branchTargetCount > 1)
844                 {
845                     // Push them on the execution stack and exit from this block.
846                     for (int index = 0; index < branchTargetCount; index++)
847                     {
848                         if (DEBUG) System.out.println("Pushing alternative branch #"+index+" out of "+branchTargetCount+", from ["+instructionOffset+"] to ["+branchTargets.instructionOffset(index)+"]");
849 
850                         pushInstructionBlock(new TracedVariables(variables),
851                                              new TracedStack(stack),
852                                              branchTargets.instructionOffset(index));
853                     }
854 
855                     break;
856                 }
857 
858                 if (DEBUG) System.out.println("Definite branch from ["+instructionOffset+"] to ["+branchTargets.instructionOffset(0)+"]");
859             }
860 
861             // Just continue with the next instruction.
862             instructionOffset = branchTargets.instructionOffset(0);
863 
864             // Is this a subroutine invocation?
865             if (instruction.opcode == InstructionConstants.OP_JSR ||
866                 instruction.opcode == InstructionConstants.OP_JSR_W)
867             {
868                 // Evaluate the subroutine in another partial evaluator.
869                 evaluateSubroutine(clazz,
870                                    method,
871                                    codeAttribute,
872                                    variables,
873                                    stack,
874                                    instructionOffset,
875                                    instructionBlockStack);
876 
877                 break;
878             }
879             else if (instruction.opcode == InstructionConstants.OP_RET)
880             {
881                 // Let the partial evaluator that has called the subroutine
882                 // handle the evaluation after the return.
883                 pushCallingInstructionBlock(new TracedVariables(variables),
884                                             new TracedStack(stack),
885                                             instructionOffset);
886                 break;
887             }
888         }
889 
890         if (DEBUG) System.out.println("Ending processing of instruction block starting at ["+startOffset+"]");
891     }
892 
893 
894     /**
895      * Evaluates a subroutine and its exception handlers, starting at the given
896      * offset and ending at a subroutine return instruction.
897      */
evaluateSubroutine(Clazz clazz, Method method, CodeAttribute codeAttribute, TracedVariables variables, TracedStack stack, int subroutineStart, java.util.Stack instructionBlockStack)898     private void evaluateSubroutine(Clazz           clazz,
899                                     Method          method,
900                                     CodeAttribute   codeAttribute,
901                                     TracedVariables variables,
902                                     TracedStack     stack,
903                                     int             subroutineStart,
904                                     java.util.Stack instructionBlockStack)
905     {
906         int subroutineEnd = branchTargetFinder.subroutineEnd(subroutineStart);
907 
908         if (DEBUG) System.out.println("Evaluating subroutine from "+subroutineStart+" to "+subroutineEnd);
909 
910         // Create a temporary partial evaluator, so there are no conflicts
911         // with variables that are alive across subroutine invocations, between
912         // different invocations.
913         PartialEvaluator subroutinePartialEvaluator =
914             new PartialEvaluator(this);
915 
916         subroutinePartialEvaluator.initializeArrays(codeAttribute);
917 
918         // Evaluate the subroutine.
919         subroutinePartialEvaluator.evaluateInstructionBlockAndExceptionHandlers(clazz,
920                                                                                 method,
921                                                                                 codeAttribute,
922                                                                                 variables,
923                                                                                 stack,
924                                                                                 subroutineStart,
925                                                                                 subroutineEnd);
926 
927         // Merge back the temporary partial evaluator. This way, we'll get
928         // the lowest common denominator of stacks and variables.
929         generalize(subroutinePartialEvaluator, 0, codeAttribute.u4codeLength);
930 
931         if (DEBUG) System.out.println("Ending subroutine from "+subroutineStart+" to "+subroutineEnd);
932     }
933 
934 
935     /**
936      * Generalizes the results of this partial evaluator with those of another
937      * given partial evaluator, over a given range of instructions.
938      */
generalize(PartialEvaluator other, int codeStart, int codeEnd)939     private void generalize(PartialEvaluator other,
940                             int              codeStart,
941                             int              codeEnd)
942     {
943         if (DEBUG) System.out.println("Generalizing with temporary partial evaluation");
944 
945         for (int offset = codeStart; offset < codeEnd; offset++)
946         {
947             if (other.branchOriginValues[offset] != null)
948             {
949                 branchOriginValues[offset] = branchOriginValues[offset] == null ?
950                     other.branchOriginValues[offset] :
951                     branchOriginValues[offset].generalize(other.branchOriginValues[offset]).instructionOffsetValue();
952             }
953 
954             if (other.isTraced(offset))
955             {
956                 if (other.branchTargetValues[offset] != null)
957                 {
958                     branchTargetValues[offset] = branchTargetValues[offset] == null ?
959                         other.branchTargetValues[offset] :
960                         branchTargetValues[offset].generalize(other.branchTargetValues[offset]).instructionOffsetValue();
961                 }
962 
963                 if (evaluationCounts[offset] == 0)
964                 {
965                     variablesBefore[offset]     = other.variablesBefore[offset];
966                     stacksBefore[offset]        = other.stacksBefore[offset];
967                     variablesAfter[offset]      = other.variablesAfter[offset];
968                     stacksAfter[offset]         = other.stacksAfter[offset];
969                     generalizedContexts[offset] = other.generalizedContexts[offset];
970                     evaluationCounts[offset]    = other.evaluationCounts[offset];
971                 }
972                 else
973                 {
974                     variablesBefore[offset].generalize(other.variablesBefore[offset], false);
975                     stacksBefore[offset]   .generalize(other.stacksBefore[offset]);
976                     variablesAfter[offset] .generalize(other.variablesAfter[offset], false);
977                     stacksAfter[offset]    .generalize(other.stacksAfter[offset]);
978                     //generalizedContexts[offset]
979                     evaluationCounts[offset] += other.evaluationCounts[offset];
980                 }
981             }
982         }
983     }
984 
985 
986     /**
987      * Evaluates the exception handlers covering and targeting the given
988      * instruction range in the given code.
989      */
evaluateExceptionHandlers(Clazz clazz, Method method, CodeAttribute codeAttribute, int startOffset, int endOffset)990     private void evaluateExceptionHandlers(Clazz         clazz,
991                                            Method        method,
992                                            CodeAttribute codeAttribute,
993                                            int           startOffset,
994                                            int           endOffset)
995     {
996         if (DEBUG) System.out.println("Evaluating exceptions covering ["+startOffset+" -> "+endOffset+"]:");
997 
998         ExceptionHandlerFilter exceptionEvaluator =
999             new ExceptionHandlerFilter(startOffset,
1000                                        endOffset,
1001                                        this);
1002 
1003         // Evaluate the exception catch blocks, until their entry variables
1004         // have stabilized.
1005         do
1006         {
1007             // Reset the flag to stop evaluating.
1008             evaluateExceptions = false;
1009 
1010             // Evaluate all relevant exception catch blocks once.
1011             codeAttribute.exceptionsAccept(clazz,
1012                                            method,
1013                                            startOffset,
1014                                            endOffset,
1015                                            exceptionEvaluator);
1016         }
1017         while (evaluateExceptions);
1018     }
1019 
1020 
1021     // Implementations for ExceptionInfoVisitor.
1022 
visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)1023     public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
1024     {
1025         int startPC = exceptionInfo.u2startPC;
1026         int endPC   = exceptionInfo.u2endPC;
1027 
1028         // Do we have to evaluate this exception catch block?
1029         if (isTraced(startPC, endPC))
1030         {
1031             int handlerPC = exceptionInfo.u2handlerPC;
1032             int catchType = exceptionInfo.u2catchType;
1033 
1034             if (DEBUG) System.out.println("Evaluating exception ["+startPC +" -> "+endPC +": "+handlerPC+"]:");
1035 
1036             // Reuse the existing variables and stack objects, ensuring the
1037             // right size.
1038             TracedVariables variables = new TracedVariables(codeAttribute.u2maxLocals);
1039             TracedStack     stack     = new TracedStack(codeAttribute.u2maxStack);
1040 
1041             // Initialize the trace values.
1042             Value storeValue = new InstructionOffsetValue(AT_CATCH_ENTRY);
1043             variables.setProducerValue(storeValue);
1044             stack.setProducerValue(storeValue);
1045 
1046             // Initialize the variables by generalizing the variables of the
1047             // try block. Make sure to include the results of the last
1048             // instruction for preverification.
1049             generalizeVariables(startPC,
1050                                 endPC,
1051                                 evaluateAllCode,
1052                                 variables);
1053 
1054             // Initialize the the stack.
1055             //stack.push(valueFactory.createReference((ClassConstant)((ProgramClass)clazz).getConstant(exceptionInfo.u2catchType), false));
1056             String catchClassName = catchType != 0 ?
1057                  clazz.getClassName(catchType) :
1058                  ClassConstants.INTERNAL_NAME_JAVA_LANG_THROWABLE;
1059 
1060             Clazz catchClass = catchType != 0 ?
1061                 ((ClassConstant)((ProgramClass)clazz).getConstant(catchType)).referencedClass :
1062                 null;
1063 
1064             stack.push(valueFactory.createReferenceValue(catchClassName,
1065                                                          catchClass,
1066                                                          false));
1067 
1068             int evaluationCount = evaluationCounts[handlerPC];
1069 
1070             // Evaluate the instructions, starting at the entry point.
1071             evaluateInstructionBlock(clazz,
1072                                      method,
1073                                      codeAttribute,
1074                                      variables,
1075                                      stack,
1076                                      handlerPC);
1077 
1078             // Remember to evaluate all exception handlers once more.
1079             if (!evaluateExceptions)
1080             {
1081                 evaluateExceptions = evaluationCount < evaluationCounts[handlerPC];
1082             }
1083         }
1084 //        else if (evaluateAllCode)
1085 //        {
1086 //            if (DEBUG) System.out.println("No information for partial evaluation of exception ["+startPC +" -> "+endPC +": "+exceptionInfo.u2handlerPC+"] yet");
1087 //
1088 //            // We don't have any information on the try block yet, but we do
1089 //            // have to evaluate the exception handler.
1090 //            // Remember to evaluate all exception handlers once more.
1091 //            evaluateExceptions = true;
1092 //        }
1093         else
1094         {
1095             if (DEBUG) System.out.println("No information for partial evaluation of exception ["+startPC +" -> "+endPC +": "+exceptionInfo.u2handlerPC+"]");
1096         }
1097     }
1098 
1099 
1100     // Small utility methods.
1101 
1102     /**
1103      * Initializes the data structures for the variables, stack, etc.
1104      */
1105     private void initializeArrays(CodeAttribute codeAttribute)
1106     {
1107         int codeLength = codeAttribute.u4codeLength;
1108 
1109         // Create new arrays for storing information at each instruction offset.
1110         if (variablesAfter.length < codeLength)
1111         {
1112             // Create new arrays.
1113             branchOriginValues  = new InstructionOffsetValue[codeLength];
1114             branchTargetValues  = new InstructionOffsetValue[codeLength];
1115             variablesBefore     = new TracedVariables[codeLength];
1116             stacksBefore        = new TracedStack[codeLength];
1117             variablesAfter      = new TracedVariables[codeLength];
1118             stacksAfter         = new TracedStack[codeLength];
1119             generalizedContexts = new boolean[codeLength];
1120             evaluationCounts    = new int[codeLength];
1121         }
1122         else
1123         {
1124             // Reset the arrays.
1125             Arrays.fill(branchOriginValues,  null);
1126             Arrays.fill(branchTargetValues,  null);
1127             Arrays.fill(generalizedContexts, false);
1128             Arrays.fill(evaluationCounts,    0);
1129 
1130             for (int index = 0; index < codeLength; index++)
1131             {
1132                 if (variablesBefore[index] != null)
1133                 {
1134                     variablesBefore[index].reset(codeAttribute.u2maxLocals);
1135                 }
1136 
1137                 if (stacksBefore[index] != null)
1138                 {
1139                     stacksBefore[index].reset(codeAttribute.u2maxStack);
1140                 }
1141 
1142                 if (variablesAfter[index] != null)
1143                 {
1144                     variablesAfter[index].reset(codeAttribute.u2maxLocals);
1145                 }
1146 
1147                 if (stacksAfter[index] != null)
1148                 {
1149                     stacksAfter[index].reset(codeAttribute.u2maxStack);
1150                 }
1151             }
1152         }
1153     }
1154 
1155 
1156     /**
1157      * Initializes the data structures for the variables, stack, etc.
1158      */
1159     private void initializeParameters(Clazz           clazz,
1160                                       Method          method,
1161                                       CodeAttribute   codeAttribute,
1162                                       TracedVariables variables)
1163     {
1164         // Create the method parameters.
1165         TracedVariables parameters = new TracedVariables(codeAttribute.u2maxLocals);
1166 
1167         // Remember this instruction's offset with any stored value.
1168         Value storeValue = new InstructionOffsetValue(AT_METHOD_ENTRY);
1169         parameters.setProducerValue(storeValue);
1170 
1171         // Initialize the method parameters.
1172         invocationUnit.enterMethod(clazz, method, parameters);
1173 
1174         if (DEBUG)
1175         {
1176             System.out.println("  Params: "+parameters);
1177         }
1178 
1179         // Initialize the variables with the parameters.
1180         variables.initialize(parameters);
1181 
1182         // Set the store value of each parameter variable.
1183         InstructionOffsetValue atMethodEntry = new InstructionOffsetValue(AT_METHOD_ENTRY);
1184 
1185         for (int index = 0; index < parameters.size(); index++)
1186         {
1187             variables.setProducerValue(index, atMethodEntry);
1188         }
1189     }
1190 
1191 
1192     /**
1193      * Generalize the local variable frames of a block of instructions.
1194      */
1195     private void generalizeVariables(int             startOffset,
1196                                      int             endOffset,
1197                                      boolean         includeAfterLastInstruction,
1198                                      TracedVariables generalizedVariables)
1199     {
1200         boolean first     = true;
1201         int     lastIndex = -1;
1202 
1203         // Generalize the variables before each of the instructions in the block.
1204         for (int index = startOffset; index < endOffset; index++)
1205         {
1206             if (isTraced(index))
1207             {
1208                 TracedVariables tracedVariables = variablesBefore[index];
1209 
1210                 if (first)
1211                 {
1212                     // Initialize the variables with the first traced local
1213                     // variable frame.
1214                     generalizedVariables.initialize(tracedVariables);
1215 
1216                     first = false;
1217                 }
1218                 else
1219                 {
1220                     // Generalize the variables with the traced local variable
1221                     // frame. We can't use the return value, because local
1222                     // generalization can be different a couple of times,
1223                     // with the global generalization being the same.
1224                     generalizedVariables.generalize(tracedVariables, false);
1225                 }
1226 
1227                 lastIndex = index;
1228             }
1229         }
1230 
1231         // Generalize the variables after the last instruction in the block,
1232         // if required.
1233         if (includeAfterLastInstruction &&
1234             lastIndex >= 0)
1235         {
1236             TracedVariables tracedVariables = variablesAfter[lastIndex];
1237 
1238             if (first)
1239             {
1240                 // Initialize the variables with the local variable frame.
1241                 generalizedVariables.initialize(tracedVariables);
1242             }
1243             else
1244             {
1245                 // Generalize the variables with the local variable frame.
1246                 generalizedVariables.generalize(tracedVariables, false);
1247             }
1248         }
1249 
1250         // Just clear the variables if there aren't any traced instructions
1251         // in the block.
1252         if (first)
1253         {
1254             generalizedVariables.reset(generalizedVariables.size());
1255         }
1256     }
1257 
1258 
1259     private static class MyInstructionBlock
1260     {
1261         private TracedVariables variables;
1262         private TracedStack     stack;
1263         private int             startOffset;
1264 
1265 
1266         private MyInstructionBlock(TracedVariables variables,
1267                                    TracedStack     stack,
1268                                    int             startOffset)
1269         {
1270             this.variables   = variables;
1271             this.stack       = stack;
1272             this.startOffset = startOffset;
1273         }
1274     }
1275 }
1276