All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.groovy.classgen.asm.CompileStack Maven / Gradle / Ivy

There is a newer version: 3.0.8-01
Show newest version
/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
package org.codehaus.groovy.classgen.asm;

import org.codehaus.groovy.GroovyBugError;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.Variable;
import org.codehaus.groovy.ast.VariableScope;
import org.codehaus.groovy.classgen.AsmClassGenerator;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * Manages different aspects of the code of a code block like handling labels,
 * defining variables, and scopes. After a MethodNode is visited clear should be
 * called, for initialization the method init should be used.
 * 

* Some Notes: *

    *
  • every push method will require a later pop call *
  • method parameters may define a category 2 variable, so * don't ignore the type stored in the variable object *
  • the index of the variable may not be as assumed when * the variable is a parameter of a method because the * parameter may be used in a closure, so don't ignore * the stored variable index *
  • the names of temporary variables can be ignored. The names * are only used for debugging and do not conflict with each * other or normal variables. For accessing, the index of the * variable must be used. *
  • never mix temporary and normal variables by changes to this class. * While the name is very important for a normal variable, it is only a * helper construct for temporary variables. That means for example a * name for a temporary variable can be used multiple times without * conflict. So mixing them both may lead to the problem that a normal * or temporary variable is hidden or even removed. That must not happen! *
* * @see org.codehaus.groovy.classgen.AsmClassGenerator */ public class CompileStack implements Opcodes { // TODO: remove optimization of this.foo -> this.@foo /** state flag */ private boolean clear = true; /** current scope */ private VariableScope scope; /** current label for continue */ private Label continueLabel; /** current label for break */ private Label breakLabel; /** available variables on stack */ private Map stackVariables = new HashMap<>(); /** index of the last variable on stack */ private int currentVariableIndex = 1; /** index for the next variable on stack */ private int nextVariableIndex = 1; /** currently temporary variables in use */ private final Deque temporaryVariables = new LinkedList<>(); /** overall used variables for a method/constructor */ private final Deque usedVariables = new LinkedList<>(); /** map containing named labels of parenting blocks */ private Map superBlockNamedLabels = new HashMap<>(); /** map containing named labels of current block */ private Map currentBlockNamedLabels = new HashMap<>(); /** * list containing finally blocks *

* such a block is created by synchronized or finally and * must be called for break/continue/return */ private LinkedList finallyBlocks = new LinkedList<>(); private final List visitedBlocks = new LinkedList<>(); /** helper to handle different stack based variables */ private final Deque stateStack = new LinkedList<>(); /** handle different states for the implicit "this" */ private final Deque implicitThisStack = new LinkedList<>(); /** handle different states for being on the left hand side */ private final Deque lhsStack = new LinkedList<>(); { implicitThisStack.add(Boolean.FALSE); lhsStack.add(Boolean.FALSE); } private String className; /** first variable index usable after all parameters of a method */ private int localVariableOffset; private Label thisStartLabel, thisEndLabel; /** goals for a "break foo" call in a loop where foo is a label. */ private final Map namedLoopBreakLabel = new HashMap<>(); /** goals for a "continue foo" call in a loop where foo is a label. */ private final Map namedLoopContinueLabel = new HashMap<>(); private final Deque typedExceptions = new LinkedList<>(); private final Deque untypedExceptions = new LinkedList<>(); /** stores if on left-hand-side during compilation */ private boolean lhs; /** stores if implicit or explicit this is used. */ private boolean implicitThis; private boolean inSpecialConstructorCall; private final WriterController controller; protected static class LabelRange { public Label start; public Label end; } public static class BlockRecorder { private boolean isEmpty = true; public Runnable excludedStatement; public final LinkedList ranges = new LinkedList<>(); public BlockRecorder() { } public BlockRecorder(final Runnable excludedStatement) { this.excludedStatement = excludedStatement; } public void startRange(final Label start) { LabelRange range = new LabelRange(); range.start = start; ranges.add(range); isEmpty = false; } public void closeRange(final Label end) { ranges.getLast().end = end; } } private static class ExceptionTableEntry { Label start, end, goal; String sig; } private class StateStackElement { final VariableScope scope; final Label continueLabel; final Label breakLabel; final Map stackVariables; final Map currentBlockNamedLabels; final LinkedList finallyBlocks; final boolean inSpecialConstructorCall; StateStackElement() { scope = CompileStack.this.scope; continueLabel = CompileStack.this.continueLabel; breakLabel = CompileStack.this.breakLabel; stackVariables = CompileStack.this.stackVariables; currentBlockNamedLabels = CompileStack.this.currentBlockNamedLabels; finallyBlocks = CompileStack.this.finallyBlocks; inSpecialConstructorCall = CompileStack.this.inSpecialConstructorCall; } } //-------------------------------------------------------------------------- public CompileStack(final WriterController controller) { this.controller = controller; } public Label getBreakLabel() { return breakLabel; } public Label getContinueLabel() { return continueLabel; } public VariableScope getScope() { return scope; } public void pushState() { stateStack.add(new StateStackElement()); stackVariables = new HashMap<>(stackVariables); finallyBlocks = new LinkedList<>(finallyBlocks); } private void popState() { if (stateStack.isEmpty()) { throw new GroovyBugError("Tried to do a pop on the compile stack without push."); } StateStackElement element = stateStack.removeLast(); scope = element.scope; breakLabel = element.breakLabel; continueLabel = element.continueLabel; stackVariables = element.stackVariables; finallyBlocks = element.finallyBlocks; inSpecialConstructorCall = element.inSpecialConstructorCall; } public void removeVar(final int tempIndex) { BytecodeVariable head = temporaryVariables.removeFirst(); if (head.getIndex() != tempIndex) { temporaryVariables.addFirst(head); MethodNode methodNode = controller.getMethodNode(); if (methodNode == null) { methodNode = controller.getConstructorNode(); } throw new GroovyBugError( "In method "+ (methodNode!=null?methodNode.getText():"") + ", " + "CompileStack#removeVar: tried to remove a temporary " + "variable with index "+ tempIndex + " in wrong order. " + "Current temporary variables=" + temporaryVariables); } } private void setEndLabels() { Label endLabel = new Label(); controller.getMethodVisitor().visitLabel(endLabel); for (BytecodeVariable var : stackVariables.values()) { var.setEndLabel(endLabel); } thisEndLabel = endLabel; } public void pop() { setEndLabels(); popState(); } /** * creates a temporary variable. * * @param var defines type and name * @param store defines if the toplevel argument of the stack should be stored * @return the index used for this temporary variable */ public int defineTemporaryVariable(final Variable var, final boolean store) { return defineTemporaryVariable(var.getName(), var.getType(),store); } public BytecodeVariable getVariable(final String variableName ) { return getVariable(variableName, true); } /** * Returns a normal variable. *

* If mustExist is true and the normal variable doesn't exist, * then this method will throw a GroovyBugError. It is not the intention of * this method to let this happen! And the exception should not be used for * flow control - it is just acting as an assertion. If the exception is thrown * then it indicates a bug in the class using CompileStack. * This method can also not be used to return a temporary variable. * Temporary variables are not normal variables. * * @param variableName name of the variable * @param mustExist throw exception if variable does not exist * @return the normal variable or null if not found (and mustExist not true) */ public BytecodeVariable getVariable(final String variableName, final boolean mustExist) { if (variableName.equals("this")) return BytecodeVariable.THIS_VARIABLE; if (variableName.equals("super")) return BytecodeVariable.SUPER_VARIABLE; BytecodeVariable v = stackVariables.get(variableName); 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"); return v; } /** * creates a temporary variable. * * @param name defines type and name * @param store defines if the top-level argument of the stack should be stored * @return the index used for this temporary variable */ public int defineTemporaryVariable(final String name, final boolean store) { return defineTemporaryVariable(name, ClassHelper.DYNAMIC_TYPE,store); } /** * creates a temporary variable. * * @param name defines the name * @param node defines the node * @param store defines if the top-level argument of the stack should be stored * @return the index used for this temporary variable */ public int defineTemporaryVariable(final String name, final ClassNode node, final boolean store) { BytecodeVariable answer = defineVar(name, node, false, false); temporaryVariables.addFirst(answer); // TRICK: we add at the beginning so when we find for remove or get we always have the last one usedVariables.removeLast(); if (store) controller.getOperandStack().storeVar(answer); return answer.getIndex(); } private void resetVariableIndex(final boolean isStatic) { temporaryVariables.clear(); if (!isStatic) { currentVariableIndex = 1; nextVariableIndex = 1; } else { currentVariableIndex = 0; nextVariableIndex = 0; } } /** * Clears the state of the class. This method should be called * after a MethodNode is visited. Note that a call to init will * fail if clear is not called before */ public void clear() { if (stateStack.size() > 1) { int size = stateStack.size() - 1; throw new GroovyBugError("state stack contains " + size + " more push instruction" + (size == 1 ? "" : "s") + " than pops."); } if (lhsStack.size() > 1) { int size = lhsStack.size() - 1; throw new GroovyBugError("lhs stack is supposed to be empty, but has " + size + " element" + (size == 1 ? "" : "s") + " left."); } if (implicitThisStack.size() > 1) { int size = implicitThisStack.size() - 1; throw new GroovyBugError("implicit 'this' stack is supposed to be empty, but has " + size + " element" + (size == 1 ? "" : "s") + " left."); } clear = true; MethodVisitor mv = controller.getMethodVisitor(); if (AsmClassGenerator.CREATE_DEBUG_INFO) { if (thisEndLabel == null) setEndLabels(); if (!scope.isInStaticContext()) { // write "this" mv.visitLocalVariable("this", className, null, thisStartLabel, thisEndLabel, 0); } for (BytecodeVariable v : usedVariables) { String type = BytecodeHelper.getTypeDescription(v.isHolder() ? ClassHelper.REFERENCE_TYPE : v.getType()); Label start = v.getStartLabel(); Label end = v.getEndLabel(); mv.visitLocalVariable(v.getName(), type, null, start, end, v.getIndex()); } } // exception table writing for (ExceptionTableEntry ep : typedExceptions) { mv.visitTryCatchBlock(ep.start, ep.end, ep.goal, ep.sig); } for (ExceptionTableEntry ep : untypedExceptions) { mv.visitTryCatchBlock(ep.start, ep.end, ep.goal, ep.sig); } popState(); typedExceptions.clear(); untypedExceptions.clear(); stackVariables.clear(); usedVariables.clear(); finallyBlocks.clear(); resetVariableIndex(false); superBlockNamedLabels.clear(); currentBlockNamedLabels.clear(); namedLoopBreakLabel.clear(); namedLoopContinueLabel.clear(); breakLabel = null; continueLabel = null; thisStartLabel = null; thisEndLabel = null; className = null; scope = null; } public void addExceptionBlock(final Label start, final Label end, final Label goal, final String sig) { // this code is in an extra method to avoid // lazy initialization issues ExceptionTableEntry ep = new ExceptionTableEntry(); ep.start = start; ep.end = end; ep.sig = sig; ep.goal = goal; if (sig == null) { untypedExceptions.add(ep); } else { typedExceptions.add(ep); } } /** * initializes this class for a MethodNode. This method will * automatically define variables for the method parameters * and will create references if needed. The created variables * can be accessed by calling getVariable(). * */ public void init(final VariableScope scope, final Parameter[] parameters) { if (!clear) throw new GroovyBugError("CompileStack#init called without calling clear before"); clear = false; pushVariableScope(scope); defineMethodVariables(parameters, scope.isInStaticContext()); this.className = BytecodeHelper.getTypeDescription(controller.getClassNode()); } /** * Causes the state-stack to add an element and sets * the given scope as new current variable scope. Creates * a element for the state stack so pop has to be called later */ public void pushVariableScope(final VariableScope scope) { pushState(); this.scope = scope; superBlockNamedLabels = new HashMap<>(superBlockNamedLabels); superBlockNamedLabels.putAll(currentBlockNamedLabels); currentBlockNamedLabels = new HashMap<>(); } /** * Should be called when descending into a loop that defines * also a scope. Calls pushVariableScope and prepares labels * for a loop structure. Creates a element for the state stack * so pop has to be called later, TODO: @Deprecate */ public void pushLoop(final VariableScope scope, final String labelName) { pushVariableScope(scope); continueLabel = new Label(); breakLabel = new Label(); if (labelName != null) { initLoopLabels(labelName); } } /** * Should be called when descending into a loop that defines * also a scope. Calls pushVariableScope and prepares labels * for a loop structure. Creates a element for the state stack * so pop has to be called later */ public void pushLoop(final VariableScope el, final List labelNames) { pushVariableScope(el); continueLabel = new Label(); breakLabel = new Label(); if (labelNames != null) { for (String labelName : labelNames) { initLoopLabels(labelName); } } } private void initLoopLabels(final String labelName) { namedLoopBreakLabel.put(labelName, breakLabel); namedLoopContinueLabel.put(labelName, continueLabel); } /** * Should be called when descending into a loop that does * not define a scope. Creates a element for the state stack * so pop has to be called later, TODO: @Deprecate */ public void pushLoop(final String labelName) { pushState(); continueLabel = new Label(); breakLabel = new Label(); initLoopLabels(labelName); } /** * Should be called when descending into a loop that does * not define a scope. Creates a element for the state stack * so pop has to be called later */ public void pushLoop(final List labelNames) { pushState(); continueLabel = new Label(); breakLabel = new Label(); if (labelNames != null) { for (String labelName : labelNames) { initLoopLabels(labelName); } } } /** * Used for break foo inside a loop to end the * execution of the marked loop. This method will return the * break label of the loop if there is one found for the name. * If not, the current break label is returned. */ public Label getNamedBreakLabel(final String name) { Label label = getBreakLabel(); Label endLabel = null; if (name != null) endLabel = namedLoopBreakLabel.get(name); if (endLabel != null) label = endLabel; return label; } /** * Used for continue foo inside a loop to continue * the execution of the marked loop. This method will return * the break label of the loop if there is one found for the * name. If not, getLabel is used. */ public Label getNamedContinueLabel(final String name) { Label label = getLabel(name); Label endLabel = null; if (name != null) endLabel = namedLoopContinueLabel.get(name); if (endLabel != null) label = endLabel; return label; } /** * Creates a new break label and a element for the state stack * so pop has to be called later */ public Label pushSwitch() { pushState(); breakLabel = new Label(); return breakLabel; } /** * because a boolean Expression may not be evaluated completely * it is important to keep the registers clean */ public void pushBooleanExpression() { pushState(); } private BytecodeVariable defineVar(final String name, final ClassNode type, final boolean holder, final boolean useReferenceDirectly) { int prevCurrent = currentVariableIndex; makeNextVariableID(type, useReferenceDirectly); int index = currentVariableIndex; if (holder && !useReferenceDirectly) index = localVariableOffset++; BytecodeVariable answer = new BytecodeVariable(index, type, name, prevCurrent); usedVariables.add(answer); answer.setHolder(holder); return answer; } private void makeLocalVariablesOffset(final Parameter[] paras, final boolean isInStaticContext) { resetVariableIndex(isInStaticContext); for (Parameter para : paras) { makeNextVariableID(para.getType(), false); } localVariableOffset = nextVariableIndex; resetVariableIndex(isInStaticContext); } private void defineMethodVariables(final Parameter[] params, final boolean isInStaticContext) { Label startLabel = new Label(); thisStartLabel = startLabel; controller.getMethodVisitor().visitLabel(startLabel); makeLocalVariablesOffset(params,isInStaticContext); for (Parameter param : params) { String name = param.getName(); BytecodeVariable answer; ClassNode type = param.getType(); if (param.isClosureSharedVariable()) { boolean useExistingReference = param.getNodeMetaData(ClosureWriter.UseExistingReference.class) != null; answer = defineVar(name, param.getOriginType(), true, useExistingReference); answer.setStartLabel(startLabel); if (!useExistingReference) { controller.getOperandStack().load(type, currentVariableIndex); controller.getOperandStack().box(); // GROOVY-4237, the original variable should always appear // in the variable index, otherwise some programs get into // trouble. So we define a dummy variable for the packaging // phase and let it end right away before the normal // reference will be used Label newStart = new Label(); controller.getMethodVisitor().visitLabel(newStart); BytecodeVariable var = new BytecodeVariable(currentVariableIndex, param.getOriginType(), name, currentVariableIndex); var.setStartLabel(startLabel); var.setEndLabel(newStart); usedVariables.add(var); answer.setStartLabel(newStart); createReference(answer); } } else { answer = defineVar(name, type, false, false); answer.setStartLabel(startLabel); } stackVariables.put(name, answer); } nextVariableIndex = localVariableOffset; } private void createReference(final BytecodeVariable reference) { MethodVisitor mv = controller.getMethodVisitor(); mv.visitTypeInsn(NEW, "groovy/lang/Reference"); mv.visitInsn(DUP_X1); mv.visitInsn(SWAP); mv.visitMethodInsn(INVOKESPECIAL, "groovy/lang/Reference", "", "(Ljava/lang/Object;)V", false); mv.visitVarInsn(ASTORE, reference.getIndex()); } private static void pushInitValue(final ClassNode type, final MethodVisitor mv) { if (ClassHelper.isPrimitiveType(type)) { if (type == ClassHelper.long_TYPE) { mv.visitInsn(LCONST_0); } else if (type == ClassHelper.double_TYPE) { mv.visitInsn(DCONST_0); } else if (type == ClassHelper.float_TYPE) { mv.visitInsn(FCONST_0); } else { mv.visitLdcInsn(0); } } else { mv.visitInsn(ACONST_NULL); } } /** * Defines a new Variable using an AST variable. * @param initFromStack if true the last element of the * stack will be used to initialize * the new variable. If false null * will be used. */ public BytecodeVariable defineVariable(final Variable v, final boolean initFromStack) { return defineVariable(v, v.getOriginType(), initFromStack); } public BytecodeVariable defineVariable(final Variable v, final ClassNode variableType, final boolean initFromStack) { String name = v.getName(); BytecodeVariable answer = defineVar(name, variableType, v.isClosureSharedVariable(), v.isClosureSharedVariable()); stackVariables.put(name, answer); MethodVisitor mv = controller.getMethodVisitor(); Label startLabel = new Label(); answer.setStartLabel(startLabel); ClassNode type = answer.getType().redirect(); OperandStack operandStack = controller.getOperandStack(); if (!initFromStack) { if (ClassHelper.isPrimitiveType(v.getOriginType()) && ClassHelper.getWrapper(v.getOriginType()) == variableType) { pushInitValue(v.getOriginType(), mv); operandStack.push(v.getOriginType()); operandStack.box(); operandStack.remove(1); } else { pushInitValue(type, mv); } } operandStack.push(answer.getType()); if (answer.isHolder()) { operandStack.box(); operandStack.remove(1); createReference(answer); } else { operandStack.storeVar(answer); } mv.visitLabel(startLabel); return answer; } /** * @param name the name of the variable of interest * @return true if a variable is already defined */ public boolean containsVariable(final String name) { return stackVariables.containsKey(name); } /** * Calculates the index of the next free register stores it * and sets the current variable index to the old value */ private void makeNextVariableID(final ClassNode type, final boolean useReferenceDirectly) { currentVariableIndex = nextVariableIndex; if ((type == ClassHelper.long_TYPE || type == ClassHelper.double_TYPE) && !useReferenceDirectly) { nextVariableIndex += 1; } nextVariableIndex += 1; } /** * Returns the label for the given name */ public Label getLabel(final String name) { if (name == null) return null; Label l = superBlockNamedLabels.get(name); if (l == null) { l = createLocalLabel(name); } return l; } /** * creates a new named label */ public Label createLocalLabel(final String name) { Label l = currentBlockNamedLabels.get(name); if (l == null) { l = new Label(); currentBlockNamedLabels.put(name, l); } return l; } public void applyFinallyBlocks(final Label label, final boolean isBreakLabel) { // first find the state defining the label. That is the state // directly after the state not knowing this label. If no state // in the list knows that label, then the defining state is the // current state. StateStackElement result = null; for (Iterator iter = stateStack.descendingIterator(); iter.hasNext(); ) { StateStackElement element = iter.next(); if (!element.currentBlockNamedLabels.containsValue(label)) { if (isBreakLabel && element.breakLabel != label) { result = element; break; } if (!isBreakLabel && element.continueLabel != label) { result = element; break; } } } Collection blockRecorders = new LinkedList<>(finallyBlocks); if (result != null) { blockRecorders.removeAll(result.finallyBlocks); } applyBlockRecorder(blockRecorders); } private void applyBlockRecorder(final Collection blockRecorders) { if (blockRecorders.isEmpty() || blockRecorders.size() == visitedBlocks.size()) return; MethodVisitor mv = controller.getMethodVisitor(); Label start = new Label(); for (BlockRecorder recorder : blockRecorders) { if (visitedBlocks.contains(recorder)) continue; Label end = new Label(); mv.visitInsn(NOP); mv.visitLabel(end); recorder.closeRange(end); // we exclude the finally block from the exception table // here to avoid double visiting of finally statements recorder.excludedStatement.run(); recorder.startRange(start); } mv.visitInsn(NOP); mv.visitLabel(start); } public void applyBlockRecorder() { applyBlockRecorder(finallyBlocks); } public boolean hasBlockRecorder() { return !finallyBlocks.isEmpty(); } public void pushBlockRecorder(final BlockRecorder recorder) { pushState(); finallyBlocks.addFirst(recorder); } public void pushBlockRecorderVisit(final BlockRecorder finallyBlock) { visitedBlocks.add(finallyBlock); } public void popBlockRecorderVisit(final BlockRecorder finallyBlock) { visitedBlocks.remove(finallyBlock); } public void writeExceptionTable(final BlockRecorder block, final Label goal, final String sig) { if (block.isEmpty) return; MethodVisitor mv = controller.getMethodVisitor(); for (LabelRange range : block.ranges) { mv.visitTryCatchBlock(range.start, range.end, goal, sig); } } public boolean isLHS() { return lhs; } public void pushLHS(final boolean lhs) { lhsStack.add(lhs); this.lhs = lhs; } public void popLHS() { lhsStack.removeLast(); lhs = lhsStack.getLast(); } public boolean isImplicitThis() { return implicitThis; } public void pushImplicitThis(final boolean implicitThis) { implicitThisStack.add(implicitThis); this.implicitThis = implicitThis; } public void popImplicitThis() { implicitThisStack.removeLast(); implicitThis = implicitThisStack.getLast(); } public boolean isInSpecialConstructorCall() { return inSpecialConstructorCall; } public void pushInSpecialConstructorCall() { pushState(); inSpecialConstructorCall = true; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy