/* * * 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.apache.flex.compiler.internal.as.codegen; import java.util.Collection; import java.util.Collections; import java.util.Vector; import org.apache.flex.abc.instructionlist.InstructionList; import org.apache.flex.abc.semantics.Label; import org.apache.flex.compiler.exceptions.DuplicateLabelException; import org.apache.flex.compiler.exceptions.UnknownControlFlowTargetException; import org.apache.flex.compiler.internal.tree.as.LabeledStatementNode; import org.apache.flex.compiler.internal.tree.as.SwitchNode; import org.apache.flex.compiler.tree.as.IASNode; import org.apache.flex.compiler.tree.as.ICatchNode; import org.apache.flex.compiler.tree.as.ITryNode; import static org.apache.flex.abc.ABCConstants.*; /** * The ControlFlowContextManager is the code generator's * keeper of active control-flow contexts and the associated * (implicit in the configuration of contexts) model of the scope stack. * * Control-flow contexts come in several varieties: * <ul> * <li> Ordinary control-flow contexts, established by a * labeled statement or a statement with break/continue semantics. * <li> Exception-handling contexts, established by try * with a catch or finally. * <li> With statements. * </ul> * * These various types of context have few common elements, aside * from participating in the model of control flow and the scope * stack, so this class manages the active configuration of contexts * and embodies the code generator's control-flow and scope stack model. */ public class ControlFlowContextManager { /** * The LexicalScope that established this * ControlFlowContextManager. * Used to allocate temporary storage, * report problems, etc. */ final LexicalScope currentScope; /** * Base class for all control flow context search criteria * classes. * All subclass are anonymous classes. */ static abstract class ControlFlowContextSearchCriteria { /** * Determines if this search criteria matches the specified * {@link ControlFlowContext}. * * @param c {@link ControlFlowContext} to check. * @return true if this search criteria matches the specified * {@link ControlFlowContext}, false otherwise. */ abstract boolean match(ControlFlowContext c); /** * Gets the {@link Label} in the specified {@link ControlFlowContext} * that should be jumped to when this criteria matches the specified * {@link ControlFlowContext}. * * @param c {@link ControlFlowContext} containing a label that should be * jumped to. * @return The {@link Label} that should be jumped to. */ abstract Label getLabel(ControlFlowContext c); /** * Determines whether the control flow context is search from the inner * most control flow context to the outer most or vice versa, when * searching for a control flow context that matches this search * criteria. * * @return true if the control flow context stack should be search from * inner most to outer most, false if the stack should be search outer * most to inner most. */ abstract boolean innerToOuter(); } /** * Search criterion which finds all active contexts. */ public static final ControlFlowContextSearchCriteria FIND_ALL_CONTEXTS = new ControlFlowContextSearchCriteria() { @Override boolean match(ControlFlowContext c) { return true; } @Override Label getLabel(ControlFlowContext c) { return null; } @Override boolean innerToOuter() { return false; } }; /** * A {@link ControlFlowContextSearchCriteria} which finds the first enclosing * context that can be targetted with a break with no label. */ static ControlFlowContextSearchCriteria breakWithOutLabelCriteria = new ControlFlowContextSearchCriteria() { @Override boolean match(ControlFlowContext c) { return c.hasDefaultBreakLabel(); } @Override Label getLabel(ControlFlowContext c) { assert match(c); return c.getBreakLabel(); } @Override boolean innerToOuter() { return true; } }; /** * A {@link ControlFlowContextSearchCriteria} which finds the first * enclosing context that can be targetted with a continue with no label. */ static ControlFlowContextSearchCriteria continueWithOutLabelCriteria = new ControlFlowContextSearchCriteria() { @Override boolean match(ControlFlowContext c) { return c.hasDefaultContinueLabel(); } @Override Label getLabel(ControlFlowContext c) { assert match(c); return c.getContinueLabel(); } @Override boolean innerToOuter() { return true; } }; /** * Creates a {@link ControlFlowContextSearchCriteria} which finds the first * enclosing context that contains a labeled statement with the specified * label and whose * {@link ControlFlowContextSearchCriteria#getLabel(ControlFlowContext)} * will return the break label for the found context. * * @param label Name of the label to find. * @return A {@link ControlFlowContextSearchCriteria} which finds the first * enclosing context that contains a labeled statement with the specified * label and whose * {@link ControlFlowContextSearchCriteria#getLabel(ControlFlowContext)} * will return the break label for the found context. */ ControlFlowContextSearchCriteria breakWithLabelCriteria(final String label) { return new ControlFlowContextSearchCriteria() { @Override boolean match(ControlFlowContext c) { return c.hasBreakLabel(label); } @Override Label getLabel(ControlFlowContext c) { assert match(c); return c.getBreakLabel(); } @Override boolean innerToOuter() { return false; } }; } /** * Creates a {@link ControlFlowContextSearchCriteria} which finds the first * enclosing context that contains a labeled loop statement with the * specified label and whose * {@link ControlFlowContextSearchCriteria#getLabel(ControlFlowContext)} * will return the continue label for the found context. * * @param label Name of the label to find. * @return A {@link ControlFlowContextSearchCriteria} which finds the first * enclosing context that contains a labeled loop statement with the * specified label and whose * {@link ControlFlowContextSearchCriteria#getLabel(ControlFlowContext)} * will return the continue label for the found context. */ ControlFlowContextSearchCriteria continueWithLabelCriteria(final String label) { return new ControlFlowContextSearchCriteria() { @Override boolean match(ControlFlowContext c) { return c.hasContinueLabel(label); } @Override Label getLabel(ControlFlowContext c) { assert match(c); return c.getContinueLabel(); } @Override boolean innerToOuter() { return false; } }; } /** * Creates a {@link ControlFlowContextSearchCriteria} which finds the first * enclosing context that contains a labeled statement with the specified * label and whose * {@link ControlFlowContextSearchCriteria#getLabel(ControlFlowContext)} * will return the goto label for the found context. * * @param label Name of the label to find. * @return A {@link ControlFlowContextSearchCriteria} which finds the first * enclosing context that contains a labeled statement with the specified * label and whose * {@link ControlFlowContextSearchCriteria#getLabel(ControlFlowContext)} * will return the goto label for the found context. */ ControlFlowContextSearchCriteria gotoLabelCriteria(final String label, final boolean allowDuplicates) { return new ControlFlowContextSearchCriteria() { @Override boolean match(ControlFlowContext c) { return c.hasGotoLabel(label, allowDuplicates); } @Override Label getLabel(ControlFlowContext c) { assert match(c); return c.getGotoLabel(label); } @Override boolean innerToOuter() { return true; } }; } /** * @param current_scope - the active LexicalScope * at the time the ControlFlowContextManager was built. */ ControlFlowContextManager(LexicalScope current_scope) { this.currentScope = current_scope; IASNode initialControlFlowRegionNode = currentScope.getInitialControlFlowRegionNode(); if (initialControlFlowRegionNode != null) { LabelScopeControlFlowContext rootContext = new LabelScopeControlFlowContext(initialControlFlowRegionNode); activeFlowContexts.add(rootContext); } } /** * The stack of currently active control flow contexts. */ Vector<ControlFlowContext> activeFlowContexts = new Vector<ControlFlowContext>(); /** * @return the index of the topmost control-flow context on the stack. */ private int getTopControlFlowContextIndex() { int result = activeFlowContexts.size() - 1; while ( result >= 0 && !(activeFlowContexts.elementAt(result) instanceof LoopControlFlowContext) ) result--; return result; } /** * @return the topmost control-flow context in the stack, which the caller * knows to be a {@link LoopControlFlowContext}. */ private LoopControlFlowContext peekActiveLoopControlFlowContext() { int idx = getTopControlFlowContextIndex(); assert(idx >= 0): "no non-finally flow context"; return (LoopControlFlowContext)activeFlowContexts.elementAt(idx); } /** * Pop the current context off the stack. * @return the popped context. */ private Object popContext() { assert(! activeFlowContexts.isEmpty()): "no active control-flow context"; return activeFlowContexts.remove(activeFlowContexts.size()-1); } /** * Pop the topmost control-flow context off the stack, which the caller * knows to be a {@link SwitchControlFlowContext}. * @return The context popped off the stack. */ private SwitchControlFlowContext popSwitchControlFlowContext() { return (SwitchControlFlowContext)popContext(); } /** * Pop the topmost control-flow context off the stack, which the caller * knows to be a {@link LabeledStatementControlFlowContext}. * @return The context popped off the stack. */ private LabeledStatementControlFlowContext popLabeledStatementControlFlowContext() { return (LabeledStatementControlFlowContext)popContext(); } /** * Pop the topmost control-flow context off the stack, which the caller * knows to be a {@link LoopControlFlowContext}. * @return The context popped off the stack. */ private LoopControlFlowContext popLoopControlFlowContext() { return (LoopControlFlowContext)popContext(); } /** * Pop the current exception handling context off the stack. * @return the popped context. */ private ExceptionHandlingContext popExceptionHandlingContext() { // pops the LabelScopeControlFlowObject that is left on the stack // by the last catch or finally context. popContext(); return (ExceptionHandlingContext)popContext(); } /** * Called by a reduction's Prologue section to establish an active * {@link LoopControlFlowContext}. * * @param loopContents The syntax tree node containing the contents of the * body of the loop. This node is used to establish a new scope for labels * referenced by goto statements. */ public void startLoopControlFlowContext(IASNode loopContents) { activeFlowContexts.add(new LoopControlFlowContext(loopContents)); } /** * Called by a reduction's Prologue section to establish an active * {@code SwitchControlFlowContext}. * * @param node The syntax tree node for the switch statement. */ public void startSwitchContext(SwitchNode node) { activeFlowContexts.add(new SwitchControlFlowContext(node)); } /** * Called by a reduction's Prologue section to establish an active * {@link LabeledStatementControlFlowContext}. * * @param labeledStatement The syntax tree node for the labeled statement. */ public void startLabeledStatementControlFlowContext(LabeledStatementNode labeledStatement) throws DuplicateLabelException { String labelName = labeledStatement.getLabel(); // Scan the active control-flow contexts for a duplicate label. boolean is_duplicate = findControlFlowContextNoError(breakWithLabelCriteria(labelName)) != CONTEXT_NOT_FOUND; activeFlowContexts.add(new LabeledStatementControlFlowContext(labeledStatement, labelName)); // Throw the exception after establishing the (duplicate) context // so that the control-flow context teardown code works for this case. if ( is_duplicate ) { throw new DuplicateLabelException(labelName); } } /** Manifest constant used by control-flow search routines for "not found" */ public static final int CONTEXT_NOT_FOUND = -1; /** * Uses the specified {@link ControlFlowContextSearchCriteria} to find the * index of the first matching control flow context on the control flow * context stack. * * @param criterion * @return the index of the first matching control flow context on the * control flow context stack. */ private final int findControlFlowContextNoError(ControlFlowContextSearchCriteria criterion) { final int nContexts = activeFlowContexts.size(); if (nContexts == 0) return CONTEXT_NOT_FOUND; if (criterion == FIND_ALL_CONTEXTS) return 0; // The criterion object can specify whether or not the // stack of control flow contexts should be searched from // the inner most context to the outer most context or vice // versa. tharwood does not remember why we don't just // always search from inner to outer, so it would be worth // doing an experiment at some point. if (criterion.innerToOuter()) { for ( int i = nContexts - 1; i >= 0; i--) { ControlFlowContext context = activeFlowContexts.elementAt(i); if (criterion.match(context)) return i; } return CONTEXT_NOT_FOUND; } else { for ( int i = 0; i < nContexts; i++) { ControlFlowContext context = activeFlowContexts.elementAt(i); if (criterion.match(context)) return i; } return CONTEXT_NOT_FOUND; } } /** * Uses the specified {@link ControlFlowContextSearchCriteria} to find the * index of the first matching control flow context on the control flow * context stack. * * @param criterion * @return the index of the first matching control flow context on the * control flow context stack. * @throws UnknownControlFlowTargetException When no matching context could * be found. */ private int findControlFlowContext(ControlFlowContextSearchCriteria criterion) throws UnknownControlFlowTargetException { int result = findControlFlowContextNoError(criterion); if ( result == CONTEXT_NOT_FOUND ) { // Callers catch this exception and report // the error in context. throw new UnknownControlFlowTargetException(criterion); } return result; } /** * Called by a reduction's Prologue section to establish * an active exception handling context. */ void startExceptionContext(ITryNode tryNode) { ExceptionHandlingContext finally_context = new ExceptionHandlingContext(this); finally_context.finallyReturns = new Vector<ExceptionHandlingContext.FinallyReturn>(); finally_context.finallyBlock = new Label(); finally_context.finallyDoRethrow = new Label(); finally_context.finallyDoFallthrough = new Label("finallyDoFallthrough"); finally_context.finallyReturnStorage = currentScope.allocateTemp(); activeFlowContexts.add(finally_context); finally_context.startTryControlState(); // Add a new label scope for labels that are referenced by // goto statements in the try block. activeFlowContexts.add(new LabelScopeControlFlowContext(tryNode.getStatementContentsNode())); } /** * @param finallyStatements Sub-tree containing all the statements in the * finally block. This node is used to establish as scope for labels that * can be referenced by goto statements. */ void startFinallyContext(IASNode finallyStatements) { getFinallyContext().startFinallyControlState(); popContext(); // Pop off the LabelScopeControlFlowContext from the try block. activeFlowContexts.add(new LabelScopeControlFlowContext(finallyStatements)); } void endFinallyContext() { getFinallyContext().endFinallyControlState(); } void startCatchContext(ICatchNode catchNode) { getFinallyContext().startCatchControlState(); popContext(); // Pop off the LabelScopeControlFlowContext from the try block or // the finally block. activeFlowContexts.add(new LabelScopeControlFlowContext(catchNode.getStatementContentsNode())); } void endCatchContext() { getFinallyContext().endCatchControlState(); } /** * @return the computed GOTO that implements the "return" from a finally block. */ public InstructionList getFinallySwitch() { InstructionList result = new InstructionList(); ExceptionHandlingContext finally_context = getFinallyContext(); int n_alternatives = getFinallyAlternativesSize(); Label[] finally_labels; if ( 0 == n_alternatives ) { finally_labels = new Label[] { finally_context.finallyDoFallthrough, finally_context.finallyDoRethrow }; } else { finally_labels = new Label[n_alternatives + 2]; finally_labels[0] = finally_context.finallyDoFallthrough; int i = 1; for ( ExceptionHandlingContext.FinallyReturn ret: getFinallyContext().finallyReturns ) finally_labels[i++] = ret.getLabel(); finally_labels[i] = finally_context.finallyDoRethrow; } result.addInstruction(OP_lookupswitch, finally_labels); return result; } /** * Find all active scopes enclosing the currently active scope, * and synthesize an instruction fragment to re-initialize * the scope stack. * @return the instruction fragment that re-initializes the * enclosing scopes on the scope stack. */ public InstructionList getScopeStackReinit() { InstructionList result = new InstructionList(); assert activeFlowContexts.size() >= 2; assert activeFlowContexts.lastElement() instanceof LabelScopeControlFlowContext; for ( int i = 0; i < activeFlowContexts.size() - 2; i++ ) { ControlFlowContext context = activeFlowContexts.elementAt(i); context.addExceptionHandlerEntry(result); } return result; } /** * Find all active exception handling blocks or scopes, * and set up finally return sequences and/or popscopes. */ InstructionList getNonLocalControlFlow(InstructionList original, ControlFlowContextSearchCriteria criterion) throws UnknownControlFlowTargetException { int criterion_index = findControlFlowContext(criterion); // Synthesize an instruction sequence that re-balances the // stack to its condition on entry to this control flow region. InstructionList result = original; for (int i = criterion_index; i < activeFlowContexts.size(); i++ ) { ControlFlowContext context = activeFlowContexts.elementAt(i); result = context.addExitPath(result); } return result; } /** * @return the ExceptionHandlingContext on top of the stack. */ public ExceptionHandlingContext getFinallyContext() { ExceptionHandlingContext current_context; assert activeFlowContexts.size() >= 2; assert activeFlowContexts.lastElement() instanceof LabelScopeControlFlowContext; current_context = (ExceptionHandlingContext)activeFlowContexts.get(activeFlowContexts.size() - 2); return current_context; } /** * @return the number of callers to a finally block. */ public int getFinallyAlternativesSize() { return getFinallyContext().finallyReturns.size(); } /** * @return a code fragment that sets up the "fail" finally return. */ public InstructionList getFinallyFailSignal() { InstructionList result = new InstructionList(); // Allow for the "success" alternative. CmcEmitter.pushNumericConstant(getFinallyAlternativesSize() + 1, result); return result; } /** * Pop the active exception handling context off the stack. */ void finishExceptionContext() { popExceptionHandlingContext(); } /** * Gets the {@link Label} for a labeled statement with the specified name. * This code is used to assign the target of the returned {@link Label} when * reducing the labeled statement node. The label may be created before we * reduce the label statement node if there is a forward reference to the * label. * <p> * This method will return null if a label specified specified name could * not be found or if more than one label with the specified label was * found. * * @param label Name of the label to return. * @return {@link Label} for a labeled statement with the specified name, or * null if no visible label with the specified name could be found. */ Label getGotoLabel(String label) { ControlFlowContextSearchCriteria criterion = gotoLabelCriteria(label, false); int context_idx = findControlFlowContextNoError(criterion); if (context_idx == CONTEXT_NOT_FOUND) return null; ControlFlowContext ctx = activeFlowContexts.elementAt(context_idx); assert ctx.hasGotoLabel(label, false); return ctx.getGotoLabel(label); } /** * Generates a jump instruction to the appropriate label in the context * matched by the specified {@link ControlFlowContextSearchCriteria}. * @param criterion * @return {@link InstructionList} containing a jump. * @throws UnknownControlFlowTargetException */ InstructionList getBranchTarget(ControlFlowContextSearchCriteria criterion) throws UnknownControlFlowTargetException { InstructionList result = new InstructionList(); int context_idx = findControlFlowContext(criterion); ControlFlowContext ctx = activeFlowContexts.elementAt(context_idx); result.addInstruction(OP_jump, criterion.getLabel(ctx)); return result; } /** * Finds all the labeled statements current in scope in the control flow * context stack that a goto with a specified label might refer to. This is * used to generate compiler problems for ambiguous goto statements. * * @param label Name of the labeled statements to return. * @return all the labeled statements current in scope in the control flow * context stack that a goto with a specified label might refer to. */ Collection<LabeledStatementNode> getGotoLabels(String label) { ControlFlowContextSearchCriteria criterion = gotoLabelCriteria(label, true); int context_idx = findControlFlowContextNoError(criterion); if (context_idx == CONTEXT_NOT_FOUND) return Collections.emptyList(); LabelScopeControlFlowContext context = (LabelScopeControlFlowContext)activeFlowContexts.elementAt(context_idx); return context.getLabelNodes(label); } /** * Finish the current context which is known by the caller of this method * to be a labeled statement control flow context. * @param insns The instruction stream of the statement that established * the control flow context. */ void finishLabeledStatementControlFlowContext(InstructionList insns) { LabeledStatementControlFlowContext context = popLabeledStatementControlFlowContext(); if (context.hasActiveBreak()) insns.labelNext(context.getBreakLabel()); } /** * Finish the current context which is known by the caller of this method * to be a labeled statement control flow context. * @param insns The instruction stream of the statement that established * the control flow context. */ void finishSwitchControlFlowContext(InstructionList insns) { SwitchControlFlowContext context = popSwitchControlFlowContext(); if (context.hasActiveBreak()) insns.labelNext(context.getBreakLabel()); } /** * Finish the current control flow context; if a break statement * targeted this context, ensure an appropriate instruction gets * labeled as the break target. * @param insns - the instruction stream of the statement * that established the control flow context */ void finishLoopControlFlowContext(InstructionList insns) { LoopControlFlowContext current_context = popLoopControlFlowContext(); if ( current_context.hasActiveBreak() ) insns.labelNext(current_context.getBreakLabel()); } /** * If a continue statement referenced the active CF context, * attach the continue target label to the target InstructionList. * @param continue_target - the InstructionList to continue to. * @pre The InstructionList must still be valid, i.e., it cannot * have been added to another InstructionList. This forces * resolveContinueLabel() calls further up the reduction logic * than finishControlFlowContext() calls, which are almost invariably * the last operation in a reduction. resolveContinueLabel() * calls are usually among the first operations in the reduction. */ void resolveContinueLabel(InstructionList continue_target) { LoopControlFlowContext context = peekActiveLoopControlFlowContext(); if ( context.hasActiveContinue() ) continue_target.labelFirst(context.getContinueLabel()); } /** * Called by a reduction's Prologue section * to establish an active with scope. */ void startWithContext(IASNode withContents) { WithContext with_context = new WithContext(withContents, this); activeFlowContexts.add(with_context); } /** * Finish a with context; propagate the lifecycle event * to the with context, then pop it off the stack. */ void finishWithContext(InstructionList result) { WithContext with_context = (WithContext) activeFlowContexts.lastElement(); with_context.finish(result); popContext(); } /** * @return the current with scope's temp storage. * @post The with scope will allocate a temp * if one was not previously allocated. * @see #hasWithStorage() */ Binding getWithStorage() { WithContext with_context = (WithContext) activeFlowContexts.lastElement(); return with_context.getWithStorage(); } /** * @return true if the current with scope * has allocated temporary storage for * its with scope. */ boolean hasWithStorage() { WithContext with_context = (WithContext) activeFlowContexts.lastElement(); return with_context.hasWithStorage(); } /** * @return true if the active control-flow contexts * contain any context that requires the caller to * cache a return value (i.e., exception handling * contexts or with statement contexts). */ boolean hasNontrivialFlowCharacteristics() { for ( ControlFlowContext ctx : activeFlowContexts ) if ( ctx instanceof WithContext || ctx instanceof ExceptionHandlingContext ) return true; return false; } }