Coverage Report

Created: 2025-09-05 06:52

/src/serenity/Userland/Libraries/LibJS/Runtime/Error.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
3
 * Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
4
 *
5
 * SPDX-License-Identifier: BSD-2-Clause
6
 */
7
8
#include <AK/StringBuilder.h>
9
#include <LibJS/AST.h>
10
#include <LibJS/Runtime/Completion.h>
11
#include <LibJS/Runtime/Error.h>
12
#include <LibJS/Runtime/ExecutionContext.h>
13
#include <LibJS/Runtime/GlobalObject.h>
14
#include <LibJS/SourceRange.h>
15
16
namespace JS {
17
18
JS_DEFINE_ALLOCATOR(Error);
19
20
SourceRange const& TracebackFrame::source_range() const
21
0
{
22
0
    if (auto* unrealized = source_range_storage.get_pointer<UnrealizedSourceRange>()) {
23
0
        auto source_range = [&] {
24
0
            if (!unrealized->source_code) {
25
0
                static auto dummy_source_code = SourceCode::create(String {}, String {});
26
0
                return SourceRange { dummy_source_code, {}, {} };
27
0
            }
28
0
            return unrealized->realize();
29
0
        }();
30
0
        source_range_storage = move(source_range);
31
0
    }
32
0
    return source_range_storage.get<SourceRange>();
33
0
}
34
35
NonnullGCPtr<Error> Error::create(Realm& realm)
36
0
{
37
0
    return realm.heap().allocate<Error>(realm, realm.intrinsics().error_prototype());
38
0
}
39
40
NonnullGCPtr<Error> Error::create(Realm& realm, String message)
41
0
{
42
0
    auto& vm = realm.vm();
43
0
    auto error = Error::create(realm);
44
0
    u8 attr = Attribute::Writable | Attribute::Configurable;
45
0
    error->define_direct_property(vm.names.message, PrimitiveString::create(vm, move(message)), attr);
46
0
    return error;
47
0
}
48
49
NonnullGCPtr<Error> Error::create(Realm& realm, StringView message)
50
0
{
51
0
    return create(realm, MUST(String::from_utf8(message)));
52
0
}
53
54
Error::Error(Object& prototype)
55
9
    : Object(ConstructWithPrototypeTag::Tag, prototype)
56
9
{
57
9
    populate_stack();
58
9
}
59
60
// 20.5.8.1 InstallErrorCause ( O, options ), https://tc39.es/ecma262/#sec-installerrorcause
61
ThrowCompletionOr<void> Error::install_error_cause(Value options)
62
0
{
63
0
    auto& vm = this->vm();
64
65
    // 1. If Type(options) is Object and ? HasProperty(options, "cause") is true, then
66
0
    if (options.is_object() && TRY(options.as_object().has_property(vm.names.cause))) {
67
        // a. Let cause be ? Get(options, "cause").
68
0
        auto cause = TRY(options.as_object().get(vm.names.cause));
69
70
        // b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "cause", cause).
71
0
        create_non_enumerable_data_property_or_throw(vm.names.cause, cause);
72
0
    }
73
74
    // 2. Return unused.
75
0
    return {};
76
0
}
77
78
void Error::populate_stack()
79
9
{
80
9
    auto stack_trace = vm().stack_trace();
81
9
    m_traceback.ensure_capacity(stack_trace.size());
82
18
    for (auto& element : stack_trace) {
83
18
        auto* context = element.execution_context;
84
18
        UnrealizedSourceRange range = {};
85
18
        if (element.source_range.has_value())
86
18
            range = element.source_range.value();
87
18
        TracebackFrame frame {
88
18
            .function_name = context->function_name ? context->function_name->byte_string() : "",
89
18
            .source_range_storage = range,
90
18
        };
91
92
18
        m_traceback.append(move(frame));
93
18
    }
94
9
}
95
96
String Error::stack_string(CompactTraceback compact) const
97
0
{
98
0
    if (m_traceback.is_empty())
99
0
        return {};
100
101
0
    StringBuilder stack_string_builder;
102
103
    // Note: We roughly follow V8's formatting
104
0
    auto append_frame = [&](TracebackFrame const& frame) {
105
0
        auto function_name = frame.function_name;
106
0
        auto source_range = frame.source_range();
107
        // Note: Since we don't know whether we have a valid SourceRange here we just check for some default values.
108
0
        if (!source_range.filename().is_empty() || source_range.start.offset != 0 || source_range.end.offset != 0) {
109
110
0
            if (function_name.is_empty())
111
0
                stack_string_builder.appendff("    at {}:{}:{}\n", source_range.filename(), source_range.start.line, source_range.start.column);
112
0
            else
113
0
                stack_string_builder.appendff("    at {} ({}:{}:{})\n", function_name, source_range.filename(), source_range.start.line, source_range.start.column);
114
0
        } else {
115
0
            stack_string_builder.appendff("    at {}\n", function_name.is_empty() ? "<unknown>"sv : function_name.view());
116
0
        }
117
0
    };
118
119
0
    auto is_same_frame = [](TracebackFrame const& a, TracebackFrame const& b) {
120
0
        if (a.function_name.is_empty() && b.function_name.is_empty()) {
121
0
            auto source_range_a = a.source_range();
122
0
            auto source_range_b = b.source_range();
123
0
            return source_range_a.filename() == source_range_b.filename() && source_range_a.start.line == source_range_b.start.line;
124
0
        }
125
0
        return a.function_name == b.function_name;
126
0
    };
127
128
    // Note: We don't want to capture the global execution context, so we omit the last frame
129
    // Note: The error's name and message get prepended by ErrorPrototype::stack
130
    // FIXME: We generate a stack-frame for the Errors constructor, other engines do not
131
0
    unsigned repetitions = 0;
132
0
    size_t used_frames = m_traceback.size() - 1;
133
0
    for (size_t i = 0; i < used_frames; ++i) {
134
0
        auto const& frame = m_traceback[i];
135
0
        if (compact == CompactTraceback::Yes && i + 1 < used_frames) {
136
0
            auto const& next_traceback_frame = m_traceback[i + 1];
137
0
            if (is_same_frame(frame, next_traceback_frame)) {
138
0
                repetitions++;
139
0
                continue;
140
0
            }
141
0
        }
142
0
        if (repetitions > 4) {
143
            // If more than 5 (1 + >4) consecutive function calls with the same name, print
144
            // the name only once and show the number of repetitions instead. This prevents
145
            // printing ridiculously large call stacks of recursive functions.
146
0
            append_frame(frame);
147
0
            stack_string_builder.appendff("    {} more calls\n", repetitions);
148
0
        } else {
149
0
            for (size_t j = 0; j < repetitions + 1; j++)
150
0
                append_frame(frame);
151
0
        }
152
0
        repetitions = 0;
153
0
    }
154
0
    for (size_t j = 0; j < repetitions; j++)
155
0
        append_frame(m_traceback[used_frames - 1]);
156
157
0
    return MUST(stack_string_builder.to_string());
158
0
}
159
160
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType)                   \
161
    JS_DEFINE_ALLOCATOR(ClassName);                                                                        \
162
    NonnullGCPtr<ClassName> ClassName::create(Realm& realm)                                                \
163
9
    {                                                                                                      \
164
9
        return realm.heap().allocate<ClassName>(realm, realm.intrinsics().snake_name##_prototype());       \
165
9
    }                                                                                                      \
Unexecuted instantiation: JS::EvalError::create(JS::Realm&)
Unexecuted instantiation: JS::InternalError::create(JS::Realm&)
Unexecuted instantiation: JS::RangeError::create(JS::Realm&)
JS::ReferenceError::create(JS::Realm&)
Line
Count
Source
163
9
    {                                                                                                      \
164
9
        return realm.heap().allocate<ClassName>(realm, realm.intrinsics().snake_name##_prototype());       \
165
9
    }                                                                                                      \
Unexecuted instantiation: JS::SyntaxError::create(JS::Realm&)
Unexecuted instantiation: JS::TypeError::create(JS::Realm&)
Unexecuted instantiation: JS::URIError::create(JS::Realm&)
166
                                                                                                           \
167
    NonnullGCPtr<ClassName> ClassName::create(Realm& realm, String message)                                \
168
9
    {                                                                                                      \
169
9
        auto& vm = realm.vm();                                                                             \
170
9
        auto error = ClassName::create(realm);                                                             \
171
9
        u8 attr = Attribute::Writable | Attribute::Configurable;                                           \
172
9
        error->define_direct_property(vm.names.message, PrimitiveString::create(vm, move(message)), attr); \
173
9
        return error;                                                                                      \
174
9
    }                                                                                                      \
Unexecuted instantiation: JS::EvalError::create(JS::Realm&, AK::String)
Unexecuted instantiation: JS::InternalError::create(JS::Realm&, AK::String)
Unexecuted instantiation: JS::RangeError::create(JS::Realm&, AK::String)
JS::ReferenceError::create(JS::Realm&, AK::String)
Line
Count
Source
168
9
    {                                                                                                      \
169
9
        auto& vm = realm.vm();                                                                             \
170
9
        auto error = ClassName::create(realm);                                                             \
171
9
        u8 attr = Attribute::Writable | Attribute::Configurable;                                           \
172
9
        error->define_direct_property(vm.names.message, PrimitiveString::create(vm, move(message)), attr); \
173
9
        return error;                                                                                      \
174
9
    }                                                                                                      \
Unexecuted instantiation: JS::SyntaxError::create(JS::Realm&, AK::String)
Unexecuted instantiation: JS::TypeError::create(JS::Realm&, AK::String)
Unexecuted instantiation: JS::URIError::create(JS::Realm&, AK::String)
175
                                                                                                           \
176
    NonnullGCPtr<ClassName> ClassName::create(Realm& realm, StringView message)                            \
177
0
    {                                                                                                      \
178
0
        return create(realm, MUST(String::from_utf8(message)));                                            \
179
0
    }                                                                                                      \
Unexecuted instantiation: JS::EvalError::create(JS::Realm&, AK::StringView)
Unexecuted instantiation: JS::InternalError::create(JS::Realm&, AK::StringView)
Unexecuted instantiation: JS::RangeError::create(JS::Realm&, AK::StringView)
Unexecuted instantiation: JS::ReferenceError::create(JS::Realm&, AK::StringView)
Unexecuted instantiation: JS::SyntaxError::create(JS::Realm&, AK::StringView)
Unexecuted instantiation: JS::TypeError::create(JS::Realm&, AK::StringView)
Unexecuted instantiation: JS::URIError::create(JS::Realm&, AK::StringView)
180
                                                                                                           \
181
    ClassName::ClassName(Object& prototype)                                                                \
182
9
        : Error(prototype)                                                                                 \
183
9
    {                                                                                                      \
184
9
    }
Unexecuted instantiation: JS::EvalError::EvalError(JS::Object&)
Unexecuted instantiation: JS::InternalError::InternalError(JS::Object&)
Unexecuted instantiation: JS::RangeError::RangeError(JS::Object&)
JS::ReferenceError::ReferenceError(JS::Object&)
Line
Count
Source
182
9
        : Error(prototype)                                                                                 \
183
9
    {                                                                                                      \
184
9
    }
Unexecuted instantiation: JS::SyntaxError::SyntaxError(JS::Object&)
Unexecuted instantiation: JS::TypeError::TypeError(JS::Object&)
Unexecuted instantiation: JS::URIError::URIError(JS::Object&)
185
186
JS_ENUMERATE_NATIVE_ERRORS
187
#undef __JS_ENUMERATE
188
189
}