1 | /* |
2 | * Copyright (C) 1999-2000 Harri Porten ([email protected]) |
3 | * Copyright (C) 2003-2019 Apple Inc. All rights reserved. |
4 | * |
5 | * This library is free software; you can redistribute it and/or |
6 | * modify it under the terms of the GNU Lesser General Public |
7 | * License as published by the Free Software Foundation; either |
8 | * version 2 of the License, or (at your option) any later version. |
9 | * |
10 | * This library is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | * Lesser General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU Lesser General Public |
16 | * License along with this library; if not, write to the Free Software |
17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
18 | * |
19 | */ |
20 | |
21 | #include "config.h" |
22 | #include "ErrorInstance.h" |
23 | |
24 | #include "CodeBlock.h" |
25 | #include "InlineCallFrame.h" |
26 | #include "Interpreter.h" |
27 | #include "JSScope.h" |
28 | #include "JSCInlines.h" |
29 | #include "ParseInt.h" |
30 | #include "StackFrame.h" |
31 | #include <wtf/text/StringBuilder.h> |
32 | |
33 | namespace JSC { |
34 | |
35 | const ClassInfo ErrorInstance::s_info = { "Error" , &JSNonFinalObject::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(ErrorInstance) }; |
36 | |
37 | ErrorInstance::ErrorInstance(VM& vm, Structure* structure) |
38 | : Base(vm, structure) |
39 | { |
40 | } |
41 | |
42 | ErrorInstance* ErrorInstance::create(JSGlobalObject* globalObject, Structure* structure, JSValue message, SourceAppender appender, RuntimeType type, bool useCurrentFrame) |
43 | { |
44 | VM& vm = globalObject->vm(); |
45 | auto scope = DECLARE_THROW_SCOPE(vm); |
46 | String messageString = message.isUndefined() ? String() : message.toWTFString(globalObject); |
47 | RETURN_IF_EXCEPTION(scope, nullptr); |
48 | return create(globalObject, vm, structure, messageString, appender, type, useCurrentFrame); |
49 | } |
50 | |
51 | static void appendSourceToError(JSGlobalObject* globalObject, CallFrame* callFrame, ErrorInstance* exception, BytecodeIndex bytecodeIndex) |
52 | { |
53 | ErrorInstance::SourceAppender appender = exception->sourceAppender(); |
54 | exception->clearSourceAppender(); |
55 | RuntimeType type = exception->runtimeTypeForCause(); |
56 | exception->clearRuntimeTypeForCause(); |
57 | |
58 | if (!callFrame->codeBlock()->hasExpressionInfo()) |
59 | return; |
60 | |
61 | int startOffset = 0; |
62 | int endOffset = 0; |
63 | int divotPoint = 0; |
64 | unsigned line = 0; |
65 | unsigned column = 0; |
66 | |
67 | CodeBlock* codeBlock; |
68 | CodeOrigin codeOrigin = callFrame->codeOrigin(); |
69 | if (codeOrigin && codeOrigin.inlineCallFrame()) |
70 | codeBlock = baselineCodeBlockForInlineCallFrame(codeOrigin.inlineCallFrame()); |
71 | else |
72 | codeBlock = callFrame->codeBlock(); |
73 | |
74 | codeBlock->expressionRangeForBytecodeIndex(bytecodeIndex, divotPoint, startOffset, endOffset, line, column); |
75 | |
76 | int expressionStart = divotPoint - startOffset; |
77 | int expressionStop = divotPoint + endOffset; |
78 | |
79 | StringView sourceString = codeBlock->source().provider()->source(); |
80 | if (!expressionStop || expressionStart > static_cast<int>(sourceString.length())) |
81 | return; |
82 | |
83 | VM& vm = globalObject->vm(); |
84 | JSValue jsMessage = exception->getDirect(vm, vm.propertyNames->message); |
85 | if (!jsMessage || !jsMessage.isString()) |
86 | return; |
87 | |
88 | String message = asString(jsMessage)->value(globalObject); |
89 | if (expressionStart < expressionStop) |
90 | message = appender(message, codeBlock->source().provider()->getRange(expressionStart, expressionStop).toString(), type, ErrorInstance::FoundExactSource); |
91 | else { |
92 | // No range information, so give a few characters of context. |
93 | int dataLength = sourceString.length(); |
94 | int start = expressionStart; |
95 | int stop = expressionStart; |
96 | // Get up to 20 characters of context to the left and right of the divot, clamping to the line. |
97 | // Then strip whitespace. |
98 | while (start > 0 && (expressionStart - start < 20) && sourceString[start - 1] != '\n') |
99 | start--; |
100 | while (start < (expressionStart - 1) && isStrWhiteSpace(sourceString[start])) |
101 | start++; |
102 | while (stop < dataLength && (stop - expressionStart < 20) && sourceString[stop] != '\n') |
103 | stop++; |
104 | while (stop > expressionStart && isStrWhiteSpace(sourceString[stop - 1])) |
105 | stop--; |
106 | message = appender(message, codeBlock->source().provider()->getRange(start, stop).toString(), type, ErrorInstance::FoundApproximateSource); |
107 | } |
108 | exception->putDirect(vm, vm.propertyNames->message, jsString(vm, message)); |
109 | |
110 | } |
111 | |
112 | void ErrorInstance::finishCreation(JSGlobalObject* globalObject, VM& vm, const String& message, bool useCurrentFrame) |
113 | { |
114 | Base::finishCreation(vm); |
115 | ASSERT(inherits(vm, info())); |
116 | if (!message.isNull()) |
117 | putDirect(vm, vm.propertyNames->message, jsString(vm, message), static_cast<unsigned>(PropertyAttribute::DontEnum)); |
118 | |
119 | std::unique_ptr<Vector<StackFrame>> stackTrace = getStackTrace(globalObject, vm, this, useCurrentFrame); |
120 | { |
121 | auto locker = holdLock(cellLock()); |
122 | m_stackTrace = WTFMove(stackTrace); |
123 | } |
124 | vm.heap.writeBarrier(this); |
125 | |
126 | if (m_stackTrace && !m_stackTrace->isEmpty() && hasSourceAppender()) { |
127 | BytecodeIndex bytecodeIndex; |
128 | CallFrame* callFrame; |
129 | getBytecodeIndex(vm, vm.topCallFrame, m_stackTrace.get(), callFrame, bytecodeIndex); |
130 | if (callFrame && callFrame->codeBlock() && !callFrame->callee().isWasm()) |
131 | appendSourceToError(globalObject, callFrame, this, bytecodeIndex); |
132 | } |
133 | } |
134 | |
135 | void ErrorInstance::destroy(JSCell* cell) |
136 | { |
137 | static_cast<ErrorInstance*>(cell)->ErrorInstance::~ErrorInstance(); |
138 | } |
139 | |
140 | // Based on ErrorPrototype's errorProtoFuncToString(), but is modified to |
141 | // have no observable side effects to the user (i.e. does not call proxies, |
142 | // and getters). |
143 | String ErrorInstance::sanitizedToString(JSGlobalObject* globalObject) |
144 | { |
145 | VM& vm = globalObject->vm(); |
146 | auto scope = DECLARE_THROW_SCOPE(vm); |
147 | |
148 | JSValue nameValue; |
149 | auto namePropertName = vm.propertyNames->name; |
150 | PropertySlot nameSlot(this, PropertySlot::InternalMethodType::VMInquiry); |
151 | |
152 | JSValue currentObj = this; |
153 | unsigned prototypeDepth = 0; |
154 | |
155 | // We only check the current object and its prototype (2 levels) because normal |
156 | // Error objects may have a name property, and if not, its prototype should have |
157 | // a name property for the type of error e.g. "SyntaxError". |
158 | while (currentObj.isCell() && prototypeDepth++ < 2) { |
159 | JSObject* obj = jsCast<JSObject*>(currentObj); |
160 | if (JSObject::getOwnPropertySlot(obj, globalObject, namePropertName, nameSlot) && nameSlot.isValue()) { |
161 | nameValue = nameSlot.getValue(globalObject, namePropertName); |
162 | break; |
163 | } |
164 | currentObj = obj->getPrototypeDirect(vm); |
165 | } |
166 | scope.assertNoException(); |
167 | |
168 | String nameString; |
169 | if (!nameValue) |
170 | nameString = "Error"_s ; |
171 | else { |
172 | nameString = nameValue.toWTFString(globalObject); |
173 | RETURN_IF_EXCEPTION(scope, String()); |
174 | } |
175 | |
176 | JSValue messageValue; |
177 | auto messagePropertName = vm.propertyNames->message; |
178 | PropertySlot messageSlot(this, PropertySlot::InternalMethodType::VMInquiry); |
179 | if (JSObject::getOwnPropertySlot(this, globalObject, messagePropertName, messageSlot) && messageSlot.isValue()) |
180 | messageValue = messageSlot.getValue(globalObject, messagePropertName); |
181 | scope.assertNoException(); |
182 | |
183 | String messageString; |
184 | if (!messageValue) |
185 | messageString = String(); |
186 | else { |
187 | messageString = messageValue.toWTFString(globalObject); |
188 | RETURN_IF_EXCEPTION(scope, String()); |
189 | } |
190 | |
191 | if (!nameString.length()) |
192 | return messageString; |
193 | |
194 | if (!messageString.length()) |
195 | return nameString; |
196 | |
197 | StringBuilder builder; |
198 | builder.append(nameString); |
199 | builder.appendLiteral(": " ); |
200 | builder.append(messageString); |
201 | return builder.toString(); |
202 | } |
203 | |
204 | void ErrorInstance::finalizeUnconditionally(VM& vm) |
205 | { |
206 | if (!m_stackTrace) |
207 | return; |
208 | |
209 | // We don't want to keep our stack traces alive forever if the user doesn't access the stack trace. |
210 | // If we did, we might end up keeping functions (and their global objects) alive that happened to |
211 | // get caught in a trace. |
212 | for (const auto& frame : *m_stackTrace.get()) { |
213 | if (!frame.isMarked(vm)) { |
214 | computeErrorInfo(vm); |
215 | return; |
216 | } |
217 | } |
218 | } |
219 | |
220 | void ErrorInstance::computeErrorInfo(VM& vm) |
221 | { |
222 | ASSERT(!m_errorInfoMaterialized); |
223 | |
224 | if (m_stackTrace && !m_stackTrace->isEmpty()) { |
225 | getLineColumnAndSource(m_stackTrace.get(), m_line, m_column, m_sourceURL); |
226 | m_stackString = Interpreter::stackTraceAsString(vm, *m_stackTrace.get()); |
227 | m_stackTrace = nullptr; |
228 | } |
229 | } |
230 | |
231 | bool ErrorInstance::materializeErrorInfoIfNeeded(VM& vm) |
232 | { |
233 | if (m_errorInfoMaterialized) |
234 | return false; |
235 | |
236 | computeErrorInfo(vm); |
237 | |
238 | if (!m_stackString.isNull()) { |
239 | auto attributes = static_cast<unsigned>(PropertyAttribute::DontEnum); |
240 | |
241 | putDirect(vm, vm.propertyNames->line, jsNumber(m_line), attributes); |
242 | putDirect(vm, vm.propertyNames->column, jsNumber(m_column), attributes); |
243 | if (!m_sourceURL.isEmpty()) |
244 | putDirect(vm, vm.propertyNames->sourceURL, jsString(vm, WTFMove(m_sourceURL)), attributes); |
245 | |
246 | putDirect(vm, vm.propertyNames->stack, jsString(vm, WTFMove(m_stackString)), attributes); |
247 | } |
248 | |
249 | m_errorInfoMaterialized = true; |
250 | return true; |
251 | } |
252 | |
253 | bool ErrorInstance::materializeErrorInfoIfNeeded(VM& vm, PropertyName propertyName) |
254 | { |
255 | if (propertyName == vm.propertyNames->line |
256 | || propertyName == vm.propertyNames->column |
257 | || propertyName == vm.propertyNames->sourceURL |
258 | || propertyName == vm.propertyNames->stack) |
259 | return materializeErrorInfoIfNeeded(vm); |
260 | return false; |
261 | } |
262 | |
263 | bool ErrorInstance::getOwnPropertySlot(JSObject* object, JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot) |
264 | { |
265 | VM& vm = globalObject->vm(); |
266 | ErrorInstance* thisObject = jsCast<ErrorInstance*>(object); |
267 | thisObject->materializeErrorInfoIfNeeded(vm, propertyName); |
268 | return Base::getOwnPropertySlot(thisObject, globalObject, propertyName, slot); |
269 | } |
270 | |
271 | void ErrorInstance::getOwnNonIndexPropertyNames(JSObject* object, JSGlobalObject* globalObject, PropertyNameArray& propertyNameArray, EnumerationMode enumerationMode) |
272 | { |
273 | VM& vm = globalObject->vm(); |
274 | ErrorInstance* thisObject = jsCast<ErrorInstance*>(object); |
275 | thisObject->materializeErrorInfoIfNeeded(vm); |
276 | Base::getOwnNonIndexPropertyNames(thisObject, globalObject, propertyNameArray, enumerationMode); |
277 | } |
278 | |
279 | void ErrorInstance::getStructurePropertyNames(JSObject* object, JSGlobalObject* globalObject, PropertyNameArray& propertyNameArray, EnumerationMode enumerationMode) |
280 | { |
281 | VM& vm = globalObject->vm(); |
282 | ErrorInstance* thisObject = jsCast<ErrorInstance*>(object); |
283 | thisObject->materializeErrorInfoIfNeeded(vm); |
284 | Base::getStructurePropertyNames(thisObject, globalObject, propertyNameArray, enumerationMode); |
285 | } |
286 | |
287 | bool ErrorInstance::defineOwnProperty(JSObject* object, JSGlobalObject* globalObject, PropertyName propertyName, const PropertyDescriptor& descriptor, bool shouldThrow) |
288 | { |
289 | VM& vm = globalObject->vm(); |
290 | ErrorInstance* thisObject = jsCast<ErrorInstance*>(object); |
291 | thisObject->materializeErrorInfoIfNeeded(vm, propertyName); |
292 | return Base::defineOwnProperty(thisObject, globalObject, propertyName, descriptor, shouldThrow); |
293 | } |
294 | |
295 | bool ErrorInstance::put(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot) |
296 | { |
297 | VM& vm = globalObject->vm(); |
298 | ErrorInstance* thisObject = jsCast<ErrorInstance*>(cell); |
299 | bool materializedProperties = thisObject->materializeErrorInfoIfNeeded(vm, propertyName); |
300 | if (materializedProperties) |
301 | slot.disableCaching(); |
302 | return Base::put(thisObject, globalObject, propertyName, value, slot); |
303 | } |
304 | |
305 | bool ErrorInstance::deleteProperty(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName) |
306 | { |
307 | VM& vm = globalObject->vm(); |
308 | ErrorInstance* thisObject = jsCast<ErrorInstance*>(cell); |
309 | thisObject->materializeErrorInfoIfNeeded(vm, propertyName); |
310 | return Base::deleteProperty(thisObject, globalObject, propertyName); |
311 | } |
312 | |
313 | } // namespace JSC |
314 | |