/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef frontend_TryEmitter_h #define frontend_TryEmitter_h #include "mozilla/Attributes.h" // MOZ_STACK_CLASS #include "mozilla/Maybe.h" // mozilla::Maybe, mozilla::Nothing #include // uint32_t #include "frontend/BytecodeControlStructures.h" // TryFinallyControl #include "frontend/BytecodeOffset.h" // BytecodeOffset #include "frontend/JumpList.h" // JumpList, JumpTarget namespace js { namespace frontend { struct BytecodeEmitter; // Class for emitting bytecode for blocks like try-catch-finally. // // Usage: (check for the return value is omitted for simplicity) // // `try { try_block } catch (ex) { catch_block }` // TryEmitter tryCatch(this, TryEmitter::Kind::TryCatch, // TryEmitter::ControlKind::Syntactic); // tryCatch.emitTry(); // emit(try_block); // tryCatch.emitCatch(); // emit(catch_block); // Pending exception is on stack // tryCatch.emitEnd(); // // `try { try_block } finally { finally_block }` // TryEmitter tryCatch(this, TryEmitter::Kind::TryFinally, // TryEmitter::ControlKind::Syntactic); // tryCatch.emitTry(); // emit(try_block); // // finally_pos: The "{" character's position in the source code text. // tryCatch.emitFinally(Some(finally_pos)); // emit(finally_block); // tryCatch.emitEnd(); // // `try { try_block } catch (ex) {catch_block} finally { finally_block }` // TryEmitter tryCatch(this, TryEmitter::Kind::TryCatchFinally, // TryEmitter::ControlKind::Syntactic); // tryCatch.emitTry(); // emit(try_block); // tryCatch.emitCatch(); // emit(catch_block); // tryCatch.emitFinally(Some(finally_pos)); // emit(finally_block); // tryCatch.emitEnd(); // class MOZ_STACK_CLASS TryEmitter { public: enum class Kind { TryCatch, TryCatchFinally, TryFinally }; // Syntactic try-catch-finally and internally used non-syntactic // try-catch-finally behave differently for 2 points. // // The first one is whether TryFinallyControl is used or not. // See the comment for `controlInfo_`. // // The second one is whether the catch and finally blocks handle the frame's // return value. For syntactic try-catch-finally, the bytecode marked with // "*" are emitted to clear return value with `undefined` before the catch // block and the finally block, and also to save/restore the return value // before/after the finally block. Note that these instructions are not // emitted for noScriptRval scripts that don't track the return value. // // JSOp::Try offsetOf(jumpToEnd) // // try_body... // // JSOp::Goto finally // JSOp::JumpTarget // jumpToEnd: // JSOp::Goto end: // // catch: // JSOp::JumpTarget // * JSOp::Undefined // * JSOp::SetRval // // catch_body... // // JSOp::Goto finally // JSOp::JumpTarget // JSOp::Goto end // // finally: // JSOp::JumpTarget // * JSOp::GetRval // * JSOp::Undefined // * JSOp::SetRval // // finally_body... // // * JSOp::SetRval // JSOp::Nop // // end: // JSOp::JumpTarget // // For syntactic try-catch-finally, Syntactic should be used. // For non-syntactic try-catch-finally, NonSyntactic should be used. enum class ControlKind { Syntactic, NonSyntactic }; private: BytecodeEmitter* bce_; Kind kind_; ControlKind controlKind_; // Tracks jumps to the finally block for later fixup. // // When a finally block is active, non-local jumps (including // jumps-over-catches) result in a goto being written into the bytecode // stream and fixed-up later. // // For non-syntactic try-catch-finally, all that handling is skipped. // The non-syntactic try-catch-finally must: // * have only one catch block // * have JSOp::Goto at the end of catch-block // * have no non-local-jump // * don't use finally block for normal completion of try-block and // catch-block // // Additionally, a finally block may be emitted for non-syntactic // try-catch-finally, even if the kind is TryCatch, because GOSUBs are not // emitted. mozilla::Maybe controlInfo_; // The stack depth before emitting JSOp::Try. int depth_; // The offset of the JSOp::Try op. BytecodeOffset tryOpOffset_; // JSOp::JumpTarget after the entire try-catch-finally block. JumpList catchAndFinallyJump_; // The offset of JSOp::Goto at the end of the try block. JumpTarget tryEnd_; // The offset of JSOp::JumpTarget at the beginning of the finally block. JumpTarget finallyStart_; #ifdef DEBUG // The state of this emitter. // // +-------+ emitTry +-----+ emitCatch +-------+ emitEnd +-----+ // | Start |-------->| Try |-+---------->| Catch |-+->+--------->| End | // +-------+ +-----+ | +-------+ | ^ +-----+ // | | | // | +------------------+ +----+ // | | | // | v emitFinally +---------+ | // +->+------------>| Finally |--+ // +---------+ enum class State { // The initial state. Start, // After calling emitTry. Try, // After calling emitCatch. Catch, // After calling emitFinally. Finally, // After calling emitEnd. End }; State state_; #endif bool hasCatch() const { return kind_ == Kind::TryCatch || kind_ == Kind::TryCatchFinally; } bool hasFinally() const { return kind_ == Kind::TryCatchFinally || kind_ == Kind::TryFinally; } BytecodeOffset offsetAfterTryOp() const { return tryOpOffset_ + BytecodeOffsetDiff(JSOpLength_Try); } // Returns true if catch and finally blocks should handle the frame's // return value. bool shouldUpdateRval() const; // Jump to the finally block. After the finally block executes, // fall through to the code following the finally block. [[nodiscard]] bool emitJumpToFinallyWithFallthrough(); public: TryEmitter(BytecodeEmitter* bce, Kind kind, ControlKind controlKind); [[nodiscard]] bool emitTry(); [[nodiscard]] bool emitCatch(); // If `finallyPos` is specified, it's an offset of the finally block's // "{" character in the source code text, to improve line:column number in // the error reporting. // For non-syntactic try-catch-finally, `finallyPos` can be omitted. [[nodiscard]] bool emitFinally( const mozilla::Maybe& finallyPos = mozilla::Nothing()); [[nodiscard]] bool emitEnd(); private: [[nodiscard]] bool emitTryEnd(); [[nodiscard]] bool emitCatchEnd(); [[nodiscard]] bool emitFinallyEnd(); }; } /* namespace frontend */ } /* namespace js */ #endif /* frontend_TryEmitter_h */