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