1/*
2 * Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
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
33namespace JSC {
34
35const ClassInfo ErrorInstance::s_info = { "Error", &JSNonFinalObject::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(ErrorInstance) };
36
37ErrorInstance::ErrorInstance(VM& vm, Structure* structure)
38 : Base(vm, structure)
39{
40}
41
42ErrorInstance* 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
51static 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
112void 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
135void 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).
143String 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
204void 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
220void 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
231bool 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
253bool 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
263bool 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
271void 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
279void 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
287bool 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
295bool 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
305bool 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