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,7 @@ use crate::{
2022 property:: Attribute ,
2123 realm:: Realm ,
2224 string:: StaticJsStrings ,
23- vm:: shadow_stack:: ShadowEntry ,
25+ vm:: shadow_stack:: ErrorStack ,
2426} ;
2527use boa_gc:: { Finalize , Trace } ;
2628use boa_macros:: js_str;
@@ -136,7 +138,7 @@ pub struct Error {
136138
137139 // The position of where the Error was created does not affect equality check.
138140 #[ unsafe_ignore_trace]
139- pub ( crate ) position : IgnoreEq < Option < ShadowEntry > > ,
141+ pub ( crate ) stack : IgnoreEq < ErrorStack > ,
140142}
141143
142144impl Error {
@@ -146,34 +148,53 @@ impl Error {
146148 pub fn new ( tag : ErrorKind ) -> Self {
147149 Self {
148150 tag,
149- position : IgnoreEq ( None ) ,
151+ stack : IgnoreEq ( ErrorStack :: Position ( None ) ) ,
150152 }
151153 }
152154
153- /// Create a new [`Error`] with the given optional [`ShadowEntry `].
154- pub ( crate ) fn with_shadow_entry ( tag : ErrorKind , entry : Option < ShadowEntry > ) -> Self {
155+ /// Create a new [`Error`] with the given [`Stack `].
156+ pub ( crate ) fn with_stack ( tag : ErrorKind , location : ErrorStack ) -> Self {
155157 Self {
156158 tag,
157- position : IgnoreEq ( entry ) ,
159+ stack : IgnoreEq ( location ) ,
158160 }
159161 }
160162
161163 /// Get the position from the last called function.
162164 pub ( crate ) fn with_caller_position ( tag : ErrorKind , context : & Context ) -> Self {
165+ let limit = context. runtime_limits ( ) . backtrace_limit ( ) ;
166+ let backtrace = context. vm . shadow_stack . caller_position ( limit) ;
163167 Self {
164168 tag,
165- position : IgnoreEq ( context . vm . shadow_stack . caller_position ( ) ) ,
169+ stack : IgnoreEq ( ErrorStack :: Backtrace ( backtrace ) ) ,
166170 }
167171 }
168172}
169173
170174impl IntrinsicObject for Error {
171175 fn init ( realm : & Realm ) {
172- let attribute = Attribute :: WRITABLE | Attribute :: NON_ENUMERABLE | Attribute :: CONFIGURABLE ;
176+ let property_attribute =
177+ Attribute :: WRITABLE | Attribute :: NON_ENUMERABLE | Attribute :: CONFIGURABLE ;
178+ let accessor_attribute = Attribute :: NON_ENUMERABLE | Attribute :: CONFIGURABLE ;
179+
180+ let get_stack = BuiltInBuilder :: callable ( realm, Self :: get_stack)
181+ . name ( js_string ! ( "get stack" ) )
182+ . build ( ) ;
183+
184+ let set_stack = BuiltInBuilder :: callable ( realm, Self :: set_stack)
185+ . name ( js_string ! ( "set stack" ) )
186+ . build ( ) ;
187+
173188 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 ) ;
189+ . property ( js_string ! ( "name" ) , Self :: NAME , property_attribute)
190+ . property ( js_string ! ( "message" ) , js_string ! ( ) , property_attribute)
191+ . method ( Self :: to_string, js_string ! ( "toString" ) , 0 )
192+ . accessor (
193+ js_string ! ( "stack" ) ,
194+ Some ( get_stack) ,
195+ Some ( set_stack) ,
196+ accessor_attribute,
197+ ) ;
177198
178199 #[ cfg( feature = "experimental" ) ]
179200 let builder = builder. static_method ( Error :: is_error, js_string ! ( "isError" ) , 1 ) ;
@@ -192,7 +213,7 @@ impl BuiltInObject for Error {
192213
193214impl BuiltInConstructor for Error {
194215 const CONSTRUCTOR_ARGUMENTS : usize = 1 ;
195- const PROTOTYPE_STORAGE_SLOTS : usize = 3 ;
216+ const PROTOTYPE_STORAGE_SLOTS : usize = 5 ;
196217 const CONSTRUCTOR_STORAGE_SLOTS : usize = 1 ;
197218
198219 const STANDARD_CONSTRUCTOR : fn ( & StandardConstructors ) -> & StandardConstructor =
@@ -263,6 +284,77 @@ impl Error {
263284 Ok ( ( ) )
264285 }
265286
287+ /// `get Error.prototype.stack`
288+ ///
289+ /// The accessor property of Error instances represents the stack trace
290+ /// when the error was created.
291+ ///
292+ /// More information:
293+ /// - [Proposal][spec]
294+ ///
295+ /// [spec]: https://tc39.es/proposal-error-stacks/
296+ #[ allow( clippy:: unnecessary_wraps) ]
297+ fn get_stack ( this : & JsValue , _: & [ JsValue ] , _context : & mut Context ) -> JsResult < JsValue > {
298+ // 1. Let E be the this value.
299+ // 2. If E is not an Object, return undefined.
300+ let Some ( e) = this. as_object ( ) else {
301+ return Ok ( JsValue :: undefined ( ) ) ;
302+ } ;
303+
304+ // 3. Let errorData be the value of the [[ErrorData]] internal slot of E.
305+ // 4. If errorData is undefined, return undefined.
306+ let Some ( error_data) = e. downcast_ref :: < Error > ( ) else {
307+ return Ok ( JsValue :: undefined ( ) ) ;
308+ } ;
309+
310+ // 5. Let stackString be an implementation-defined String value representing the call stack.
311+ // 6. Return stackString.
312+ if let Some ( backtrace) = error_data. stack . 0 . backtrace ( ) {
313+ let stack_string = backtrace
314+ . iter ( )
315+ . rev ( )
316+ . fold ( String :: new ( ) , |mut output, entry| {
317+ let _ = writeln ! ( & mut output, " at {}" , entry. display( true ) ) ;
318+ output
319+ } ) ;
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+ // 4. If numberOfArgs is 0, throw a TypeError exception.
345+ let Some ( value) = args. first ( ) else {
346+ return Err ( JsNativeError :: typ ( )
347+ . with_message (
348+ "Error.prototype.stack setter requires at least 1 argument, but only 0 were passed" ,
349+ )
350+ . into ( ) ) ;
351+ } ;
352+
353+ // 5. Return ? CreateDataPropertyOrThrow(E, "stack", value).
354+ e. create_data_property_or_throw ( js_string ! ( "stack" ) , value. clone ( ) , context)
355+ . map ( Into :: into)
356+ }
357+
266358 /// `Error.prototype.toString()`
267359 ///
268360 /// The `toString()` method returns a string representing the specified Error object.
0 commit comments