25 |
25 |
26 package jdk.nashorn.internal.ir; |
26 package jdk.nashorn.internal.ir; |
27 |
27 |
28 import java.util.ArrayList; |
28 import java.util.ArrayList; |
29 import java.util.Collections; |
29 import java.util.Collections; |
|
30 import java.util.HashSet; |
30 import java.util.List; |
31 import java.util.List; |
|
32 import java.util.Set; |
31 import jdk.nashorn.internal.ir.annotations.Immutable; |
33 import jdk.nashorn.internal.ir.annotations.Immutable; |
32 import jdk.nashorn.internal.ir.visitor.NodeVisitor; |
34 import jdk.nashorn.internal.ir.visitor.NodeVisitor; |
33 |
35 |
34 /** |
36 /** |
35 * IR representation of a TRY statement. |
37 * IR representation of a TRY statement. |
36 */ |
38 */ |
37 @Immutable |
39 @Immutable |
38 public final class TryNode extends Statement implements JoinPredecessor { |
40 public final class TryNode extends LexicalContextStatement implements JoinPredecessor { |
39 private static final long serialVersionUID = 1L; |
41 private static final long serialVersionUID = 1L; |
40 |
42 |
41 /** Try statements. */ |
43 /** Try statements. */ |
42 private final Block body; |
44 private final Block body; |
43 |
45 |
45 private final List<Block> catchBlocks; |
47 private final List<Block> catchBlocks; |
46 |
48 |
47 /** Finally clause. */ |
49 /** Finally clause. */ |
48 private final Block finallyBody; |
50 private final Block finallyBody; |
49 |
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 |
50 /** Exception symbol. */ |
67 /** Exception symbol. */ |
51 private Symbol exception; |
68 private Symbol exception; |
52 |
|
53 /** Catchall exception for finally expansion, where applicable */ |
|
54 private Symbol finallyCatchAll; |
|
55 |
69 |
56 private final LocalVariableConversion conversion; |
70 private final LocalVariableConversion conversion; |
57 |
71 |
58 /** |
72 /** |
59 * Constructor |
73 * Constructor |
69 super(lineNumber, token, finish); |
83 super(lineNumber, token, finish); |
70 this.body = body; |
84 this.body = body; |
71 this.catchBlocks = catchBlocks; |
85 this.catchBlocks = catchBlocks; |
72 this.finallyBody = finallyBody; |
86 this.finallyBody = finallyBody; |
73 this.conversion = null; |
87 this.conversion = null; |
74 } |
88 this.inlinedFinallies = Collections.emptyList(); |
75 |
89 } |
76 private TryNode(final TryNode tryNode, final Block body, final List<Block> catchBlocks, final Block finallyBody, final LocalVariableConversion conversion) { |
90 |
|
91 private TryNode(final TryNode tryNode, final Block body, final List<Block> catchBlocks, final Block finallyBody, final LocalVariableConversion conversion, final List<Block> inlinedFinallies) { |
77 super(tryNode); |
92 super(tryNode); |
78 this.body = body; |
93 this.body = body; |
79 this.catchBlocks = catchBlocks; |
94 this.catchBlocks = catchBlocks; |
80 this.finallyBody = finallyBody; |
95 this.finallyBody = finallyBody; |
81 this.conversion = conversion; |
96 this.conversion = conversion; |
|
97 this.inlinedFinallies = inlinedFinallies; |
82 this.exception = tryNode.exception; |
98 this.exception = tryNode.exception; |
83 } |
99 } |
84 |
100 |
85 @Override |
101 @Override |
86 public Node ensureUniqueLabels(final LexicalContext lc) { |
102 public Node ensureUniqueLabels(final LexicalContext lc) { |
87 //try nodes are never in lex context |
103 //try nodes are never in lex context |
88 return new TryNode(this, body, catchBlocks, finallyBody, conversion); |
104 return new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies); |
89 } |
105 } |
90 |
106 |
91 @Override |
107 @Override |
92 public boolean isTerminal() { |
108 public boolean isTerminal() { |
93 if (body.isTerminal()) { |
109 if (body.isTerminal()) { |
104 /** |
120 /** |
105 * Assist in IR navigation. |
121 * Assist in IR navigation. |
106 * @param visitor IR navigating visitor. |
122 * @param visitor IR navigating visitor. |
107 */ |
123 */ |
108 @Override |
124 @Override |
109 public Node accept(final NodeVisitor<? extends LexicalContext> visitor) { |
125 public Node accept(final LexicalContext lc, NodeVisitor<? extends LexicalContext> visitor) { |
110 if (visitor.enterTryNode(this)) { |
126 if (visitor.enterTryNode(this)) { |
111 // Need to do finallybody first for termination analysis. TODO still necessary? |
127 // Need to do finallybody first for termination analysis. TODO still necessary? |
112 final Block newFinallyBody = finallyBody == null ? null : (Block)finallyBody.accept(visitor); |
128 final Block newFinallyBody = finallyBody == null ? null : (Block)finallyBody.accept(visitor); |
113 final Block newBody = (Block)body.accept(visitor); |
129 final Block newBody = (Block)body.accept(visitor); |
114 return visitor.leaveTryNode( |
130 return visitor.leaveTryNode( |
115 setBody(newBody). |
131 setBody(lc, newBody). |
116 setFinallyBody(newFinallyBody). |
132 setFinallyBody(lc, newFinallyBody). |
117 setCatchBlocks(Node.accept(visitor, catchBlocks)). |
133 setCatchBlocks(lc, Node.accept(visitor, catchBlocks)). |
118 setFinallyCatchAll(finallyCatchAll)); |
134 setInlinedFinallies(lc, Node.accept(visitor, inlinedFinallies))); |
119 } |
135 } |
120 |
136 |
121 return this; |
137 return this; |
122 } |
138 } |
123 |
139 |
134 return body; |
150 return body; |
135 } |
151 } |
136 |
152 |
137 /** |
153 /** |
138 * Reset the body of this try block |
154 * Reset the body of this try block |
|
155 * @param lc current lexical context |
139 * @param body new body |
156 * @param body new body |
140 * @return new TryNode or same if unchanged |
157 * @return new TryNode or same if unchanged |
141 */ |
158 */ |
142 public TryNode setBody(final Block body) { |
159 public TryNode setBody(final LexicalContext lc, final Block body) { |
143 if (this.body == body) { |
160 if (this.body == body) { |
144 return this; |
161 return this; |
145 } |
162 } |
146 return new TryNode(this, body, catchBlocks, finallyBody, conversion); |
163 return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies)); |
147 } |
164 } |
148 |
165 |
149 /** |
166 /** |
150 * Get the catches for this try block |
167 * Get the catches for this try block |
151 * @return a list of catch nodes |
168 * @return a list of catch nodes |
170 return Collections.unmodifiableList(catchBlocks); |
187 return Collections.unmodifiableList(catchBlocks); |
171 } |
188 } |
172 |
189 |
173 /** |
190 /** |
174 * Set the catch blocks of this try |
191 * Set the catch blocks of this try |
|
192 * @param lc current lexical context |
175 * @param catchBlocks list of catch blocks |
193 * @param catchBlocks list of catch blocks |
176 * @return new TryNode or same if unchanged |
194 * @return new TryNode or same if unchanged |
177 */ |
195 */ |
178 public TryNode setCatchBlocks(final List<Block> catchBlocks) { |
196 public TryNode setCatchBlocks(final LexicalContext lc, final List<Block> catchBlocks) { |
179 if (this.catchBlocks == catchBlocks) { |
197 if (this.catchBlocks == catchBlocks) { |
180 return this; |
198 return this; |
181 } |
199 } |
182 return new TryNode(this, body, catchBlocks, finallyBody, conversion); |
200 return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies)); |
183 } |
201 } |
184 |
202 |
185 /** |
203 /** |
186 * Get the exception symbol for this try block |
204 * Get the exception symbol for this try block |
187 * @return a symbol for the compiler to store the exception in |
205 * @return a symbol for the compiler to store the exception in |
198 this.exception = exception; |
216 this.exception = exception; |
199 return this; |
217 return this; |
200 } |
218 } |
201 |
219 |
202 /** |
220 /** |
203 * Get the catch all symbol for this try block |
|
204 * @return catch all symbol |
|
205 */ |
|
206 public Symbol getFinallyCatchAll() { |
|
207 return this.finallyCatchAll; |
|
208 } |
|
209 |
|
210 /** |
|
211 * If a finally block exists, the synthetic catchall needs another symbol to |
|
212 * store its throwable |
|
213 * @param finallyCatchAll a symbol for the finally catch all exception |
|
214 * @return new TryNode or same if unchanged |
|
215 * |
|
216 * TODO can this still be stateful? |
|
217 */ |
|
218 public TryNode setFinallyCatchAll(final Symbol finallyCatchAll) { |
|
219 this.finallyCatchAll = finallyCatchAll; |
|
220 return this; |
|
221 } |
|
222 |
|
223 /** |
|
224 * Get the body of the finally clause for this try |
221 * Get the body of the finally clause for this try |
225 * @return finally body, or null if no finally |
222 * @return finally body, or null if no finally |
226 */ |
223 */ |
227 public Block getFinallyBody() { |
224 public Block getFinallyBody() { |
228 return finallyBody; |
225 return finallyBody; |
229 } |
226 } |
230 |
227 |
231 /** |
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 /** |
232 * Set the finally body of this try |
271 * Set the finally body of this try |
|
272 * @param lc current lexical context |
233 * @param finallyBody new finally body |
273 * @param finallyBody new finally body |
234 * @return new TryNode or same if unchanged |
274 * @return new TryNode or same if unchanged |
235 */ |
275 */ |
236 public TryNode setFinallyBody(final Block finallyBody) { |
276 public TryNode setFinallyBody(final LexicalContext lc, final Block finallyBody) { |
237 if (this.finallyBody == finallyBody) { |
277 if (this.finallyBody == finallyBody) { |
238 return this; |
278 return this; |
239 } |
279 } |
240 return new TryNode(this, body, catchBlocks, finallyBody, conversion); |
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; |
241 } |
310 } |
242 |
311 |
243 @Override |
312 @Override |
244 public JoinPredecessor setLocalVariableConversion(final LexicalContext lc, final LocalVariableConversion conversion) { |
313 public JoinPredecessor setLocalVariableConversion(final LexicalContext lc, final LocalVariableConversion conversion) { |
245 if(this.conversion == conversion) { |
314 if(this.conversion == conversion) { |
246 return this; |
315 return this; |
247 } |
316 } |
248 return new TryNode(this, body, catchBlocks, finallyBody, conversion); |
317 return new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies); |
249 } |
318 } |
250 |
319 |
251 @Override |
320 @Override |
252 public LocalVariableConversion getLocalVariableConversion() { |
321 public LocalVariableConversion getLocalVariableConversion() { |
253 return conversion; |
322 return conversion; |