TryNode.java revision 1399:eea9202e8930
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
26package jdk.nashorn.internal.ir;
27
28import java.util.ArrayList;
29import java.util.Collections;
30import java.util.HashSet;
31import java.util.List;
32import java.util.Set;
33import jdk.nashorn.internal.ir.annotations.Immutable;
34import jdk.nashorn.internal.ir.visitor.NodeVisitor;
35
36/**
37 * IR representation of a TRY statement.
38 */
39@Immutable
40public 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}
329