boa_engine/vm/opcode/control_flow/
throw.rs

1use crate::{
2    vm::{opcode::Operation, CompletionType},
3    Context, JsError, JsNativeError, JsResult,
4};
5
6/// `Throw` implements the Opcode Operation for `Opcode::Throw`
7///
8/// Operation:
9///  - Throw exception.
10#[derive(Debug, Clone, Copy)]
11pub(crate) struct Throw;
12
13impl Operation for Throw {
14    const NAME: &'static str = "Throw";
15    const INSTRUCTION: &'static str = "INST - Throw";
16    const COST: u8 = 6;
17
18    fn execute(context: &mut Context) -> JsResult<CompletionType> {
19        let error = JsError::from_opaque(context.vm.pop());
20        context.vm.pending_exception = Some(error);
21
22        // Note: -1 because we increment after fetching the opcode.
23        let pc = context.vm.frame().pc - 1;
24        if context.vm.handle_exception_at(pc) {
25            return Ok(CompletionType::Normal);
26        }
27
28        Ok(CompletionType::Throw)
29    }
30}
31
32/// `ReThrow` implements the Opcode Operation for `Opcode::ReThrow`
33///
34/// Operation:
35///  - Rethrow the pending exception.
36#[derive(Debug, Clone, Copy)]
37pub(crate) struct ReThrow;
38
39impl Operation for ReThrow {
40    const NAME: &'static str = "ReThrow";
41    const INSTRUCTION: &'static str = "INST - ReThrow";
42    const COST: u8 = 2;
43
44    fn execute(context: &mut Context) -> JsResult<CompletionType> {
45        // Note: -1 because we increment after fetching the opcode.
46        let pc = context.vm.frame().pc.saturating_sub(1);
47        if context.vm.handle_exception_at(pc) {
48            return Ok(CompletionType::Normal);
49        }
50
51        // Note: If we are rethowing and there is no pending error,
52        //       this means that return was called on the generator.
53        //
54        // Note: If we reached this stage then we there is no handler to handle this,
55        //       so return (only for generators).
56        if context.vm.pending_exception.is_none() {
57            return Ok(CompletionType::Return);
58        }
59
60        Ok(CompletionType::Throw)
61    }
62}
63
64/// `Exception` implements the Opcode Operation for `Opcode::Exception`
65///
66/// Operation:
67///  - Get the thrown exception and push on the stack.
68#[derive(Debug, Clone, Copy)]
69pub(crate) struct Exception;
70
71impl Operation for Exception {
72    const NAME: &'static str = "Exception";
73    const INSTRUCTION: &'static str = "INST - Exception";
74    const COST: u8 = 2;
75
76    fn execute(context: &mut Context) -> JsResult<CompletionType> {
77        if let Some(error) = context.vm.pending_exception.take() {
78            let error = error.to_opaque(context);
79            context.vm.push(error);
80            return Ok(CompletionType::Normal);
81        }
82
83        // If there is no pending error, this means that `return()` was called
84        // on the generator, so we rethrow this (empty) error until there is no handler to handle it.
85        // This is done to run the finally code.
86        //
87        // This should be unreachable for regular functions.
88        ReThrow::execute(context)
89    }
90}
91
92/// `MaybeException` implements the Opcode Operation for `Opcode::MaybeException`
93///
94/// Operation:
95///  - Get the thrown pending exception if it's set and push `true`, otherwise push only `false`.
96#[derive(Debug, Clone, Copy)]
97pub(crate) struct MaybeException;
98
99impl Operation for MaybeException {
100    const NAME: &'static str = "MaybeException";
101    const INSTRUCTION: &'static str = "INST - MaybeException";
102    const COST: u8 = 3;
103
104    fn execute(context: &mut Context) -> JsResult<CompletionType> {
105        if let Some(error) = context.vm.pending_exception.take() {
106            let error = error.to_opaque(context);
107            context.vm.push(error);
108            context.vm.push(true);
109            return Ok(CompletionType::Normal);
110        }
111
112        context.vm.push(false);
113        Ok(CompletionType::Normal)
114    }
115}
116
117/// `ThrowNewTypeError` implements the Opcode Operation for `Opcode::ThrowNewTypeError`
118///
119/// Operation:
120///  - Throws a `TypeError` exception.
121#[derive(Debug, Clone, Copy)]
122pub(crate) struct ThrowNewTypeError;
123
124impl ThrowNewTypeError {
125    fn operation(context: &mut Context, index: usize) -> JsResult<CompletionType> {
126        let msg = context.vm.frame().code_block().constant_string(index);
127        let msg = msg
128            .to_std_string()
129            .expect("throw message must be an ASCII string");
130        Err(JsNativeError::typ().with_message(msg).into())
131    }
132}
133
134impl Operation for ThrowNewTypeError {
135    const NAME: &'static str = "ThrowNewTypeError";
136    const INSTRUCTION: &'static str = "INST - ThrowNewTypeError";
137    const COST: u8 = 2;
138
139    fn execute(context: &mut Context) -> JsResult<CompletionType> {
140        let index = context.vm.read::<u8>() as usize;
141        Self::operation(context, index)
142    }
143
144    fn execute_with_u16_operands(context: &mut Context) -> JsResult<CompletionType> {
145        let index = context.vm.read::<u16>() as usize;
146        Self::operation(context, index)
147    }
148
149    fn execute_with_u32_operands(context: &mut Context) -> JsResult<CompletionType> {
150        let index = context.vm.read::<u32>() as usize;
151        Self::operation(context, index)
152    }
153}
154
155/// `ThrowNewSyntaxError` implements the Opcode Operation for `Opcode::ThrowNewSyntaxError`
156///
157/// Operation:
158///  - Throws a `SyntaxError` exception.
159#[derive(Debug, Clone, Copy)]
160pub(crate) struct ThrowNewSyntaxError;
161
162impl ThrowNewSyntaxError {
163    fn operation(context: &mut Context, index: usize) -> JsResult<CompletionType> {
164        let msg = context.vm.frame().code_block().constant_string(index);
165        let msg = msg
166            .to_std_string()
167            .expect("throw message must be an ASCII string");
168        Err(JsNativeError::syntax().with_message(msg).into())
169    }
170}
171
172impl Operation for ThrowNewSyntaxError {
173    const NAME: &'static str = "ThrowNewSyntaxError";
174    const INSTRUCTION: &'static str = "INST - ThrowNewSyntaxError";
175    const COST: u8 = 2;
176
177    fn execute(context: &mut Context) -> JsResult<CompletionType> {
178        let index = context.vm.read::<u8>() as usize;
179        Self::operation(context, index)
180    }
181
182    fn execute_with_u16_operands(context: &mut Context) -> JsResult<CompletionType> {
183        let index = context.vm.read::<u16>() as usize;
184        Self::operation(context, index)
185    }
186
187    fn execute_with_u32_operands(context: &mut Context) -> JsResult<CompletionType> {
188        let index = context.vm.read::<u32>() as usize;
189        Self::operation(context, index)
190    }
191}