1/*
2 * Copyright (C) 1999-2000 Harri Porten ([email protected])
3 * Copyright (C) 2003-2017 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(ExecState* state, Structure* structure, JSValue message, SourceAppender appender, RuntimeType type, bool useCurrentFrame)
43{
44 VM& vm = state->vm();
45 auto scope = DECLARE_THROW_SCOPE(vm);
46 String messageString = message.isUndefined() ? String() : message.toWTFString(state);
47 RETURN_IF_EXCEPTION(scope, nullptr);
48 return create(state, vm, structure, messageString, appender, type, useCurrentFrame);
49}
50
51static void appendSourceToError(CallFrame* callFrame, ErrorInstance* exception, unsigned bytecodeOffset)
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->expressionRangeForBytecodeOffset(bytecodeOffset, 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 = &callFrame->vm();
84 JSValue jsMessage = exception->getDirect(*vm, vm->propertyNames->message);
85 if (!jsMessage || !jsMessage.isString())
86 return;
87
88 String message = asString(jsMessage)->value(callFrame);
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(ExecState* exec, 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(exec, 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 unsigned bytecodeOffset;
128 CallFrame* callFrame;
129 getBytecodeOffset(exec, vm, m_stackTrace.get(), callFrame, bytecodeOffset);
130 if (callFrame && callFrame->codeBlock()) {
131 ASSERT(!callFrame->callee().isWasm());
132 appendSourceToError(callFrame, this, bytecodeOffset);
133 }
134 }
135}
136
137void ErrorInstance::destroy(JSCell* cell)
138{
139 static_cast<ErrorInstance*>(cell)->ErrorInstance::~ErrorInstance();
140}
141
142// Based on ErrorPrototype's errorProtoFuncToString(), but is modified to
143// have no observable side effects to the user (i.e. does not call proxies,
144// and getters).
145String ErrorInstance::sanitizedToString(ExecState* exec)
146{
147 VM& vm = exec->vm();
148 auto scope = DECLARE_THROW_SCOPE(vm);
149
150 JSValue nameValue;
151 auto namePropertName = vm.propertyNames->name;
152 PropertySlot nameSlot(this, PropertySlot::InternalMethodType::VMInquiry);
153
154 JSValue currentObj = this;
155 unsigned prototypeDepth = 0;
156
157 // We only check the current object and its prototype (2 levels) because normal
158 // Error objects may have a name property, and if not, its prototype should have
159 // a name property for the type of error e.g. "SyntaxError".
160 while (currentObj.isCell() && prototypeDepth++ < 2) {
161 JSObject* obj = jsCast<JSObject*>(currentObj);
162 if (JSObject::getOwnPropertySlot(obj, exec, namePropertName, nameSlot) && nameSlot.isValue()) {
163 nameValue = nameSlot.getValue(exec, namePropertName);
164 break;
165 }
166 currentObj = obj->getPrototypeDirect(vm);
167 }
168 scope.assertNoException();
169
170 String nameString;
171 if (!nameValue)
172 nameString = "Error"_s;
173 else {
174 nameString = nameValue.toWTFString(exec);
175 RETURN_IF_EXCEPTION(scope, String());
176 }
177
178 JSValue messageValue;
179 auto messagePropertName = vm.propertyNames->message;
180 PropertySlot messageSlot(this, PropertySlot::InternalMethodType::VMInquiry);
181 if (JSObject::getOwnPropertySlot(this, exec, messagePropertName, messageSlot) && messageSlot.isValue())
182 messageValue = messageSlot.getValue(exec, messagePropertName);
183 scope.assertNoException();
184
185 String messageString;
186 if (!messageValue)
187 messageString = String();
188 else {
189 messageString = messageValue.toWTFString(exec);
190 RETURN_IF_EXCEPTION(scope, String());
191 }
192
193 if (!nameString.length())
194 return messageString;
195
196 if (!messageString.length())
197 return nameString;
198
199 StringBuilder builder;
200 builder.append(nameString);
201 builder.appendLiteral(": ");
202 builder.append(messageString);
203 return builder.toString();
204}
205
206void ErrorInstance::finalizeUnconditionally(VM& vm)
207{
208 if (!m_stackTrace)
209 return;
210
211 // We don't want to keep our stack traces alive forever if the user doesn't access the stack trace.
212 // If we did, we might end up keeping functions (and their global objects) alive that happened to
213 // get caught in a trace.
214 for (const auto& frame : *m_stackTrace.get()) {
215 if (!frame.isMarked(vm)) {
216 computeErrorInfo(vm);
217 return;
218 }
219 }
220}
221
222void ErrorInstance::computeErrorInfo(VM& vm)
223{
224 ASSERT(!m_errorInfoMaterialized);
225
226 if (m_stackTrace && !m_stackTrace->isEmpty()) {
227 getLineColumnAndSource(m_stackTrace.get(), m_line, m_column, m_sourceURL);
228 m_stackString = Interpreter::stackTraceAsString(vm, *m_stackTrace.get());
229 m_stackTrace = nullptr;
230 }
231}
232
233bool ErrorInstance::materializeErrorInfoIfNeeded(VM& vm)
234{
235 if (m_errorInfoMaterialized)
236 return false;
237
238 computeErrorInfo(vm);
239
240 if (!m_stackString.isNull()) {
241 putDirect(vm, vm.propertyNames->line, jsNumber(m_line));
242 putDirect(vm, vm.propertyNames->column, jsNumber(m_column));
243 if (!m_sourceURL.isEmpty())
244 putDirect(vm, vm.propertyNames->sourceURL, jsString(&vm, WTFMove(m_sourceURL)));
245
246 putDirect(vm, vm.propertyNames->stack, jsString(&vm, WTFMove(m_stackString)), static_cast<unsigned>(PropertyAttribute::DontEnum));
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, ExecState* exec, PropertyName propertyName, PropertySlot& slot)
264{
265 VM& vm = exec->vm();
266 ErrorInstance* thisObject = jsCast<ErrorInstance*>(object);
267 thisObject->materializeErrorInfoIfNeeded(vm, propertyName);
268 return Base::getOwnPropertySlot(thisObject, exec, propertyName, slot);
269}
270
271void ErrorInstance::getOwnNonIndexPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNameArray, EnumerationMode enumerationMode)
272{
273 VM& vm = exec->vm();
274 ErrorInstance* thisObject = jsCast<ErrorInstance*>(object);
275 thisObject->materializeErrorInfoIfNeeded(vm);
276 Base::getOwnNonIndexPropertyNames(thisObject, exec, propertyNameArray, enumerationMode);
277}
278
279void ErrorInstance::getStructurePropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNameArray, EnumerationMode enumerationMode)
280{
281 VM& vm = exec->vm();
282 ErrorInstance* thisObject = jsCast<ErrorInstance*>(object);
283 thisObject->materializeErrorInfoIfNeeded(vm);
284 Base::getStructurePropertyNames(thisObject, exec, propertyNameArray, enumerationMode);
285}
286
287bool ErrorInstance::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName propertyName, const PropertyDescriptor& descriptor, bool shouldThrow)
288{
289 VM& vm = exec->vm();
290 ErrorInstance* thisObject = jsCast<ErrorInstance*>(object);
291 thisObject->materializeErrorInfoIfNeeded(vm, propertyName);
292 return Base::defineOwnProperty(thisObject, exec, propertyName, descriptor, shouldThrow);
293}
294
295bool ErrorInstance::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
296{
297 VM& vm = exec->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, exec, propertyName, value, slot);
303}
304
305bool ErrorInstance::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName)
306{
307 VM& vm = exec->vm();
308 ErrorInstance* thisObject = jsCast<ErrorInstance*>(cell);
309 thisObject->materializeErrorInfoIfNeeded(vm, propertyName);
310 return Base::deleteProperty(thisObject, exec, propertyName);
311}
312
313} // namespace JSC
314