@@ -20,7 +20,8 @@ use crate::{
2020 property:: Attribute ,
2121 realm:: Realm ,
2222 string:: StaticJsStrings ,
23- vm:: shadow_stack:: ShadowEntry ,
23+ vm:: shadow_stack:: { ErrorLocation , ShadowEntry } ,
24+ vm:: source_info:: NativeSourceInfo ,
2425} ;
2526use boa_gc:: { Finalize , Trace } ;
2627use boa_macros:: js_str;
@@ -136,44 +137,67 @@ pub struct Error {
136137
137138 // The position of where the Error was created does not affect equality check.
138139 #[ unsafe_ignore_trace]
139- pub ( crate ) position : IgnoreEq < Option < ShadowEntry > > ,
140+ pub ( crate ) location : IgnoreEq < ErrorLocation > ,
140141}
141142
142143impl Error {
143144 /// Create a new [`Error`].
144145 #[ inline]
145146 #[ must_use]
147+ #[ cfg_attr( feature = "native-backtrace" , track_caller) ]
146148 pub fn new ( tag : ErrorKind ) -> Self {
147149 Self {
148150 tag,
149- position : IgnoreEq ( None ) ,
151+ location : IgnoreEq :: new ( ErrorLocation :: Position ( ShadowEntry :: Native {
152+ function_name : None ,
153+ source_info : NativeSourceInfo :: caller ( ) ,
154+ } ) ) ,
150155 }
151156 }
152157
153- /// Create a new [`Error`] with the given optional [`ShadowEntry `].
154- pub ( crate ) fn with_shadow_entry ( tag : ErrorKind , entry : Option < ShadowEntry > ) -> Self {
158+ /// Create a new [`Error`] with the given [`ErrorLocation `].
159+ pub ( crate ) fn with_location ( tag : ErrorKind , location : ErrorLocation ) -> Self {
155160 Self {
156161 tag,
157- position : IgnoreEq ( entry ) ,
162+ location : IgnoreEq :: new ( location ) ,
158163 }
159164 }
160165
161166 /// Get the position from the last called function.
162167 pub ( crate ) fn with_caller_position ( tag : ErrorKind , context : & Context ) -> Self {
168+ let limit = context. runtime_limits ( ) . backtrace_limit ( ) ;
169+ let backtrace = context. vm . shadow_stack . caller_position ( limit) ;
163170 Self {
164171 tag,
165- position : IgnoreEq ( context . vm . shadow_stack . caller_position ( ) ) ,
172+ location : IgnoreEq :: new ( ErrorLocation :: Backtrace ( backtrace ) ) ,
166173 }
167174 }
168175}
169176
170177impl IntrinsicObject for Error {
171178 fn init ( realm : & Realm ) {
172- let attribute = Attribute :: WRITABLE | Attribute :: NON_ENUMERABLE | Attribute :: CONFIGURABLE ;
179+ let property_attribute =
180+ Attribute :: WRITABLE | Attribute :: NON_ENUMERABLE | Attribute :: CONFIGURABLE ;
181+ let accessor_attribute = Attribute :: NON_ENUMERABLE | Attribute :: CONFIGURABLE ;
182+
183+ let get_stack = BuiltInBuilder :: callable ( realm, Self :: get_stack)
184+ . name ( js_string ! ( "get stack" ) )
185+ . build ( ) ;
186+
187+ let set_stack = BuiltInBuilder :: callable ( realm, Self :: set_stack)
188+ . name ( js_string ! ( "set stack" ) )
189+ . build ( ) ;
190+
173191 let builder = BuiltInBuilder :: from_standard_constructor :: < Self > ( realm)
174- . property ( js_string ! ( "name" ) , Self :: NAME , attribute)
175- . property ( js_string ! ( "message" ) , js_string ! ( ) , attribute)
176- . method ( Self :: to_string, js_string ! ( "toString" ) , 0 ) ;
192+ . property ( js_string ! ( "name" ) , Self :: NAME , property_attribute)
193+ . property ( js_string ! ( "message" ) , js_string ! ( ) , property_attribute)
194+ . method ( Self :: to_string, js_string ! ( "toString" ) , 0 )
195+ . accessor (
196+ js_string ! ( "stack" ) ,
197+ Some ( get_stack) ,
198+ Some ( set_stack) ,
199+ accessor_attribute,
200+ ) ;
177201
178202 #[ cfg( feature = "experimental" ) ]
179203 let builder = builder. static_method ( Error :: is_error, js_string ! ( "isError" ) , 1 ) ;
@@ -192,7 +216,7 @@ impl BuiltInObject for Error {
192216
193217impl BuiltInConstructor for Error {
194218 const CONSTRUCTOR_ARGUMENTS : usize = 1 ;
195- const PROTOTYPE_STORAGE_SLOTS : usize = 3 ;
219+ const PROTOTYPE_STORAGE_SLOTS : usize = 5 ;
196220 const CONSTRUCTOR_STORAGE_SLOTS : usize = 1 ;
197221
198222 const STANDARD_CONSTRUCTOR : fn ( & StandardConstructors ) -> & StandardConstructor =
@@ -263,6 +287,76 @@ impl Error {
263287 Ok ( ( ) )
264288 }
265289
290+ /// `get Error.prototype.stack`
291+ ///
292+ /// The accessor property of Error instances represents the stack trace
293+ /// when the error was created.
294+ ///
295+ /// More information:
296+ /// - [Proposal][spec]
297+ ///
298+ /// [spec]: https://tc39.es/proposal-error-stacks/
299+ fn get_stack ( this : & JsValue , _: & [ JsValue ] , _context : & mut Context ) -> JsResult < JsValue > {
300+ // 1. Let E be the this value.
301+ // 2. If E is not an Object, return undefined.
302+ let Some ( e) = this. as_object ( ) else {
303+ return Ok ( JsValue :: undefined ( ) ) ;
304+ } ;
305+
306+ // 3. Let errorData be the value of the [[ErrorData]] internal slot of E.
307+ // 4. If errorData is undefined, return undefined.
308+ let Some ( error_data) = e. downcast_ref :: < Error > ( ) else {
309+ return Ok ( JsValue :: undefined ( ) ) ;
310+ } ;
311+
312+ // 5. Let stackString be an implementation-defined String value representing the call stack.
313+ // 6. Return stackString.
314+ if let Some ( backtrace) = error_data. location . as_ref ( ) . backtrace ( ) {
315+ let stack_string = backtrace
316+ . iter ( )
317+ . rev ( )
318+ . map ( |entry| format ! ( " at {}\n " , entry. display( true ) ) )
319+ . collect :: < String > ( ) ;
320+ return Ok ( js_string ! ( stack_string) . into ( ) ) ;
321+ }
322+
323+ // 7. If no stack trace is available, return undefined.
324+ Ok ( JsValue :: undefined ( ) )
325+ }
326+
327+ /// `set Error.prototype.stack`
328+ ///
329+ /// The setter for the stack property.
330+ ///
331+ /// More information:
332+ /// - [Proposal][spec]
333+ ///
334+ /// [spec]: https://tc39.es/proposal-error-stacks/
335+ fn set_stack ( this : & JsValue , args : & [ JsValue ] , context : & mut Context ) -> JsResult < JsValue > {
336+ // 1. Let E be the this value.
337+ // 2. If Type(E) is not Object, throw a TypeError exception.
338+ let e = this. as_object ( ) . ok_or_else ( || {
339+ JsNativeError :: typ ( )
340+ . with_message ( "Error.prototype.stack setter requires that 'this' be an Object" )
341+ } ) ?;
342+
343+ // 3. Let numberOfArgs be the number of arguments passed to this function call.
344+ let number_of_args = args. len ( ) ;
345+
346+ // 4. If numberOfArgs is 0, throw a TypeError exception.
347+ if number_of_args == 0 {
348+ return Err ( JsNativeError :: typ ( )
349+ . with_message (
350+ "Error.prototype.stack setter requires at least 1 argument, but only 0 were passed" ,
351+ )
352+ . into ( ) ) ;
353+ }
354+
355+ // 5. Return ? CreateDataPropertyOrThrow(E, "stack", value).
356+ e. create_data_property_or_throw ( js_string ! ( "stack" ) , args[ 0 ] . clone ( ) , context)
357+ . map ( Into :: into)
358+ }
359+
266360 /// `Error.prototype.toString()`
267361 ///
268362 /// The `toString()` method returns a string representing the specified Error object.
0 commit comments