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 }