2026-04-04 10:39:01 -03:00
/*
* Copyright ( c ) 2020 - 2025 , Andreas Kling < andreas @ ladybird . org >
* Copyright ( c ) 2021 - 2023 , Linus Groh < linusg @ serenityos . org >
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2026-06-04 05:40:32 -03:00
# include <AK/NeverDestroyed.h>
2026-06-21 20:59:18 -03:00
# include <AK/Utf16StringBuilder.h>
2026-04-04 10:39:01 -03:00
# include <LibJS/Runtime/ErrorData.h>
# include <LibJS/Runtime/ExecutionContext.h>
2026-05-05 19:19:35 -03:00
# include <LibJS/Runtime/ExternalMemory.h>
2026-04-04 10:39:01 -03:00
# include <LibJS/Runtime/FunctionObject.h>
# include <LibJS/Runtime/PrimitiveString.h>
# include <LibJS/Runtime/VM.h>
namespace JS {
2026-06-04 05:40:32 -03:00
static auto & dummy_source_range ( )
{
static NeverDestroyed < SourceRange > source_range { SourceRange { SourceCode : : create ( { } , Utf16String { } ) , { } } } ;
return * source_range ;
}
2026-04-04 10:39:01 -03:00
SourceRange const & TracebackFrame : : source_range ( ) const
{
if ( ! cached_source_range . has_value ( ) )
2026-06-04 05:40:32 -03:00
return dummy_source_range ( ) ;
2026-04-04 10:39:01 -03:00
return * cached_source_range ;
}
ErrorData : : ErrorData ( VM & vm )
{
populate_stack ( vm ) ;
}
void ErrorData : : visit_edges ( Cell : : Visitor & visitor )
{
visitor . visit ( m_cached_string ) ;
}
2026-05-05 19:19:35 -03:00
size_t ErrorData : : external_memory_size ( ) const
{
size_t size = vector_external_memory_size ( m_traceback ) ;
for ( auto const & frame : m_traceback )
size = saturating_add_external_memory_size ( size , utf16_string_external_memory_size ( frame . function_name ) ) ;
return size ;
}
2026-04-04 10:39:01 -03:00
void ErrorData : : populate_stack ( VM & vm )
{
auto stack_trace = vm . stack_trace ( ) ;
m_traceback . ensure_capacity ( stack_trace . size ( ) ) ;
for ( auto & element : stack_trace ) {
auto * context = element . execution_context ;
m_traceback . append ( {
. function_name = context - > function ? context - > function - > name_for_call_stack ( ) : " " _utf16 ,
. cached_source_range = move ( element . source_range ) ,
} ) ;
}
}
Utf16String ErrorData : : stack_string ( CompactTraceback compact ) const
{
if ( m_traceback . is_empty ( ) )
return { } ;
2026-06-21 20:59:18 -03:00
Utf16StringBuilder stack_string_builder ;
2026-04-04 10:39:01 -03:00
// Note: We roughly follow V8's formatting
auto append_frame = [ & ] ( TracebackFrame const & frame ) {
auto const & function_name = frame . function_name ;
auto const & source_range = frame . source_range ( ) ;
// Note: Since we don't know whether we have a valid SourceRange here we just check for some default values.
2026-05-14 03:29:39 -03:00
if ( ! source_range . filename ( ) . is_empty ( ) | | source_range . start . line ! = 0 | | source_range . start . column ! = 0 ) {
2026-04-04 10:39:01 -03:00
if ( function_name . is_empty ( ) )
2026-06-21 20:59:18 -03:00
stack_string_builder . append ( Utf16String : : formatted ( " at {}:{}:{} \n " , source_range . filename ( ) , source_range . start . line , source_range . start . column ) ) ;
2026-04-04 10:39:01 -03:00
else
2026-06-21 20:59:18 -03:00
stack_string_builder . append ( Utf16String : : formatted ( " at {} ({}:{}:{}) \n " , function_name , source_range . filename ( ) , source_range . start . line , source_range . start . column ) ) ;
2026-04-04 10:39:01 -03:00
} else {
2026-06-21 20:59:18 -03:00
stack_string_builder . append ( Utf16String : : formatted ( " at {} \n " , function_name . is_empty ( ) ? " <unknown> " _utf16 : function_name ) ) ;
2026-04-04 10:39:01 -03:00
}
} ;
auto is_same_frame = [ ] ( TracebackFrame const & a , TracebackFrame const & b ) {
if ( a . function_name . is_empty ( ) & & b . function_name . is_empty ( ) ) {
auto const & source_range_a = a . source_range ( ) ;
auto const & source_range_b = b . source_range ( ) ;
return source_range_a . filename ( ) = = source_range_b . filename ( ) & & source_range_a . start . line = = source_range_b . start . line ;
}
return a . function_name = = b . function_name ;
} ;
// Note: We don't want to capture the global execution context, so we omit the last frame
// Note: The error's name and message get prepended by Error.prototype.stack
unsigned repetitions = 0 ;
size_t used_frames = m_traceback . size ( ) - 1 ;
for ( size_t i = 0 ; i < used_frames ; + + i ) {
auto const & frame = m_traceback [ i ] ;
if ( compact = = CompactTraceback : : Yes & & i + 1 < used_frames ) {
auto const & next_traceback_frame = m_traceback [ i + 1 ] ;
if ( is_same_frame ( frame , next_traceback_frame ) ) {
repetitions + + ;
continue ;
}
}
if ( repetitions > 4 ) {
// If more than 5 (1 + >4) consecutive function calls with the same name, print
// the name only once and show the number of repetitions instead. This prevents
// printing ridiculously large call stacks of recursive functions.
append_frame ( frame ) ;
2026-06-21 20:59:18 -03:00
stack_string_builder . append ( Utf16String : : formatted ( " {} more calls \n " , repetitions ) ) ;
2026-04-04 10:39:01 -03:00
} else {
for ( size_t j = 0 ; j < repetitions + 1 ; j + + )
append_frame ( frame ) ;
}
repetitions = 0 ;
}
for ( size_t j = 0 ; j < repetitions ; j + + )
append_frame ( m_traceback [ used_frames - 1 ] ) ;
2026-06-21 20:59:18 -03:00
return stack_string_builder . to_string ( ) ;
2026-04-04 10:39:01 -03:00
}
}