1use std::fmt::Write;
6
7use super::{Cx, Throw, frame_flags};
8use crate::heap::HeapError;
9use crate::runtime::{Intrinsic, Names, Realm};
10use crate::shape::PropertyFlags;
11use crate::value::{ObjectId, Value};
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum ErrorKind {
15 Error,
16 Eval,
17 Range,
18 Reference,
19 Syntax,
20 Type,
21 Internal,
22 Uri,
23 Aggregate,
24}
25
26impl ErrorKind {
27 pub const ALL: [ErrorKind; 9] = [
28 ErrorKind::Error,
29 ErrorKind::Eval,
30 ErrorKind::Range,
31 ErrorKind::Reference,
32 ErrorKind::Syntax,
33 ErrorKind::Type,
34 ErrorKind::Internal,
35 ErrorKind::Uri,
36 ErrorKind::Aggregate,
37 ];
38
39 pub const fn name(self) -> &'static str {
40 match self {
41 ErrorKind::Error => "Error",
42 ErrorKind::Eval => "EvalError",
43 ErrorKind::Range => "RangeError",
44 ErrorKind::Reference => "ReferenceError",
45 ErrorKind::Syntax => "SyntaxError",
46 ErrorKind::Type => "TypeError",
47 ErrorKind::Internal => "InternalError",
48 ErrorKind::Uri => "URIError",
49 ErrorKind::Aggregate => "AggregateError",
50 }
51 }
52
53 pub const fn prototype(self) -> Intrinsic {
54 match self {
55 ErrorKind::Error => Intrinsic::ErrorPrototype,
56 ErrorKind::Eval => Intrinsic::EvalErrorPrototype,
57 ErrorKind::Range => Intrinsic::RangeErrorPrototype,
58 ErrorKind::Reference => Intrinsic::ReferenceErrorPrototype,
59 ErrorKind::Syntax => Intrinsic::SyntaxErrorPrototype,
60 ErrorKind::Type => Intrinsic::TypeErrorPrototype,
61 ErrorKind::Internal => Intrinsic::InternalErrorPrototype,
62 ErrorKind::Uri => Intrinsic::UriErrorPrototype,
63 ErrorKind::Aggregate => Intrinsic::AggregateErrorPrototype,
64 }
65 }
66}
67
68#[derive(Clone, Copy, Debug)]
71pub(crate) struct Location<'a> {
72 pub file: &'a str,
73 pub line: u32,
74 pub column: u32,
75}
76
77pub(crate) fn new_error(
80 cx: &mut Cx<'_>,
81 kind: ErrorKind,
82 message: Value,
83) -> Result<Value, HeapError> {
84 new_error_at(cx, kind, message, None, 0)
85}
86
87pub(crate) fn new_error_at(
91 cx: &mut Cx<'_>,
92 kind: ErrorKind,
93 message: Value,
94 location: Option<Location<'_>>,
95 omit: usize,
96) -> Result<Value, HeapError> {
97 let prototype = cx.realm.intrinsic(kind.prototype());
98 let heap = &mut *cx.heap;
99 let mark = heap.roots().mark();
100 heap.roots().push(message);
101 let result = (|| {
102 let object = heap.alloc_object(crate::ObjectKind::Ordinary, Some(prototype), 1)?;
103 heap.set_object_flag(object, crate::heap::object::flags::ERROR);
104 if !message.is_undefined() {
105 let key = Names::key(cx.realm.names.message);
106 heap.add_property(object, key, PropertyFlags::data(true, false, true), message)?;
107 }
108 Ok(object)
109 })();
110 let result = result.and_then(|object| {
111 cx.heap.roots().push(Value::object(object));
112 add_stack_at(cx, object, false, location, omit).map(|()| Value::object(object))
113 });
114 cx.heap.roots().truncate(mark);
115 result
116}
117
118pub(crate) fn new_bare_error(cx: &mut Cx<'_>, kind: ErrorKind) -> Result<Value, HeapError> {
122 let prototype = cx.realm.intrinsic(kind.prototype());
123 let object = cx
124 .heap
125 .alloc_object(crate::ObjectKind::Ordinary, Some(prototype), 1)?;
126 cx.heap
127 .set_object_flag(object, crate::heap::object::flags::ERROR);
128 Ok(Value::object(object))
129}
130
131const BACKTRACE_FRAMES: usize = 64;
133
134pub(crate) fn add_stack(
145 cx: &mut Cx<'_>,
146 object: ObjectId,
147 skip_native: bool,
148) -> Result<(), HeapError> {
149 add_stack_at(cx, object, skip_native, None, 0)
150}
151
152pub(crate) fn add_stack_at(
159 cx: &mut Cx<'_>,
160 object: ObjectId,
161 skip_native: bool,
162 location: Option<Location<'_>>,
163 omit: usize,
164) -> Result<(), HeapError> {
165 let mut text = String::new();
166 if let Some(Location { file, line, column }) = location {
167 let _ = writeln!(text, " at {file}:{line}:{column}");
168 let names = cx.realm.names;
169 let flags = PropertyFlags::data(true, false, true);
170 let file = cx.heap.alloc_string(file)?;
171 cx.heap.add_property(
172 object,
173 Names::key(names.file_name),
174 flags,
175 Value::string(file),
176 )?;
177 cx.heap.add_property(
178 object,
179 Names::key(names.line_number),
180 flags,
181 Value::int32(line as i32),
182 )?;
183 cx.heap.add_property(
184 object,
185 Names::key(names.column_number),
186 flags,
187 Value::int32(column as i32),
188 )?;
189 }
190 let vm = &cx.heap.vm;
191 let len = vm.frames.len();
192 let top = len.saturating_sub(omit);
193 let mut line_index = u32::from(location.is_some());
194 let eval = Value::object(cx.realm.intrinsic(crate::Intrinsic::Eval));
195 let mut sites = vm.natives.iter().rev().peekable();
196 while sites.next_if(|site| site.depth as usize > top).is_some() {}
197 let mut unpositioned = Vec::new();
198 for index in (0..top).rev().take(BACKTRACE_FRAMES) {
199 let frame = &vm.frames[index];
200 let unit = cx.unit(frame.unit);
201 let mut site_pc = None;
205 let mut matched = false;
206 while let Some(site) = sites.next_if(|site| site.depth as usize == index + 1) {
207 let direct_eval =
208 site.callee == eval && unit.direct_eval_sites.binary_search(&site.pc).is_ok();
209 if let Some(callee) = site.callee.as_object()
212 && !(skip_native && index + 1 == len && !matched)
213 && !direct_eval
214 {
215 let name = native_name(cx, callee);
216 let name = if name.is_empty() {
217 "<anonymous>"
218 } else {
219 &name
220 };
221 let _ = writeln!(text, " at {name} (native)");
222 line_index += 1;
223 }
224 matched = true;
225 site_pc = (site.pc != super::NO_PC).then_some(site.pc);
226 }
227 let pc = match site_pc {
228 Some(pc) => Some(pc),
229 None if index + 1 == len => None,
230 None => {
231 let above = &vm.frames[index + 1];
232 (above.flags & frame_flags::ENTRY == 0 && above.return_pc > 0)
233 .then(|| above.return_pc - 1)
234 }
235 };
236 if pc.is_none() {
237 unpositioned.push((index as u32 + 1, line_index));
238 }
239 line_index += 1;
240 let debug = &unit.debug;
241 let key = Names::key(cx.realm.names.name);
245 let name =
246 if cx.heap.lookup_own(frame.function, key).is_none() && !debug_name(&unit).is_empty() {
247 debug_name(&unit).to_owned()
248 } else {
249 native_name(cx, frame.function)
250 };
251 let name = if name.is_empty() {
252 "<anonymous>"
253 } else {
254 &name
255 };
256 let _ = match (&debug.filename, pc.and_then(|pc| debug.position(pc))) {
259 (None, _) => writeln!(text, " at {name}"),
260 (Some(file), Some((line, column))) => {
261 writeln!(text, " at {name} ({file}:{line}:{column})")
262 }
263 (Some(file), None) => writeln!(text, " at {name} ({file})"),
264 };
265 if frame.flags & frame_flags::VIA_CALL != 0 {
268 let _ = writeln!(text, " at call (native)");
269 line_index += 1;
270 } else if frame.flags & frame_flags::VIA_APPLY != 0 {
271 let _ = writeln!(text, " at apply (native)");
272 line_index += 1;
273 } else if frame.flags & frame_flags::VIA_CHAIN != 0 {
274 let vias = vm
275 .via
276 .frames
277 .iter()
278 .rev()
279 .filter(|(at, _)| *at as usize == index);
280 for &(_, via) in vias {
281 let name = via
282 .as_object()
283 .map(|via| native_name(cx, via))
284 .unwrap_or_default();
285 let _ = writeln!(text, " at {name} (native)");
286 line_index += 1;
287 }
288 }
289 }
290 let text = cx.heap.alloc_string(&text)?;
291 let key = Names::key(cx.realm.names.stack);
292 cx.heap.add_property(
293 object,
294 key,
295 PropertyFlags::data(true, false, true),
296 Value::string(text),
297 )?;
298 cx.heap.vm.unpositioned = (!unpositioned.is_empty()).then_some((object, unpositioned));
299 Ok(())
300}
301
302fn debug_name(unit: &crate::bytecode::CodeUnit) -> &str {
303 &unit.debug.name
304}
305
306fn native_name(cx: &Cx<'_>, function: ObjectId) -> String {
310 let key = Names::key(cx.realm.names.name);
311 let read = |object: ObjectId| {
312 cx.heap.lookup_own(object, key).map(|(slot, flags)| {
313 if flags.has(PropertyFlags::ACCESSOR) {
314 String::new()
315 } else {
316 cx.heap
318 .get_slot(object, slot)
319 .as_string()
320 .filter(|id| cx.heap.strings.get(id.0).flat().is_some())
321 .map(|id| cx.heap.string_text(id))
322 .unwrap_or_default()
323 }
324 })
325 };
326 read(function)
327 .or_else(|| cx.heap.prototype_of(function).and_then(read))
328 .unwrap_or_default()
329}
330
331#[cold]
335#[inline(never)]
336pub(crate) fn place_frame(
337 cx: &mut Cx<'_>,
338 thrown: Value,
339 unit: &crate::bytecode::CodeUnit,
340 pc: u32,
341) {
342 let depth = cx.heap.vm.frames.len() as u32;
343 let Some((error, entries)) = &mut cx.heap.vm.unpositioned else {
344 return;
345 };
346 if thrown.as_object() != Some(*error) {
347 return;
348 }
349 let error = *error;
350 let Some(entry) = entries.iter().position(|&(frame, _)| frame == depth) else {
351 return;
352 };
353 let (_, line) = entries.swap_remove(entry);
354 locate_line(cx, error, unit, pc, line as usize);
355}
356
357fn locate_line(
361 cx: &mut Cx<'_>,
362 error: ObjectId,
363 unit: &crate::bytecode::CodeUnit,
364 pc: u32,
365 index: usize,
366) {
367 let Some((line, column)) = unit.debug.position(pc) else {
368 return;
369 };
370 let key = Names::key(cx.realm.names.stack);
371 let Some((slot, flags)) = cx.heap.lookup_own(error, key) else {
372 return;
373 };
374 if flags.has(PropertyFlags::ACCESSOR) {
375 return;
376 }
377 let Some(id) = cx.heap.get_slot(error, slot).as_string() else {
378 return;
379 };
380 let text = cx.heap.string_text(id);
381 let mut lines: Vec<String> = text.split('\n').map(str::to_owned).collect();
382 let Some(target) = lines.get_mut(index) else {
383 return;
384 };
385 let Some(head) = target.strip_suffix(')') else {
386 return;
387 };
388 if head.ends_with("native")
389 || head
390 .rsplit('(')
391 .next()
392 .is_some_and(|file| file.contains(':'))
393 {
394 return;
395 }
396 *target = format!("{head}:{line}:{column})");
397 let located = lines.join("\n");
398 let mark = cx.heap.roots().mark();
399 cx.heap.roots().push(Value::object(error));
400 if let Ok(located) = cx.heap.alloc_string(&located) {
401 cx.heap.set_slot(error, slot, Value::string(located));
402 }
403 cx.heap.roots().truncate(mark);
404}
405
406#[inline(never)]
408pub(crate) fn out_of_memory(cx: &Cx<'_>) -> Throw {
409 oom(cx.realm)
410}
411
412pub(crate) fn oom(realm: &Realm) -> Throw {
414 Throw(Value::object(realm.intrinsic(Intrinsic::OutOfMemoryError)))
415}
416
417#[inline(never)]
418pub(crate) fn heap_error(cx: &Cx<'_>, _: HeapError) -> Throw {
419 out_of_memory(cx)
420}
421
422#[inline(never)]
425pub(crate) fn throw(cx: &mut Cx<'_>, kind: ErrorKind, message: &str) -> Throw {
426 let units: Vec<u16> = message.encode_utf16().collect();
427 throw_units(cx, kind, &units)
428}
429
430pub(crate) fn buffered_message(units: &[u16]) -> Vec<u16> {
435 const BUFFER: usize = 255;
436 let mut out = Vec::with_capacity(units.len().min(BUFFER));
437 let mut used = 0;
438 let mut index = 0;
439 while index < units.len() {
440 let unit = units[index];
441 if unit == 0 {
442 break;
443 }
444 let pair = (0xd800..0xdc00).contains(&unit)
445 && units
446 .get(index + 1)
447 .is_some_and(|next| (0xdc00..0xe000).contains(next));
448 let (bytes, count) = match unit {
449 _ if pair => (4, 2),
450 0..0x80 => (1, 1),
451 0x80..0x800 => (2, 1),
452 _ => (3, 1),
453 };
454 if used + bytes > BUFFER {
455 if used < BUFFER {
456 out.push(0xfffd);
457 }
458 break;
459 }
460 out.extend_from_slice(&units[index..index + count]);
461 used += bytes;
462 index += count;
463 }
464 out
465}
466
467#[inline(never)]
470pub(crate) fn throw_units(cx: &mut Cx<'_>, kind: ErrorKind, units: &[u16]) -> Throw {
471 let units = buffered_message(units);
472 let result = cx
473 .heap
474 .alloc_string_units(&units)
475 .and_then(|text| new_error(cx, kind, Value::string(text)));
476 match result {
477 Ok(error) => Throw(error),
478 Err(_) => out_of_memory(cx),
479 }
480}
481
482#[cold]
486#[inline(never)]
487pub(crate) fn throw_prologue(cx: &mut Cx<'_>, kind: ErrorKind, message: &str) -> Throw {
488 let result = cx
489 .heap
490 .alloc_string(message)
491 .and_then(|text| new_error_at(cx, kind, Value::string(text), None, 1));
492 match result {
493 Ok(error) => Throw(error),
494 Err(_) => out_of_memory(cx),
495 }
496}
497
498#[inline(never)]
502pub(crate) fn throw_at(
503 cx: &mut Cx<'_>,
504 kind: ErrorKind,
505 message: &str,
506 file: &str,
507 line: u32,
508 column: u32,
509) -> Throw {
510 located(cx, kind, message, Location { file, line, column }, false)
511}
512
513#[inline(never)]
516pub(crate) fn throw_located(
517 cx: &mut Cx<'_>,
518 kind: ErrorKind,
519 message: &str,
520 file: &str,
521 line: u32,
522 column: u32,
523) -> Throw {
524 located(cx, kind, message, Location { file, line, column }, true)
525}
526
527fn located(
528 cx: &mut Cx<'_>,
529 kind: ErrorKind,
530 message: &str,
531 location: Location<'_>,
532 frames: bool,
533) -> Throw {
534 let result = cx.heap.alloc_string(message).and_then(|text| {
535 let omit = if frames { 0 } else { usize::MAX };
536 new_error_at(cx, kind, Value::string(text), Some(location), omit)
537 });
538 match result {
539 Ok(error) => Throw(error),
540 Err(_) => out_of_memory(cx),
541 }
542}
543
544#[inline(never)]
545pub(crate) fn type_error(cx: &mut Cx<'_>, message: &str) -> Throw {
546 throw(cx, ErrorKind::Type, message)
547}
548
549#[inline(never)]
550pub(crate) fn range_error(cx: &mut Cx<'_>, message: &str) -> Throw {
551 throw(cx, ErrorKind::Range, message)
552}
553
554#[inline(never)]
555pub(crate) fn stack_overflow(cx: &mut Cx<'_>) -> Throw {
556 throw(cx, ErrorKind::Internal, "stack overflow")
557}
558
559#[cold]
564#[inline(never)]
565pub(crate) fn poll_interrupts(cx: &mut Cx<'_>) -> Result<(), Throw> {
566 let interrupt = cx.heap.vm.interrupted
567 || cx.host.interrupt.load(std::sync::atomic::Ordering::Relaxed)
568 || cx.host.hooks.interrupt();
569 if !interrupt {
570 cx.heap.vm.poll = super::INTERRUPT_INTERVAL;
571 return Ok(());
572 }
573 cx.heap.vm.interrupted = true;
574 cx.heap.vm.poll = 1;
575 Err(throw(cx, ErrorKind::Internal, "interrupted"))
576}
577
578#[inline]
581pub(crate) fn catchable(cx: &Cx<'_>) -> bool {
582 !cx.heap.vm.interrupted
583}
584
585#[cold]
589#[inline(never)]
590pub(crate) fn not_a_constructor(cx: &mut Cx<'_>, value: Value) -> Throw {
591 match function_name_units(cx, value) {
592 Some(mut name) => {
593 name.extend(" is not a constructor".encode_utf16());
594 throw_units(cx, ErrorKind::Type, &name)
595 }
596 None => type_error(cx, "not a constructor"),
597 }
598}
599
600pub(crate) fn atom_units(heap: &crate::Heap, key: crate::PropertyKey) -> Vec<u16> {
605 let units: Vec<u16> = match key.kind() {
606 crate::KeyKind::String(id) => heap.string(id).repr().units().collect(),
607 _ => {
608 return super::property::atom_text(heap, key)
609 .encode_utf16()
610 .collect();
611 }
612 };
613 let mut out = Vec::new();
614 if units.iter().all(|&unit| unit < 0x80) {
615 out = units;
616 } else {
617 const LIMIT: usize = 64 - 6;
618 let mut used = 0;
619 for &unit in &units {
620 if used >= LIMIT {
621 break;
622 }
623 used += match unit {
624 0..0x80 => 1,
625 0x80..0x800 => 2,
626 _ => 3,
627 };
628 out.push(unit);
629 }
630 }
631 if let Some(end) = out.iter().position(|&unit| unit == 0) {
633 out.truncate(end);
634 }
635 out
636}
637
638pub(crate) fn atom_message(
640 heap: &crate::Heap,
641 before: &str,
642 key: crate::PropertyKey,
643 after: &str,
644) -> Vec<u16> {
645 let mut units: Vec<u16> = before.encode_utf16().collect();
646 units.extend(atom_units(heap, key));
647 units.extend(after.encode_utf16());
648 units
649}
650
651#[cold]
654#[inline(never)]
655pub(crate) fn construct_error(cx: &mut Cx<'_>, value: Value) -> Throw {
656 if value.as_object().is_none() {
657 return type_error(cx, "not a function");
658 }
659 not_a_constructor(cx, value)
660}
661
662fn function_name_units(cx: &Cx<'_>, value: Value) -> Option<Vec<u16>> {
665 let object = value.as_object()?;
666 let heap = &cx.heap;
667 if !heap
668 .header(object)
669 .has(crate::heap::object::flags::CALLABLE)
670 || heap.object_kind(object) == crate::heap::object::ObjectKind::Proxy
671 {
672 return None;
673 }
674 let key = Names::key(cx.realm.names.name);
675 let read = |object: ObjectId| -> Option<Option<Vec<u16>>> {
676 let (slot, flags) = heap.lookup_own(object, key)?;
677 if flags.has(PropertyFlags::ACCESSOR) {
678 return Some(None);
679 }
680 Some(
683 heap.get_slot(object, slot)
684 .as_string()
685 .filter(|id| heap.strings.get(id.0).flat().is_some())
686 .map(|id| heap.string(id).repr().units().collect()),
687 )
688 };
689 let units = match read(object) {
690 Some(found) => found,
691 None => heap.prototype_of(object).and_then(read).flatten(),
692 }?;
693 let end = units
694 .iter()
695 .position(|&unit| unit == 0)
696 .unwrap_or(units.len());
697 Some(units[..end].to_vec())
698}
699
700#[cold]
702#[inline(never)]
703pub(crate) fn object_expected(cx: &mut Cx<'_>, class: &str) -> Throw {
704 type_error(cx, &format!("{class} object expected"))
705}
706
707#[cold]
709#[inline(never)]
710pub(crate) fn string_too_long(cx: &mut Cx<'_>) -> Throw {
711 throw(cx, ErrorKind::Internal, "string too long")
712}
713
714#[cold]
717#[inline(never)]
718pub(crate) fn not_iterable_nullish(cx: &mut Cx<'_>, value: Value) -> Throw {
719 let base = if value.is_null() { "null" } else { "undefined" };
720 type_error(
721 cx,
722 &format!("cannot read property 'Symbol.iterator' of {base}"),
723 )
724}