View Javadoc

    1   /*
    2    $Id: CompileStack.java 4352 2006-12-13 15:58:48Z blackdrag $
    3
    4    Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
    5
    6    Redistribution and use of this software and associated documentation
    7    ("Software"), with or without modification, are permitted provided
    8    that the following conditions are met:
    9
    10   1. Redistributions of source code must retain copyright
    11      statements and notices.  Redistributions must also contain a
    12      copy of this document.
    13
    14   2. Redistributions in binary form must reproduce the
    15      above copyright notice, this list of conditions and the
    16      following disclaimer in the documentation and/or other
    17      materials provided with the distribution.
    18
    19   3. The name "groovy" must not be used to endorse or promote
    20      products derived from this Software without prior written
    21      permission of The Codehaus.  For written permission,
    22      please contact info@codehaus.org.
    23
    24   4. Products derived from this Software may not be called "groovy"
    25      nor may "groovy" appear in their names without prior written
    26      permission of The Codehaus. "groovy" is a registered
    27      trademark of The Codehaus.
    28
    29   5. Due credit should be given to The Codehaus -
    30      http://groovy.codehaus.org/
    31
    32   THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
    33   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
    34   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
    35   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
    36   THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
    37   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    38   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    39   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    40   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
    41   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    42   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
    43   OF THE POSSIBILITY OF SUCH DAMAGE.
    44
    45   */
    46
    47  package org.codehaus.groovy.classgen;
    48
    49  import java.util.ArrayList;
    50  import java.util.Collections;
    51  import java.util.HashMap;
    52  import java.util.Iterator;
    53  import java.util.LinkedList;
    54  import java.util.List;
    55  import java.util.ListIterator;
    56
    57  import org.codehaus.groovy.GroovyBugError;
    58  import org.codehaus.groovy.ast.ClassHelper;
    59  import org.codehaus.groovy.ast.ClassNode;
    60  import org.codehaus.groovy.ast.Parameter;
    61  import org.codehaus.groovy.ast.VariableScope;
    62  import org.objectweb.asm.Label;
    63  import org.objectweb.asm.MethodVisitor;
    64  import org.objectweb.asm.Opcodes;
    65
    66  /***
    67   * This class is a helper for AsmClassGenerator. It manages
    68   * different aspects of the code of a code block like
    69   * handling labels, defining variables, and scopes.
    70   * After a MethodNode is visited clear should be called, for
    71   * initialization the method init should be used.
    72   * <p>
    73   * Some Notes:
    74   * <ul>
    75   * <li> every push method will require a later pop call
    76   * <li> method parameters may define a category 2 variable, so
    77   *      don't ignore the type stored in the variable object
    78   * <li> the index of the variable may not be as assumed when
    79   *      the variable is a parameter of a method because the
    80   *      parameter may be used in a closure, so don't ignore
    81   *      the stored variable index
    82   * <li> the names of temporary variables can be ignored. The names
    83   *      are only used for debugging and do not conflict with each
    84   *      other or normal variables. For accessing the index of the
    85   *      variable must be used.
    86   * </ul>
    87   *
    88   *
    89   * @see org.codehaus.groovy.classgen.AsmClassGenerator
    90   * @author Jochen Theodorou
    91   */
    92  public class CompileStack implements Opcodes {
    93      /***
    94       * @TODO remove optimization of this.foo -> this.@foo
    95       *
    96       */
    97
    98      // state flag
    99      private boolean clear=true;
    100     // current scope
    101     private VariableScope scope;
    102     // current label for continue
    103     private Label continueLabel;
    104     // current label for break
    105     private Label breakLabel;
    106     // available variables on stack
    107     private HashMap stackVariables = new HashMap();
    108     // index of the last variable on stack
    109     private int currentVariableIndex = 1;
    110     // index for the next variable on stack
    111     private int nextVariableIndex = 1;
    112     // currently temporary variables in use
    113     private LinkedList temporaryVariables = new LinkedList();
    114     // overall used variables for a method/constructor
    115     private LinkedList usedVariables = new LinkedList();
    116     // map containing named labels of parenting blocks
    117     private HashMap superBlockNamedLabels = new HashMap();
    118     // map containing named labels of current block
    119     private HashMap currentBlockNamedLabels = new HashMap();
    120     // list containing runnables representing a finally block
    121     // such a block is created by synchronized or finally and
    122     // must be called for break/continue/return
    123     private LinkedList finallyBlocks = new LinkedList();
    124     // a list of blocks already visiting.
    125     private List visitedBlocks = new LinkedList();
    126
    127     private Label thisStartLabel, thisEndLabel;
    128
    129     // current class index
    130     private int currentClassIndex , currentMetaClassIndex;
    131
    132     private MethodVisitor mv;
    133     private BytecodeHelper helper;
    134
    135     // helper to handle different stack based variables
    136     private LinkedList stateStack = new LinkedList();
    137
    138     // defines the first variable index useable after
    139     // all parameters of a method
    140     private int localVariableOffset;
    141     // this is used to store the goals for a "break foo" call
    142     // in a loop where foo is a label.
    143 	private HashMap namedLoopBreakLabel = new HashMap();
    144 	//this is used to store the goals for a "continue foo" call
    145     // in a loop where foo is a label.
    146 	private HashMap namedLoopContinueLabel = new HashMap();
    147     private String className;
    148
    149     private class StateStackElement {
    150         VariableScope _scope;
    151         Label _continueLabel;
    152         Label _breakLabel;
    153         Label _finallyLabel;
    154         int _lastVariableIndex;
    155         int _nextVariableIndex;
    156         HashMap _stackVariables;
    157         LinkedList _temporaryVariables = new LinkedList();
    158         LinkedList _usedVariables = new LinkedList();
    159         HashMap _superBlockNamedLabels;
    160         HashMap _currentBlockNamedLabels;
    161         LinkedList _finallyBlocks;
    162
    163         StateStackElement() {
    164             _scope = CompileStack.this.scope;
    165             _continueLabel = CompileStack.this.continueLabel;
    166             _breakLabel = CompileStack.this.breakLabel;
    167             _lastVariableIndex = CompileStack.this.currentVariableIndex;
    168             _stackVariables = CompileStack.this.stackVariables;
    169             _temporaryVariables = CompileStack.this.temporaryVariables;
    170             _nextVariableIndex = nextVariableIndex;
    171             _superBlockNamedLabels = superBlockNamedLabels;
    172             _currentBlockNamedLabels = currentBlockNamedLabels;
    173             _finallyBlocks = finallyBlocks;
    174         }
    175     }
    176
    177     private void pushState() {
    178         stateStack.add(new StateStackElement());
    179         stackVariables = new HashMap(stackVariables);
    180         finallyBlocks = new LinkedList(finallyBlocks);
    181     }
    182
    183     private void popState() {
    184         if (stateStack.size()==0) {
    185              throw new GroovyBugError("Tried to do a pop on the compile stack without push.");
    186         }
    187         StateStackElement element = (StateStackElement) stateStack.removeLast();
    188         scope = element._scope;
    189         continueLabel = element._continueLabel;
    190         breakLabel = element._breakLabel;
    191         currentVariableIndex = element._lastVariableIndex;
    192         stackVariables = element._stackVariables;
    193         nextVariableIndex = element._nextVariableIndex;
    194         finallyBlocks = element._finallyBlocks;
    195     }
    196
    197     public Label getContinueLabel() {
    198         return continueLabel;
    199     }
    200
    201     public Label getBreakLabel() {
    202         return breakLabel;
    203     }
    204
    205     public void removeVar(int tempIndex) {
    206         for (Iterator iter = temporaryVariables.iterator(); iter.hasNext();) {
    207             Variable element = (Variable) iter.next();
    208             if (element.getIndex()==tempIndex) {
    209                 iter.remove();
    210                 return;
    211             }
    212         }
    213         throw new GroovyBugError("CompileStack#removeVar: tried to remove a temporary variable with a non existent index");
    214     }
    215
    216     private void setEndLabels(){
    217         Label endLabel = new Label();
    218         mv.visitLabel(endLabel);
    219         for (Iterator iter = stackVariables.values().iterator(); iter.hasNext();) {
    220             Variable var = (Variable) iter.next();
    221             var.setEndLabel(endLabel);
    222         }
    223         thisEndLabel = endLabel;
    224     }
    225
    226     public void pop() {
    227         setEndLabels();
    228         popState();
    229     }
    230
    231     public VariableScope getScope() {
    232         return scope;
    233     }
    234
    235     /***
    236      * creates a temporary variable.
    237      *
    238      * @param var defines type and name
    239      * @param store defines if the toplevel argument of the stack should be stored
    240      * @return the index used for this temporary variable
    241      */
    242     public int defineTemporaryVariable(org.codehaus.groovy.ast.Variable var, boolean store) {
    243         return defineTemporaryVariable(var.getName(), var.getType(),store);
    244     }
    245
    246     public Variable getVariable(String variableName ) {
    247         return getVariable(variableName,true);
    248     }
    249
    250     public Variable getVariable(String variableName, boolean mustExist) {
    251         if (variableName.equals("this")) return Variable.THIS_VARIABLE;
    252         if (variableName.equals("super")) return Variable.SUPER_VARIABLE;
    253         Variable v = (Variable) stackVariables.get(variableName);
    254         if (v==null && mustExist)  throw new GroovyBugError("tried to get a variable with the name "+variableName+" as stack variable, but a variable with this name was not created");
    255         return v;
    256     }
    257
    258     /***
    259      * creates a temporary variable.
    260      *
    261      * @param name defines type and name
    262      * @param store defines if the toplevel argument of the stack should be stored
    263      * @return the index used for this temporary variable
    264      */
    265     public int defineTemporaryVariable(String name,boolean store) {
    266         return defineTemporaryVariable(name, ClassHelper.DYNAMIC_TYPE,store);
    267     }
    268
    269     /***
    270      * creates a temporary variable.
    271      *
    272      * @param name defines the name
    273      * @param node defines the node
    274      * @param store defines if the toplevel argument of the stack should be stored
    275      * @return the index used for this temporary variable
    276      */
    277     public int defineTemporaryVariable(String name, ClassNode node, boolean store) {
    278         Variable answer = defineVar(name,node,false);
    279         temporaryVariables.add(answer);
    280         usedVariables.removeLast();
    281
    282         if (store) mv.visitVarInsn(ASTORE, currentVariableIndex);
    283
    284         return answer.getIndex();
    285     }
    286
    287     private void resetVariableIndex(boolean isStatic) {
    288         if (!isStatic) {
    289             currentVariableIndex=1;
    290             nextVariableIndex=1;
    291         } else {
    292             currentVariableIndex=0;
    293             nextVariableIndex=0;
    294         }
    295     }
    296
    297     /***
    298      * Clears the state of the class. This method should be called
    299      * after a MethodNode is visited. Note that a call to init will
    300      * fail if clear is not called before
    301      */
    302     public void clear() {
    303         if (stateStack.size()>1) {
    304             int size = stateStack.size()-1;
    305             throw new GroovyBugError("the compile stack contains "+size+" more push instruction"+(size==1?"":"s")+" than pops.");
    306         }
    307         clear = true;
    308         // br experiment with local var table so debuggers can retrieve variable names
    309         if (true) {//AsmClassGenerator.CREATE_DEBUG_INFO) {
    310             if (thisEndLabel==null) setEndLabels();
    311
    312             if (!scope.isInStaticContext()) {
    313                 // write "this"
    314                 mv.visitLocalVariable("this", className, null, thisStartLabel, thisEndLabel, 0);
    315             }
    316
    317             for (Iterator iterator = usedVariables.iterator(); iterator.hasNext();) {
    318                 Variable v = (Variable) iterator.next();
    319                 String type = BytecodeHelper.getTypeDescription(v.getType());
    320                 Label start = v.getStartLabel();
    321                 Label end = v.getEndLabel();
    322                 mv.visitLocalVariable(v.getName(), type, null, start, end, v.getIndex());
    323             }
    324         }
    325         pop();
    326         stackVariables.clear();
    327         usedVariables.clear();
    328         scope = null;
    329         mv=null;
    330         resetVariableIndex(false);
    331         superBlockNamedLabels.clear();
    332         currentBlockNamedLabels.clear();
    333         namedLoopBreakLabel.clear();
    334         namedLoopContinueLabel.clear();
    335         continueLabel=null;
    336         breakLabel=null;
    337         helper = null;
    338         thisStartLabel=null;
    339         thisEndLabel=null;
    340     }
    341
    342     /***
    343      * initializes this class for a MethodNode. This method will
    344      * automatically define varibales for the method parameters
    345      * and will create references if needed. the created variables
    346      * can be get by getVariable
    347      *
    348      */
    349     protected void init(VariableScope el, Parameter[] parameters, MethodVisitor mv, ClassNode cn) {
    350         if (!clear) throw new GroovyBugError("CompileStack#init called without calling clear before");
    351         clear=false;
    352         pushVariableScope(el);
    353         this.mv = mv;
    354         this.helper = new BytecodeHelper(mv);
    355         defineMethodVariables(parameters,el.isInStaticContext());
    356         this.className = BytecodeHelper.getTypeDescription(cn);
    357         currentClassIndex = -1; currentMetaClassIndex = -1;
    358     }
    359
    360     /***
    361      * Causes the statestack to add an element and sets
    362      * the given scope as new current variable scope. Creates
    363      * a element for the state stack so pop has to be called later
    364      */
    365     protected void pushVariableScope(VariableScope el) {
    366         pushState();
    367         scope = el;
    368         superBlockNamedLabels = new HashMap(superBlockNamedLabels);
    369         superBlockNamedLabels.putAll(currentBlockNamedLabels);
    370         currentBlockNamedLabels = new HashMap();
    371     }
    372
    373     /***
    374      * Should be called when decending into a loop that defines
    375      * also a scope. Calls pushVariableScope and prepares labels
    376      * for a loop structure. Creates a element for the state stack
    377      * so pop has to be called later
    378      */
    379     protected void pushLoop(VariableScope el, String labelName) {
    380         pushVariableScope(el);
    381         initLoopLabels(labelName);
    382     }
    383
    384     private void initLoopLabels(String labelName) {
    385         continueLabel = new Label();
    386         breakLabel = new Label();
    387         if (labelName!=null) {
    388         	namedLoopBreakLabel.put(labelName,breakLabel);
    389         	namedLoopContinueLabel.put(labelName,continueLabel);
    390         }
    391     }
    392
    393     /***
    394      * Should be called when decending into a loop that does
    395      * not define a scope. Creates a element for the state stack
    396      * so pop has to be called later
    397      */
    398     protected void pushLoop(String labelName) {
    399         pushState();
    400         initLoopLabels(labelName);
    401     }
    402
    403     /***
    404      * Used for <code>break foo</code> inside a loop to end the
    405      * execution of the marked loop. This method will return the
    406      * break label of the loop if there is one found for the name.
    407      * If not, the current break label is returned.
    408      */
    409     protected Label getNamedBreakLabel(String name) {
    410     	Label label = getBreakLabel();
    411     	Label endLabel = null;
    412         if (name!=null) endLabel = (Label) namedLoopBreakLabel.get(name);
    413     	if (endLabel!=null) label = endLabel;
    414         return label;
    415     }
    416
    417     /***
    418      * Used for <code>continue foo</code> inside a loop to continue
    419      * the execution of the marked loop. This method will return
    420      * the break label of the loop if there is one found for the
    421      * name. If not, getLabel is used.
    422      */
    423     protected Label getNamedContinueLabel(String name) {
    424     	Label label = getLabel(name);
    425     	Label endLabel = null;
    426         if (name!=null) endLabel = (Label) namedLoopContinueLabel.get(name);
    427     	if (endLabel!=null) label = endLabel;
    428         return label;
    429     }
    430
    431     /***
    432      * Creates a new break label and a element for the state stack
    433      * so pop has to be called later
    434      */
    435     protected Label pushSwitch(){
    436         pushState();
    437         breakLabel = new Label();
    438         return breakLabel;
    439     }
    440
    441     /***
    442      * because a boolean Expression may not be evaluated completly
    443      * it is important to keep the registers clean
    444      */
    445     protected void pushBooleanExpression(){
    446         pushState();
    447     }
    448
    449     private Variable defineVar(String name, ClassNode type, boolean methodParameterUsedInClosure) {
    450         makeNextVariableID(type);
    451         int index = currentVariableIndex;
    452         if (methodParameterUsedInClosure) {
    453             index = localVariableOffset++;
    454         }
    455         Variable answer = new Variable(index, type, name);
    456         usedVariables.add(answer);
    457         answer.setHolder(methodParameterUsedInClosure);
    458         return answer;
    459     }
    460
    461     private void makeLocalVariablesOffset(Parameter[] paras,boolean isInStaticContext) {
    462         resetVariableIndex(isInStaticContext);
    463
    464         for (int i = 0; i < paras.length; i++) {
    465             makeNextVariableID(paras[i].getType());
    466         }
    467         localVariableOffset = nextVariableIndex;
    468
    469         resetVariableIndex(isInStaticContext);
    470     }
    471
    472     private void defineMethodVariables(Parameter[] paras,boolean isInStaticContext) {
    473         Label startLabel  = new Label();
    474         thisStartLabel = startLabel;
    475         mv.visitLabel(startLabel);
    476
    477         makeLocalVariablesOffset(paras,isInStaticContext);
    478
    479         boolean hasHolder = false;
    480         for (int i = 0; i < paras.length; i++) {
    481             String name = paras[i].getName();
    482             Variable answer;
    483             if (paras[i].isClosureSharedVariable()) {
    484                 answer = defineVar(name, ClassHelper.getWrapper(paras[i].getType()), true);
    485                 ClassNode type = paras[i].getType();
    486                 helper.load(type,currentVariableIndex);
    487                 helper.box(type);
    488                 createReference(answer);
    489                 hasHolder = true;
    490             } else {
    491                 answer = defineVar(name,paras[i].getType(),false);
    492             }
    493             answer.setStartLabel(startLabel);
    494             stackVariables.put(name, answer);
    495         }
    496
    497         if (hasHolder) {
    498             nextVariableIndex = localVariableOffset;
    499         }
    500     }
    501
    502     private void createReference(Variable reference) {
    503         mv.visitTypeInsn(NEW, "groovy/lang/Reference");
    504         mv.visitInsn(DUP_X1);
    505         mv.visitInsn(SWAP);
    506         mv.visitMethodInsn(INVOKESPECIAL, "groovy/lang/Reference", "<init>", "(Ljava/lang/Object;)V");
    507         mv.visitVarInsn(ASTORE, reference.getIndex());
    508     }
    509
    510     /***
    511      * Defines a new Variable using an AST variable.
    512      * @param initFromStack if true the last element of the
    513      *                      stack will be used to initilize
    514      *                      the new variable. If false null
    515      *                      will be used.
    516      */
    517     public Variable defineVariable(org.codehaus.groovy.ast.Variable v, boolean initFromStack) {
    518         String name = v.getName();
    519         Variable answer = defineVar(name,v.getType(),false);
    520         if (v.isClosureSharedVariable()) answer.setHolder(true);
    521         stackVariables.put(name, answer);
    522
    523         Label startLabel  = new Label();
    524         answer.setStartLabel(startLabel);
    525         if (answer.isHolder())  {
    526             if (!initFromStack) mv.visitInsn(ACONST_NULL);
    527             createReference(answer);
    528         } else {
    529             if (!initFromStack) mv.visitInsn(ACONST_NULL);
    530             mv.visitVarInsn(ASTORE, currentVariableIndex);
    531         }
    532         mv.visitLabel(startLabel);
    533         return answer;
    534     }
    535
    536     /***
    537      * Returns true if a varibale is already defined
    538      */
    539     public boolean containsVariable(String name) {
    540         return stackVariables.containsKey(name);
    541     }
    542
    543     /***
    544      * Calculates the index of the next free register stores ir
    545      * and sets the current variable index to the old value
    546      */
    547     private void makeNextVariableID(ClassNode type) {
    548         currentVariableIndex = nextVariableIndex;
    549         if (type==ClassHelper.long_TYPE || type==ClassHelper.double_TYPE) {
    550             nextVariableIndex++;
    551         }
    552         nextVariableIndex++;
    553     }
    554
    555     /***
    556      * Returns the label for the given name
    557      */
    558     public Label getLabel(String name) {
    559         if (name==null) return null;
    560         Label l = (Label) superBlockNamedLabels.get(name);
    561         if (l==null) l = createLocalLabel(name);
    562         return l;
    563     }
    564
    565     /***
    566      * creates a new named label
    567      */
    568     public Label createLocalLabel(String name) {
    569         Label l = (Label) currentBlockNamedLabels.get(name);
    570         if (l==null) {
    571             l = new Label();
    572             currentBlockNamedLabels.put(name,l);
    573         }
    574         return l;
    575     }
    576
    577     public int getCurrentClassIndex(){
    578         return currentClassIndex;
    579     }
    580
    581     public void setCurrentClassIndex(int index){
    582         currentClassIndex=index;
    583     }
    584
    585     public int getCurrentMetaClassIndex(){
    586         return currentMetaClassIndex;
    587     }
    588
    589     public void setCurrentMetaClassIndex(int index){
    590         currentMetaClassIndex=index;
    591     }
    592
    593     public void applyFinallyBlocks(Label label, boolean isBreakLabel) {
    594         // first find the state defining the label. That is the state
    595         // directly after the state not knowing this label. If no state
    596         // in the list knows that label, then the defining state is the
    597         // current state.
    598         StateStackElement result = null;
    599         for (ListIterator iter = stateStack.listIterator(stateStack.size()); iter.hasPrevious();) {
    600             StateStackElement element = (StateStackElement) iter.previous();
    601             if (!element._currentBlockNamedLabels.values().contains(label)) {
    602                 if (isBreakLabel && element._breakLabel != label) {
    603                     result = element;
    604                     break;
    605                 }
    606                 if (!isBreakLabel && element._continueLabel != label) {
    607                     result = element;
    608                     break;
    609                 }
    610             }
    611         }
    612
    613         List blocksToRemove;
    614         if (result==null) {
    615             // all Blocks do know the label, so use all finally blocks
    616             blocksToRemove = Collections.EMPTY_LIST;
    617         } else {
    618             blocksToRemove = result._finallyBlocks;
    619         }
    620
    621         ArrayList blocks = new ArrayList(finallyBlocks);
    622         blocks.removeAll(blocksToRemove);
    623         applyFinallyBlocks(blocks);
    624     }
    625
    626     private void applyFinallyBlocks(List blocks) {
    627         for (Iterator iter = blocks.iterator(); iter.hasNext();) {
    628             Runnable block = (Runnable) iter.next();
    629             if (visitedBlocks.contains(block)) continue;
    630             visitedBlocks.add(block);
    631             block.run();
    632         }
    633     }
    634
    635     public void applyFinallyBlocks() {
    636         applyFinallyBlocks(finallyBlocks);
    637     }
    638
    639     public boolean hasFinallyBlocks() {
    640         return !finallyBlocks.isEmpty();
    641     }
    642
    643     public void pushFinallyBlock(Runnable block) {
    644         finallyBlocks.addFirst(block);
    645         pushState();
    646     }
    647
    648     public void popFinallyBlock() {
    649         popState();
    650         finallyBlocks.removeFirst();
    651     }
    652 }