1 /*
   2  * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.nashorn.internal.ir;
  27 
  28 import java.util.ArrayList;
  29 import java.util.Collections;
  30 import java.util.HashSet;
  31 import java.util.List;
  32 import java.util.Set;
  33 import jdk.nashorn.internal.ir.annotations.Immutable;
  34 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
  35 
  36 /**
  37  * IR representation of a TRY statement.
  38  */
  39 @Immutable
  40 public final class TryNode extends LexicalContextStatement implements JoinPredecessor {
  41     private static final long serialVersionUID = 1L;
  42 
  43     /** Try statements. */
  44     private final Block body;
  45 
  46     /** List of catch clauses. */
  47     private final List<Block> catchBlocks;
  48 
  49     /** Finally clause. */
  50     private final Block finallyBody;
  51 
  52     /**
  53      * List of inlined finally blocks. The structure of every inlined finally is:
  54      * Block(LabelNode(label, Block(finally-statements, (JumpStatement|ReturnNode)?))).
  55      * That is, the block has a single LabelNode statement with the label and a block containing the
  56      * statements of the inlined finally block with the jump or return statement appended (if the finally
  57      * block was not terminal; the original jump/return is simply ignored if the finally block itself
  58      * terminates). The reason for this somewhat strange arrangement is that we didn't want to create a
  59      * separate class for the (label, BlockStatement pair) but rather reused the already available LabelNode.
  60      * However, if we simply used List<LabelNode> without wrapping the label nodes in an additional Block,
  61      * that would've thrown off visitors relying on BlockLexicalContext -- same reason why we never use
  62      * Statement as the type of bodies of e.g. IfNode, WhileNode etc. but rather blockify them even when they're
  63      * single statements.
  64      */
  65     private final List<Block> inlinedFinallies;
  66 
  67     /** Exception symbol. */
  68     private Symbol exception;
  69 
  70     private final LocalVariableConversion conversion;
  71 
  72     /**
  73      * Constructor
  74      *
  75      * @param lineNumber  lineNumber
  76      * @param token       token
  77      * @param finish      finish
  78      * @param body        try node body
  79      * @param catchBlocks list of catch blocks in order
  80      * @param finallyBody body of finally block or null if none
  81      */
  82     public TryNode(final int lineNumber, final long token, final int finish, final Block body, final List<Block> catchBlocks, final Block finallyBody) {
  83         super(lineNumber, token, finish);
  84         this.body        = body;
  85         this.catchBlocks = catchBlocks;
  86         this.finallyBody = finallyBody;
  87         this.conversion  = null;
  88         this.inlinedFinallies = Collections.emptyList();
  89     }
  90 
  91     private TryNode(final TryNode tryNode, final Block body, final List<Block> catchBlocks, final Block finallyBody, final LocalVariableConversion conversion, final List<Block> inlinedFinallies) {
  92         super(tryNode);
  93         this.body        = body;
  94         this.catchBlocks = catchBlocks;
  95         this.finallyBody = finallyBody;
  96         this.conversion  = conversion;
  97         this.inlinedFinallies = inlinedFinallies;
  98         this.exception = tryNode.exception;
  99     }
 100 
 101     @Override
 102     public Node ensureUniqueLabels(final LexicalContext lc) {
 103         //try nodes are never in lex context
 104         return new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies);
 105     }
 106 
 107     @Override
 108     public boolean isTerminal() {
 109         if (body.isTerminal()) {
 110             for (final Block catchBlock : getCatchBlocks()) {
 111                 if (!catchBlock.isTerminal()) {
 112                     return false;
 113                 }
 114             }
 115             return true;
 116         }
 117         return false;
 118     }
 119 
 120     /**
 121      * Assist in IR navigation.
 122      * @param visitor IR navigating visitor.
 123      */
 124     @Override
 125     public Node accept(final LexicalContext lc, NodeVisitor<? extends LexicalContext> visitor) {
 126         if (visitor.enterTryNode(this)) {
 127             // Need to do finallybody first for termination analysis. TODO still necessary?
 128             final Block newFinallyBody = finallyBody == null ? null : (Block)finallyBody.accept(visitor);
 129             final Block newBody        = (Block)body.accept(visitor);
 130             return visitor.leaveTryNode(
 131                 setBody(lc, newBody).
 132                 setFinallyBody(lc, newFinallyBody).
 133                 setCatchBlocks(lc, Node.accept(visitor, catchBlocks)).
 134                 setInlinedFinallies(lc, Node.accept(visitor, inlinedFinallies)));
 135         }
 136 
 137         return this;
 138     }
 139 
 140     @Override
 141     public void toString(final StringBuilder sb, final boolean printType) {
 142         sb.append("try ");
 143     }
 144 
 145     /**
 146      * Get the body for this try block
 147      * @return body
 148      */
 149     public Block getBody() {
 150         return body;
 151     }
 152 
 153     /**
 154      * Reset the body of this try block
 155      * @param lc current lexical context
 156      * @param body new body
 157      * @return new TryNode or same if unchanged
 158      */
 159     public TryNode setBody(final LexicalContext lc, final Block body) {
 160         if (this.body == body) {
 161             return this;
 162         }
 163         return Node.replaceInLexicalContext(lc, this, new TryNode(this,  body, catchBlocks, finallyBody, conversion, inlinedFinallies));
 164     }
 165 
 166     /**
 167      * Get the catches for this try block
 168      * @return a list of catch nodes
 169      */
 170     public List<CatchNode> getCatches() {
 171         final List<CatchNode> catches = new ArrayList<>(catchBlocks.size());
 172         for (final Block catchBlock : catchBlocks) {
 173             catches.add(getCatchNodeFromBlock(catchBlock));
 174         }
 175         return Collections.unmodifiableList(catches);
 176     }
 177 
 178     private static CatchNode getCatchNodeFromBlock(final Block catchBlock) {
 179         return (CatchNode)catchBlock.getStatements().get(0);
 180     }
 181 
 182     /**
 183      * Get the catch blocks for this try block
 184      * @return a list of blocks
 185      */
 186     public List<Block> getCatchBlocks() {
 187         return Collections.unmodifiableList(catchBlocks);
 188     }
 189 
 190     /**
 191      * Set the catch blocks of this try
 192      * @param lc current lexical context
 193      * @param catchBlocks list of catch blocks
 194      * @return new TryNode or same if unchanged
 195      */
 196     public TryNode setCatchBlocks(final LexicalContext lc, final List<Block> catchBlocks) {
 197         if (this.catchBlocks == catchBlocks) {
 198             return this;
 199         }
 200         return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies));
 201     }
 202 
 203     /**
 204      * Get the exception symbol for this try block
 205      * @return a symbol for the compiler to store the exception in
 206      */
 207     public Symbol getException() {
 208         return exception;
 209     }
 210     /**
 211      * Set the exception symbol for this try block
 212      * @param exception a symbol for the compiler to store the exception in
 213      * @return new TryNode or same if unchanged
 214      */
 215     public TryNode setException(final Symbol exception) {
 216         this.exception = exception;
 217         return this;
 218     }
 219 
 220     /**
 221      * Get the body of the finally clause for this try
 222      * @return finally body, or null if no finally
 223      */
 224     public Block getFinallyBody() {
 225         return finallyBody;
 226     }
 227 
 228     /**
 229      * Get the inlined finally block with the given label name. This returns the actual finally block in the
 230      * {@link LabelNode}, not the outer wrapper block for the {@link LabelNode}.
 231      * @param labelName the name of the inlined finally's label
 232      * @return the requested finally block, or null if no finally block's label matches the name.
 233      */
 234     public Block getInlinedFinally(final String labelName) {
 235         for(final Block inlinedFinally: inlinedFinallies) {
 236             final LabelNode labelNode = getInlinedFinallyLabelNode(inlinedFinally);
 237             if (labelNode.getLabelName().equals(labelName)) {
 238                 return labelNode.getBody();
 239             }
 240         }
 241         return null;
 242     }
 243 
 244     private static LabelNode getInlinedFinallyLabelNode(final Block inlinedFinally) {
 245         return (LabelNode)inlinedFinally.getStatements().get(0);
 246     }
 247 
 248     /**
 249      * Given an outer wrapper block for the {@link LabelNode} as returned by {@link #getInlinedFinallies()},
 250      * returns its actual inlined finally block.
 251      * @param inlinedFinally the outer block for inlined finally, as returned as an element of
 252      * {@link #getInlinedFinallies()}.
 253      * @return the block contained in the {@link LabelNode} contained in the passed block.
 254      */
 255     public static Block getLabelledInlinedFinallyBlock(final Block inlinedFinally) {
 256         return getInlinedFinallyLabelNode(inlinedFinally).getBody();
 257     }
 258 
 259     /**
 260      * Returns a list of inlined finally blocks. Note that this returns a list of {@link Block}s such that each one of
 261      * them has a single {@link LabelNode}, which in turn contains the label name for the finally block and the
 262      * actual finally block. To safely extract the actual finally block, use
 263      * {@link #getLabelledInlinedFinallyBlock(Block)}.
 264      * @return a list of inlined finally blocks.
 265      */
 266     public List<Block> getInlinedFinallies() {
 267         return Collections.unmodifiableList(inlinedFinallies);
 268     }
 269 
 270     /**
 271      * Set the finally body of this try
 272      * @param lc current lexical context
 273      * @param finallyBody new finally body
 274      * @return new TryNode or same if unchanged
 275      */
 276     public TryNode setFinallyBody(final LexicalContext lc, final Block finallyBody) {
 277         if (this.finallyBody == finallyBody) {
 278             return this;
 279         }
 280         return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies));
 281     }
 282 
 283     /**
 284      * Set the inlined finally blocks of this try. Each element should be a block with a single statement that is a
 285      * {@link LabelNode} with a unique label, and the block within the label node should contain the actual inlined
 286      * finally block.
 287      * @param lc current lexical context
 288      * @param inlinedFinallies list of inlined finally blocks
 289      * @return new TryNode or same if unchanged
 290      */
 291     public TryNode setInlinedFinallies(final LexicalContext lc, final List<Block> inlinedFinallies) {
 292         if (this.inlinedFinallies == inlinedFinallies) {
 293             return this;
 294         }
 295         assert checkInlinedFinallies(inlinedFinallies);
 296         return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies));
 297     }
 298 
 299     private static boolean checkInlinedFinallies(final List<Block> inlinedFinallies) {
 300         if (!inlinedFinallies.isEmpty()) {
 301             final Set<String> labels = new HashSet<>();
 302             for (final Block inlinedFinally : inlinedFinallies) {
 303                 final List<Statement> stmts = inlinedFinally.getStatements();
 304                 assert stmts.size() == 1;
 305                 final LabelNode ln = getInlinedFinallyLabelNode(inlinedFinally);
 306                 assert labels.add(ln.getLabelName()); // unique label
 307             }
 308         }
 309         return true;
 310     }
 311 
 312     @Override
 313     public JoinPredecessor setLocalVariableConversion(final LexicalContext lc, final LocalVariableConversion conversion) {
 314         if(this.conversion == conversion) {
 315             return this;
 316         }
 317         return new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies);
 318     }
 319 
 320     @Override
 321     public LocalVariableConversion getLocalVariableConversion() {
 322         return conversion;
 323     }
 324 }