1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  * vim: set ts=8 sts=2 et sw=2 tw=80:
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "frontend/TryEmitter.h"
8 
9 #include "mozilla/Assertions.h"  // MOZ_ASSERT
10 
11 #include "frontend/BytecodeEmitter.h"  // BytecodeEmitter
12 #include "frontend/SharedContext.h"    // StatementKind
13 #include "vm/JSScript.h"               // JSTRY_CATCH, JSTRY_FINALLY
14 #include "vm/Opcodes.h"                // JSOp
15 
16 using namespace js;
17 using namespace js::frontend;
18 
19 using mozilla::Maybe;
20 
TryEmitter(BytecodeEmitter * bce,Kind kind,ControlKind controlKind)21 TryEmitter::TryEmitter(BytecodeEmitter* bce, Kind kind, ControlKind controlKind)
22     : bce_(bce),
23       kind_(kind),
24       controlKind_(controlKind),
25       depth_(0),
26       tryOpOffset_(0)
27 #ifdef DEBUG
28       ,
29       state_(State::Start)
30 #endif
31 {
32   if (controlKind_ == ControlKind::Syntactic) {
33     controlInfo_.emplace(
34         bce_, hasFinally() ? StatementKind::Finally : StatementKind::Try);
35   }
36 }
37 
emitTry()38 bool TryEmitter::emitTry() {
39   MOZ_ASSERT(state_ == State::Start);
40 
41   // Since an exception can be thrown at any place inside the try block,
42   // we need to restore the stack and the scope chain before we transfer
43   // the control to the exception handler.
44   //
45   // For that we store in a try note associated with the catch or
46   // finally block the stack depth upon the try entry. The interpreter
47   // uses this depth to properly unwind the stack and the scope chain.
48   depth_ = bce_->bytecodeSection().stackDepth();
49 
50   tryOpOffset_ = bce_->bytecodeSection().offset();
51   if (!bce_->emit1(JSOp::Try)) {
52     return false;
53   }
54 
55 #ifdef DEBUG
56   state_ = State::Try;
57 #endif
58   return true;
59 }
60 
emitTryEnd()61 bool TryEmitter::emitTryEnd() {
62   MOZ_ASSERT(state_ == State::Try);
63   MOZ_ASSERT(depth_ == bce_->bytecodeSection().stackDepth());
64 
65   // Gosub to finally, if present.
66   if (hasFinally() && controlInfo_) {
67     if (!bce_->emitGoSub(&controlInfo_->gosubs)) {
68       return false;
69     }
70   }
71 
72   // Emit jump over catch and/or finally.
73   if (!bce_->emitJump(JSOp::Goto, &catchAndFinallyJump_)) {
74     return false;
75   }
76 
77   if (!bce_->emitJumpTarget(&tryEnd_)) {
78     return false;
79   }
80 
81   return true;
82 }
83 
emitCatch()84 bool TryEmitter::emitCatch() {
85   MOZ_ASSERT(state_ == State::Try);
86   if (!emitTryEnd()) {
87     return false;
88   }
89 
90   MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == depth_);
91 
92   if (controlKind_ == ControlKind::Syntactic) {
93     // Clear the frame's return value that might have been set by the
94     // try block:
95     //
96     //   eval("try { 1; throw 2 } catch(e) {}"); // undefined, not 1
97     if (!bce_->emit1(JSOp::Undefined)) {
98       return false;
99     }
100     if (!bce_->emit1(JSOp::SetRval)) {
101       return false;
102     }
103   }
104 
105   if (!bce_->emit1(JSOp::Exception)) {
106     return false;
107   }
108 
109 #ifdef DEBUG
110   state_ = State::Catch;
111 #endif
112   return true;
113 }
114 
emitCatchEnd()115 bool TryEmitter::emitCatchEnd() {
116   MOZ_ASSERT(state_ == State::Catch);
117 
118   if (!controlInfo_) {
119     return true;
120   }
121 
122   // gosub <finally>, if required.
123   if (hasFinally()) {
124     if (!bce_->emitGoSub(&controlInfo_->gosubs)) {
125       return false;
126     }
127     MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == depth_);
128 
129     // Jump over the finally block.
130     if (!bce_->emitJump(JSOp::Goto, &catchAndFinallyJump_)) {
131       return false;
132     }
133   }
134 
135   return true;
136 }
137 
emitFinally(const Maybe<uint32_t> & finallyPos)138 bool TryEmitter::emitFinally(
139     const Maybe<uint32_t>& finallyPos /* = Nothing() */) {
140   // If we are using controlInfo_ (i.e., emitting a syntactic try
141   // blocks), we must have specified up front if there will be a finally
142   // close. For internal non-syntactic try blocks, like those emitted for
143   // yield* and IteratorClose inside for-of loops, we can emitFinally even
144   // without specifying up front, since the internal non-syntactic try
145   // blocks emit no GOSUBs.
146   if (!controlInfo_) {
147     if (kind_ == Kind::TryCatch) {
148       kind_ = Kind::TryCatchFinally;
149     }
150   } else {
151     MOZ_ASSERT(hasFinally());
152   }
153 
154   if (!hasCatch()) {
155     MOZ_ASSERT(state_ == State::Try);
156     if (!emitTryEnd()) {
157       return false;
158     }
159   } else {
160     MOZ_ASSERT(state_ == State::Catch);
161     if (!emitCatchEnd()) {
162       return false;
163     }
164   }
165 
166   MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == depth_);
167 
168   if (!bce_->emitJumpTarget(&finallyStart_)) {
169     return false;
170   }
171 
172   if (controlInfo_) {
173     // Fix up the gosubs that might have been emitted before non-local
174     // jumps to the finally code.
175     bce_->patchJumpsToTarget(controlInfo_->gosubs, finallyStart_);
176 
177     // Indicate that we're emitting a subroutine body.
178     controlInfo_->setEmittingSubroutine();
179   }
180   if (finallyPos) {
181     if (!bce_->updateSourceCoordNotes(finallyPos.value())) {
182       return false;
183     }
184   }
185   if (!bce_->emit1(JSOp::Finally)) {
186     return false;
187   }
188 
189   if (controlKind_ == ControlKind::Syntactic) {
190     if (!bce_->emit1(JSOp::GetRval)) {
191       return false;
192     }
193 
194     // Clear the frame's return value to make break/continue return
195     // correct value even if there's no other statement before them:
196     //
197     //   eval("x: try { 1 } finally { break x; }"); // undefined, not 1
198     if (!bce_->emit1(JSOp::Undefined)) {
199       return false;
200     }
201     if (!bce_->emit1(JSOp::SetRval)) {
202       return false;
203     }
204   }
205 
206 #ifdef DEBUG
207   state_ = State::Finally;
208 #endif
209   return true;
210 }
211 
emitFinallyEnd()212 bool TryEmitter::emitFinallyEnd() {
213   MOZ_ASSERT(state_ == State::Finally);
214 
215   if (controlKind_ == ControlKind::Syntactic) {
216     if (!bce_->emit1(JSOp::SetRval)) {
217       return false;
218     }
219   }
220 
221   if (!bce_->emit1(JSOp::Retsub)) {
222     return false;
223   }
224 
225   bce_->hasTryFinally = true;
226   return true;
227 }
228 
emitEnd()229 bool TryEmitter::emitEnd() {
230   if (!hasFinally()) {
231     MOZ_ASSERT(state_ == State::Catch);
232     if (!emitCatchEnd()) {
233       return false;
234     }
235   } else {
236     MOZ_ASSERT(state_ == State::Finally);
237     if (!emitFinallyEnd()) {
238       return false;
239     }
240   }
241 
242   MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == depth_);
243 
244   // Fix up the end-of-try/catch jumps to come here.
245   if (!bce_->emitJumpTargetAndPatch(catchAndFinallyJump_)) {
246     return false;
247   }
248 
249   // Add the try note last, to let post-order give us the right ordering
250   // (first to last for a given nesting level, inner to outer by level).
251   if (hasCatch()) {
252     if (!bce_->addTryNote(TryNoteKind::Catch, depth_, offsetAfterTryOp(),
253                           tryEnd_.offset)) {
254       return false;
255     }
256   }
257 
258   // If we've got a finally, mark try+catch region with additional
259   // trynote to catch exceptions (re)thrown from a catch block or
260   // for the try{}finally{} case.
261   if (hasFinally()) {
262     if (!bce_->addTryNote(TryNoteKind::Finally, depth_, offsetAfterTryOp(),
263                           finallyStart_.offset)) {
264       return false;
265     }
266   }
267 
268 #ifdef DEBUG
269   state_ = State::End;
270 #endif
271   return true;
272 }
273