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&lt;LabelNode&gt; 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 final 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         this.exception = null;
  90     }
  91 
  92     private TryNode(final TryNode tryNode, final Block body, final List<Block> catchBlocks, final Block finallyBody, final LocalVariableConversion conversion, final List<Block> inlinedFinallies, final Symbol exception) {
  93         super(tryNode);
  94         this.body        = body;
  95         this.catchBlocks = catchBlocks;
  96         this.finallyBody = finallyBody;
  97         this.conversion  = conversion;
  98         this.inlinedFinallies = inlinedFinallies;
  99         this.exception = exception;
 100     }
 101 
 102     @Override
 103     public Node ensureUniqueLabels(final LexicalContext lc) {
 104         //try nodes are never in lex context
 105         return new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies, exception);
 106     }
 107 
 108     @Override
 109     public boolean isTerminal() {
 110         if (body.isTerminal()) {
 111             for (final Block catchBlock : getCatchBlocks()) {
 112                 if (!catchBlock.isTerminal()) {
 113                     return false;
 114                 }
 115             }
 116             return true;
 117         }
 118         return false;
 119     }
 120 
 121     /**
 122      * Assist in IR navigation.
 123      * @param visitor IR navigating visitor.
 124      */
 125     @Override
 126     public Node accept(final LexicalContext lc, final NodeVisitor<? extends LexicalContext> visitor) {
 127         if (visitor.enterTryNode(this)) {
 128             // Need to do finallybody first for termination analysis. TODO still necessary?
 129             final Block newFinallyBody = finallyBody == null ? null : (Block)finallyBody.accept(visitor);
 130             final Block newBody        = (Block)body.accept(visitor);
 131             return visitor.leaveTryNode(
 132                 setBody(lc, newBody).
 133                 setFinallyBody(lc, newFinallyBody).
 134                 setCatchBlocks(lc, Node.accept(visitor, catchBlocks)).
 135                 setInlinedFinallies(lc, Node.accept(visitor, inlinedFinallies)));
 136         }
 137 
 138         return this;
 139     }
 140 
 141     @Override
 142     public void toString(final StringBuilder sb, final boolean printType) {
 143         sb.append("try ");
 144     }
 145 
 146     /**
 147      * Get the body for this try block
 148      * @return body
 149      */
 150     public Block getBody() {
 151         return body;
 152     }
 153 
 154     /**
 155      * Reset the body of this try block
 156      * @param lc current lexical context
 157      * @param body new body
 158      * @return new TryNode or same if unchanged
 159      */
 160     public TryNode setBody(final LexicalContext lc, final Block body) {
 161         if (this.body == body) {
 162             return this;
 163         }
 164         return Node.replaceInLexicalContext(lc, this, new TryNode(this,  body, catchBlocks, finallyBody, conversion, inlinedFinallies, exception));
 165     }
 166 
 167     /**
 168      * Get the catches for this try block
 169      * @return a list of catch nodes
 170      */
 171     public List<CatchNode> getCatches() {
 172         final List<CatchNode> catches = new ArrayList<>(catchBlocks.size());
 173         for (final Block catchBlock : catchBlocks) {
 174             catches.add(getCatchNodeFromBlock(catchBlock));
 175         }
 176         return Collections.unmodifiableList(catches);
 177     }
 178 
 179     private static CatchNode getCatchNodeFromBlock(final Block catchBlock) {
 180         return (CatchNode)catchBlock.getStatements().get(0);
 181     }
 182 
 183     /**
 184      * Get the catch blocks for this try block
 185      * @return a list of blocks
 186      */
 187     public List<Block> getCatchBlocks() {
 188         return Collections.unmodifiableList(catchBlocks);
 189     }
 190 
 191     /**
 192      * Set the catch blocks of this try
 193      * @param lc current lexical context
 194      * @param catchBlocks list of catch blocks
 195      * @return new TryNode or same if unchanged
 196      */
 197     public TryNode setCatchBlocks(final LexicalContext lc, final List<Block> catchBlocks) {
 198         if (this.catchBlocks == catchBlocks) {
 199             return this;
 200         }
 201         return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies, exception));
 202     }
 203 
 204     /**
 205      * Get the exception symbol for this try block
 206      * @return a symbol for the compiler to store the exception in
 207      */
 208     public Symbol getException() {
 209         return exception;
 210     }
 211     /**
 212      * Set the exception symbol for this try block
 213      * @param lc lexical context
 214      * @param exception a symbol for the compiler to store the exception in
 215      * @return new TryNode or same if unchanged
 216      */
 217     public TryNode setException(final LexicalContext lc, final Symbol exception) {
 218         if (this.exception == exception) {
 219             return this;
 220         }
 221         return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies, exception));
 222     }
 223 
 224     /**
 225      * Get the body of the finally clause for this try
 226      * @return finally body, or null if no finally
 227      */
 228     public Block getFinallyBody() {
 229         return finallyBody;
 230     }
 231 
 232     /**
 233      * Get the inlined finally block with the given label name. This returns the actual finally block in the
 234      * {@link LabelNode}, not the outer wrapper block for the {@link LabelNode}.
 235      * @param labelName the name of the inlined finally's label
 236      * @return the requested finally block, or null if no finally block's label matches the name.
 237      */
 238     public Block getInlinedFinally(final String labelName) {
 239         for(final Block inlinedFinally: inlinedFinallies) {
 240             final LabelNode labelNode = getInlinedFinallyLabelNode(inlinedFinally);
 241             if (labelNode.getLabelName().equals(labelName)) {
 242                 return labelNode.getBody();
 243             }
 244         }
 245         return null;
 246     }
 247 
 248     private static LabelNode getInlinedFinallyLabelNode(final Block inlinedFinally) {
 249         return (LabelNode)inlinedFinally.getStatements().get(0);
 250     }
 251 
 252     /**
 253      * Given an outer wrapper block for the {@link LabelNode} as returned by {@link #getInlinedFinallies()},
 254      * returns its actual inlined finally block.
 255      * @param inlinedFinally the outer block for inlined finally, as returned as an element of
 256      * {@link #getInlinedFinallies()}.
 257      * @return the block contained in the {@link LabelNode} contained in the passed block.
 258      */
 259     public static Block getLabelledInlinedFinallyBlock(final Block inlinedFinally) {
 260         return getInlinedFinallyLabelNode(inlinedFinally).getBody();
 261     }
 262 
 263     /**
 264      * Returns a list of inlined finally blocks. Note that this returns a list of {@link Block}s such that each one of
 265      * them has a single {@link LabelNode}, which in turn contains the label name for the finally block and the
 266      * actual finally block. To safely extract the actual finally block, use
 267      * {@link #getLabelledInlinedFinallyBlock(Block)}.
 268      * @return a list of inlined finally blocks.
 269      */
 270     public List<Block> getInlinedFinallies() {
 271         return Collections.unmodifiableList(inlinedFinallies);
 272     }
 273 
 274     /**
 275      * Set the finally body of this try
 276      * @param lc current lexical context
 277      * @param finallyBody new finally body
 278      * @return new TryNode or same if unchanged
 279      */
 280     public TryNode setFinallyBody(final LexicalContext lc, final Block finallyBody) {
 281         if (this.finallyBody == finallyBody) {
 282             return this;
 283         }
 284         return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies, exception));
 285     }
 286 
 287     /**
 288      * Set the inlined finally blocks of this try. Each element should be a block with a single statement that is a
 289      * {@link LabelNode} with a unique label, and the block within the label node should contain the actual inlined
 290      * finally block.
 291      * @param lc current lexical context
 292      * @param inlinedFinallies list of inlined finally blocks
 293      * @return new TryNode or same if unchanged
 294      */
 295     public TryNode setInlinedFinallies(final LexicalContext lc, final List<Block> inlinedFinallies) {
 296         if (this.inlinedFinallies == inlinedFinallies) {
 297             return this;
 298         }
 299         assert checkInlinedFinallies(inlinedFinallies);
 300         return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies, exception));
 301     }
 302 
 303     private static boolean checkInlinedFinallies(final List<Block> inlinedFinallies) {
 304         if (!inlinedFinallies.isEmpty()) {
 305             final Set<String> labels = new HashSet<>();
 306             for (final Block inlinedFinally : inlinedFinallies) {
 307                 final List<Statement> stmts = inlinedFinally.getStatements();
 308                 assert stmts.size() == 1;
 309                 final LabelNode ln = getInlinedFinallyLabelNode(inlinedFinally);
 310                 assert labels.add(ln.getLabelName()); // unique label
 311             }
 312         }
 313         return true;
 314     }
 315 
 316     @Override
 317     public JoinPredecessor setLocalVariableConversion(final LexicalContext lc, final LocalVariableConversion conversion) {
 318         if(this.conversion == conversion) {
 319             return this;
 320         }
 321         return new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies, exception);
 322     }
 323 
 324     @Override
 325     public LocalVariableConversion getLocalVariableConversion() {
 326         return conversion;
 327     }
 328 }