/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 | | } |