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 }