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      */
TryNode(final int lineNumber, final long token, final int finish, final Block body, final List<Block> catchBlocks, final Block finallyBody)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 
TryNode(final TryNode tryNode, final Block body, final List<Block> catchBlocks, final Block finallyBody, final LocalVariableConversion conversion, final List<Block> inlinedFinallies, final Symbol exception)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
ensureUniqueLabels(final LexicalContext lc)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
isTerminal()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
accept(final LexicalContext lc, final NodeVisitor<? extends LexicalContext> visitor)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
toString(final StringBuilder sb, final boolean printType)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      */
getBody()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      */
setBody(final LexicalContext lc, final Block body)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      */
getCatches()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 
getCatchNodeFromBlock(final Block catchBlock)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      */
getCatchBlocks()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      */
setCatchBlocks(final LexicalContext lc, final List<Block> catchBlocks)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      */
getException()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      */
setException(final LexicalContext lc, final Symbol exception)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      */
getFinallyBody()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      */
getInlinedFinally(final String labelName)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 
getInlinedFinallyLabelNode(final Block inlinedFinally)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      */
getLabelledInlinedFinallyBlock(final Block inlinedFinally)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      */
getInlinedFinallies()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      */
setFinallyBody(final LexicalContext lc, final Block finallyBody)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      */
setInlinedFinallies(final LexicalContext lc, final List<Block> inlinedFinallies)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 
checkInlinedFinallies(final List<Block> inlinedFinallies)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
setLocalVariableConversion(final LexicalContext lc, final LocalVariableConversion conversion)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
getLocalVariableConversion()325     public LocalVariableConversion getLocalVariableConversion() {
326         return conversion;
327     }
328 }
329