| 1 | /* |
| 2 | * Copyright (C) 1999-2000 Harri Porten (porten@kde.org) |
| 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 | |
| 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(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 | |
| 51 | static 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 | |
| 112 | void 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 | |
| 137 | void 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). |
| 145 | String 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 | |
| 206 | void 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 | |
| 222 | void 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 | |
| 233 | bool 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 | |
| 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, 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 | |
| 271 | void 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 | |
| 279 | void 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 | |
| 287 | bool 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 | |
| 295 | bool 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 | |
| 305 | bool 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 | |