1010//! [spec]: https://tc39.es/ecma262/#sec-error-objects
1111//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
1212
13+ use std:: fmt:: Write ;
14+
1315use crate :: {
1416 Context , JsArgs , JsData , JsResult , JsString , JsValue ,
1517 builtins:: BuiltInObject ,
@@ -20,7 +22,10 @@ use crate::{
2022 property:: Attribute ,
2123 realm:: Realm ,
2224 string:: StaticJsStrings ,
23- vm:: shadow_stack:: { Backtrace , ShadowEntry } ,
25+ vm:: {
26+ NativeSourceInfo ,
27+ shadow_stack:: { ErrorStack , ShadowEntry } ,
28+ } ,
2429} ;
2530use boa_gc:: { Finalize , Trace } ;
2631use boa_macros:: js_str;
@@ -136,53 +141,67 @@ pub struct Error {
136141
137142 // The position of where the Error was created does not affect equality check.
138143 #[ unsafe_ignore_trace]
139- pub ( crate ) position : IgnoreEq < Option < ShadowEntry > > ,
140-
141- // The backtrace captured when this error was thrown. Stored here so it
142- // survives the JsError → JsValue → JsError round-trip through promise
143- // rejection. Does not affect equality checks.
144- #[ unsafe_ignore_trace]
145- pub ( crate ) backtrace : IgnoreEq < Option < Backtrace > > ,
144+ pub ( crate ) stack : IgnoreEq < ErrorStack > ,
146145}
147146
148147impl Error {
149148 /// Create a new [`Error`].
150149 #[ inline]
151150 #[ must_use]
151+ #[ cfg_attr( feature = "native-backtrace" , track_caller) ]
152152 pub fn new ( tag : ErrorKind ) -> Self {
153153 Self {
154154 tag,
155- position : IgnoreEq ( None ) ,
156- backtrace : IgnoreEq ( None ) ,
155+ stack : IgnoreEq ( ErrorStack :: Position ( ShadowEntry :: Native {
156+ function_name : None ,
157+ source_info : NativeSourceInfo :: caller ( ) ,
158+ } ) ) ,
157159 }
158160 }
159161
160- /// Create a new [`Error`] with the given optional [`ShadowEntry `].
161- pub ( crate ) fn with_shadow_entry ( tag : ErrorKind , entry : Option < ShadowEntry > ) -> Self {
162+ /// Create a new [`Error`] with the given [`ErrorStack `].
163+ pub ( crate ) fn with_stack ( tag : ErrorKind , location : ErrorStack ) -> Self {
162164 Self {
163165 tag,
164- position : IgnoreEq ( entry) ,
165- backtrace : IgnoreEq ( None ) ,
166+ stack : IgnoreEq ( location) ,
166167 }
167168 }
168169
169170 /// Get the position from the last called function.
170171 pub ( crate ) fn with_caller_position ( tag : ErrorKind , context : & Context ) -> Self {
172+ let limit = context. runtime_limits ( ) . backtrace_limit ( ) ;
173+ let backtrace = context. vm . shadow_stack . caller_position ( limit) ;
171174 Self {
172175 tag,
173- position : IgnoreEq ( context. vm . shadow_stack . caller_position ( ) ) ,
174- backtrace : IgnoreEq ( None ) ,
176+ stack : IgnoreEq ( ErrorStack :: Backtrace ( backtrace) ) ,
175177 }
176178 }
177179}
178180
179181impl IntrinsicObject for Error {
180182 fn init ( realm : & Realm ) {
181- let attribute = Attribute :: WRITABLE | Attribute :: NON_ENUMERABLE | Attribute :: CONFIGURABLE ;
183+ let property_attribute =
184+ Attribute :: WRITABLE | Attribute :: NON_ENUMERABLE | Attribute :: CONFIGURABLE ;
185+ let accessor_attribute = Attribute :: NON_ENUMERABLE | Attribute :: CONFIGURABLE ;
186+
187+ let get_stack = BuiltInBuilder :: callable ( realm, Self :: get_stack)
188+ . name ( js_string ! ( "get stack" ) )
189+ . build ( ) ;
190+
191+ let set_stack = BuiltInBuilder :: callable ( realm, Self :: set_stack)
192+ . name ( js_string ! ( "set stack" ) )
193+ . build ( ) ;
194+
182195 let builder = BuiltInBuilder :: from_standard_constructor :: < Self > ( realm)
183- . property ( js_string ! ( "name" ) , Self :: NAME , attribute)
184- . property ( js_string ! ( "message" ) , js_string ! ( ) , attribute)
185- . method ( Self :: to_string, js_string ! ( "toString" ) , 0 ) ;
196+ . property ( js_string ! ( "name" ) , Self :: NAME , property_attribute)
197+ . property ( js_string ! ( "message" ) , js_string ! ( ) , property_attribute)
198+ . method ( Self :: to_string, js_string ! ( "toString" ) , 0 )
199+ . accessor (
200+ js_string ! ( "stack" ) ,
201+ Some ( get_stack) ,
202+ Some ( set_stack) ,
203+ accessor_attribute,
204+ ) ;
186205
187206 #[ cfg( feature = "experimental" ) ]
188207 let builder = builder. static_method ( Error :: is_error, js_string ! ( "isError" ) , 1 ) ;
@@ -201,7 +220,7 @@ impl BuiltInObject for Error {
201220
202221impl BuiltInConstructor for Error {
203222 const CONSTRUCTOR_ARGUMENTS : usize = 1 ;
204- const PROTOTYPE_STORAGE_SLOTS : usize = 3 ;
223+ const PROTOTYPE_STORAGE_SLOTS : usize = 5 ;
205224 const CONSTRUCTOR_STORAGE_SLOTS : usize = 1 ;
206225
207226 const STANDARD_CONSTRUCTOR : fn ( & StandardConstructors ) -> & StandardConstructor =
@@ -272,6 +291,77 @@ impl Error {
272291 Ok ( ( ) )
273292 }
274293
294+ /// `get Error.prototype.stack`
295+ ///
296+ /// The accessor property of Error instances represents the stack trace
297+ /// when the error was created.
298+ ///
299+ /// More information:
300+ /// - [Proposal][spec]
301+ ///
302+ /// [spec]: https://tc39.es/proposal-error-stacks/
303+ #[ allow( clippy:: unnecessary_wraps) ]
304+ fn get_stack ( this : & JsValue , _: & [ JsValue ] , _context : & mut Context ) -> JsResult < JsValue > {
305+ // 1. Let E be the this value.
306+ // 2. If E is not an Object, return undefined.
307+ let Some ( e) = this. as_object ( ) else {
308+ return Ok ( JsValue :: undefined ( ) ) ;
309+ } ;
310+
311+ // 3. Let errorData be the value of the [[ErrorData]] internal slot of E.
312+ // 4. If errorData is undefined, return undefined.
313+ let Some ( error_data) = e. downcast_ref :: < Error > ( ) else {
314+ return Ok ( JsValue :: undefined ( ) ) ;
315+ } ;
316+
317+ // 5. Let stackString be an implementation-defined String value representing the call stack.
318+ // 6. Return stackString.
319+ if let Some ( backtrace) = error_data. stack . 0 . backtrace ( ) {
320+ let stack_string = backtrace
321+ . iter ( )
322+ . rev ( )
323+ . fold ( String :: new ( ) , |mut output, entry| {
324+ let _ = writeln ! ( & mut output, " at {}" , entry. display( true ) ) ;
325+ output
326+ } ) ;
327+ return Ok ( js_string ! ( stack_string) . into ( ) ) ;
328+ }
329+
330+ // 7. If no stack trace is available, return undefined.
331+ Ok ( JsValue :: undefined ( ) )
332+ }
333+
334+ /// `set Error.prototype.stack`
335+ ///
336+ /// The setter for the stack property.
337+ ///
338+ /// More information:
339+ /// - [Proposal][spec]
340+ ///
341+ /// [spec]: https://tc39.es/proposal-error-stacks/
342+ fn set_stack ( this : & JsValue , args : & [ JsValue ] , context : & mut Context ) -> JsResult < JsValue > {
343+ // 1. Let E be the this value.
344+ // 2. If Type(E) is not Object, throw a TypeError exception.
345+ let e = this. as_object ( ) . ok_or_else ( || {
346+ JsNativeError :: typ ( )
347+ . with_message ( "Error.prototype.stack setter requires that 'this' be an Object" )
348+ } ) ?;
349+
350+ // 3. Let numberOfArgs be the number of arguments passed to this function call.
351+ // 4. If numberOfArgs is 0, throw a TypeError exception.
352+ let Some ( value) = args. first ( ) else {
353+ return Err ( JsNativeError :: typ ( )
354+ . with_message (
355+ "Error.prototype.stack setter requires at least 1 argument, but only 0 were passed" ,
356+ )
357+ . into ( ) ) ;
358+ } ;
359+
360+ // 5. Return ? CreateDataPropertyOrThrow(E, "stack", value).
361+ e. create_data_property_or_throw ( js_string ! ( "stack" ) , value. clone ( ) , context)
362+ . map ( Into :: into)
363+ }
364+
275365 /// `Error.prototype.toString()`
276366 ///
277367 /// The `toString()` method returns a string representing the specified Error object.
0 commit comments