ErrorInstance.cpp
1 /* 2 * Copyright (C) 1999-2000 Harri Porten (porten@kde.org) 3 * Copyright (C) 2003-2020 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 "IntegrityInlines.h" 27 #include "Interpreter.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 String appendSourceToErrorMessage(CallFrame* callFrame, ErrorInstance* exception, BytecodeIndex bytecodeIndex, const String& message) 52 { 53 ErrorInstance::SourceAppender appender = exception->sourceAppender(); 54 exception->clearSourceAppender(); 55 RuntimeType type = exception->runtimeTypeForCause(); 56 exception->clearRuntimeTypeForCause(); 57 58 if (!callFrame->codeBlock()->hasExpressionInfo() || message.isNull()) 59 return message; 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 message; 82 83 if (expressionStart < expressionStop) 84 return appender(message, codeBlock->source().provider()->getRange(expressionStart, expressionStop).toString(), type, ErrorInstance::FoundExactSource); 85 86 // No range information, so give a few characters of context. 87 int dataLength = sourceString.length(); 88 int start = expressionStart; 89 int stop = expressionStart; 90 // Get up to 20 characters of context to the left and right of the divot, clamping to the line. 91 // Then strip whitespace. 92 while (start > 0 && (expressionStart - start < 20) && sourceString[start - 1] != '\n') 93 start--; 94 while (start < (expressionStart - 1) && isStrWhiteSpace(sourceString[start])) 95 start++; 96 while (stop < dataLength && (stop - expressionStart < 20) && sourceString[stop] != '\n') 97 stop++; 98 while (stop > expressionStart && isStrWhiteSpace(sourceString[stop - 1])) 99 stop--; 100 return appender(message, codeBlock->source().provider()->getRange(start, stop).toString(), type, ErrorInstance::FoundApproximateSource); 101 } 102 103 void ErrorInstance::finishCreation(VM& vm, JSGlobalObject* globalObject, const String& message, SourceAppender appender, RuntimeType type, bool useCurrentFrame) 104 { 105 Base::finishCreation(vm); 106 ASSERT(inherits(vm, info())); 107 108 m_sourceAppender = appender; 109 m_runtimeTypeForCause = type; 110 111 std::unique_ptr<Vector<StackFrame>> stackTrace = getStackTrace(globalObject, vm, this, useCurrentFrame); 112 { 113 auto locker = holdLock(cellLock()); 114 m_stackTrace = WTFMove(stackTrace); 115 } 116 vm.heap.writeBarrier(this); 117 118 String messageWithSource = message; 119 if (m_stackTrace && !m_stackTrace->isEmpty() && hasSourceAppender()) { 120 BytecodeIndex bytecodeIndex; 121 CallFrame* callFrame; 122 getBytecodeIndex(vm, vm.topCallFrame, m_stackTrace.get(), callFrame, bytecodeIndex); 123 if (callFrame && callFrame->codeBlock() && !callFrame->callee().isWasm()) 124 messageWithSource = appendSourceToErrorMessage(callFrame, this, bytecodeIndex, message); 125 } 126 127 if (!messageWithSource.isNull()) 128 putDirect(vm, vm.propertyNames->message, jsString(vm, messageWithSource), static_cast<unsigned>(PropertyAttribute::DontEnum)); 129 } 130 131 // Based on ErrorPrototype's errorProtoFuncToString(), but is modified to 132 // have no observable side effects to the user (i.e. does not call proxies, 133 // and getters). 134 String ErrorInstance::sanitizedToString(JSGlobalObject* globalObject) 135 { 136 VM& vm = globalObject->vm(); 137 auto scope = DECLARE_THROW_SCOPE(vm); 138 Integrity::auditStructureID(vm, structureID()); 139 140 JSValue nameValue; 141 auto namePropertName = vm.propertyNames->name; 142 PropertySlot nameSlot(this, PropertySlot::InternalMethodType::VMInquiry, &vm); 143 144 JSValue currentObj = this; 145 unsigned prototypeDepth = 0; 146 147 // We only check the current object and its prototype (2 levels) because normal 148 // Error objects may have a name property, and if not, its prototype should have 149 // a name property for the type of error e.g. "SyntaxError". 150 while (currentObj.isCell() && prototypeDepth++ < 2) { 151 JSObject* obj = jsCast<JSObject*>(currentObj); 152 if (JSObject::getOwnPropertySlot(obj, globalObject, namePropertName, nameSlot) && nameSlot.isValue()) { 153 nameValue = nameSlot.getValue(globalObject, namePropertName); 154 break; 155 } 156 currentObj = obj->getPrototypeDirect(vm); 157 } 158 scope.assertNoException(); 159 160 String nameString; 161 if (!nameValue) 162 nameString = "Error"_s; 163 else { 164 nameString = nameValue.toWTFString(globalObject); 165 RETURN_IF_EXCEPTION(scope, String()); 166 } 167 168 JSValue messageValue; 169 auto messagePropertName = vm.propertyNames->message; 170 PropertySlot messageSlot(this, PropertySlot::InternalMethodType::VMInquiry, &vm); 171 if (JSObject::getOwnPropertySlot(this, globalObject, messagePropertName, messageSlot) && messageSlot.isValue()) 172 messageValue = messageSlot.getValue(globalObject, messagePropertName); 173 scope.assertNoException(); 174 175 String messageString; 176 if (!messageValue) 177 messageString = String(); 178 else { 179 messageString = messageValue.toWTFString(globalObject); 180 RETURN_IF_EXCEPTION(scope, String()); 181 } 182 183 if (!nameString.length()) 184 return messageString; 185 186 if (!messageString.length()) 187 return nameString; 188 189 StringBuilder builder; 190 builder.append(nameString); 191 builder.appendLiteral(": "); 192 builder.append(messageString); 193 return builder.toString(); 194 } 195 196 void ErrorInstance::finalizeUnconditionally(VM& vm) 197 { 198 if (!m_stackTrace) 199 return; 200 201 // We don't want to keep our stack traces alive forever if the user doesn't access the stack trace. 202 // If we did, we might end up keeping functions (and their global objects) alive that happened to 203 // get caught in a trace. 204 for (const auto& frame : *m_stackTrace.get()) { 205 if (!frame.isMarked(vm)) { 206 computeErrorInfo(vm); 207 return; 208 } 209 } 210 } 211 212 void ErrorInstance::computeErrorInfo(VM& vm) 213 { 214 ASSERT(!m_errorInfoMaterialized); 215 216 if (m_stackTrace && !m_stackTrace->isEmpty()) { 217 getLineColumnAndSource(m_stackTrace.get(), m_line, m_column, m_sourceURL); 218 m_stackString = Interpreter::stackTraceAsString(vm, *m_stackTrace.get()); 219 m_stackTrace = nullptr; 220 } 221 } 222 223 bool ErrorInstance::materializeErrorInfoIfNeeded(VM& vm) 224 { 225 if (m_errorInfoMaterialized) 226 return false; 227 228 computeErrorInfo(vm); 229 230 if (!m_stackString.isNull()) { 231 auto attributes = static_cast<unsigned>(PropertyAttribute::DontEnum); 232 233 putDirect(vm, vm.propertyNames->line, jsNumber(m_line), attributes); 234 putDirect(vm, vm.propertyNames->column, jsNumber(m_column), attributes); 235 if (!m_sourceURL.isEmpty()) 236 putDirect(vm, vm.propertyNames->sourceURL, jsString(vm, WTFMove(m_sourceURL)), attributes); 237 238 putDirect(vm, vm.propertyNames->stack, jsString(vm, WTFMove(m_stackString)), attributes); 239 } 240 241 m_errorInfoMaterialized = true; 242 return true; 243 } 244 245 bool ErrorInstance::materializeErrorInfoIfNeeded(VM& vm, PropertyName propertyName) 246 { 247 if (propertyName == vm.propertyNames->line 248 || propertyName == vm.propertyNames->column 249 || propertyName == vm.propertyNames->sourceURL 250 || propertyName == vm.propertyNames->stack) 251 return materializeErrorInfoIfNeeded(vm); 252 return false; 253 } 254 255 bool ErrorInstance::getOwnPropertySlot(JSObject* object, JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot) 256 { 257 VM& vm = globalObject->vm(); 258 ErrorInstance* thisObject = jsCast<ErrorInstance*>(object); 259 thisObject->materializeErrorInfoIfNeeded(vm, propertyName); 260 return Base::getOwnPropertySlot(thisObject, globalObject, propertyName, slot); 261 } 262 263 void ErrorInstance::getOwnSpecialPropertyNames(JSObject* object, JSGlobalObject* globalObject, PropertyNameArray&, DontEnumPropertiesMode mode) 264 { 265 VM& vm = globalObject->vm(); 266 ErrorInstance* thisObject = jsCast<ErrorInstance*>(object); 267 if (mode == DontEnumPropertiesMode::Include) 268 thisObject->materializeErrorInfoIfNeeded(vm); 269 } 270 271 bool ErrorInstance::defineOwnProperty(JSObject* object, JSGlobalObject* globalObject, PropertyName propertyName, const PropertyDescriptor& descriptor, bool shouldThrow) 272 { 273 VM& vm = globalObject->vm(); 274 ErrorInstance* thisObject = jsCast<ErrorInstance*>(object); 275 thisObject->materializeErrorInfoIfNeeded(vm, propertyName); 276 return Base::defineOwnProperty(thisObject, globalObject, propertyName, descriptor, shouldThrow); 277 } 278 279 bool ErrorInstance::put(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot) 280 { 281 VM& vm = globalObject->vm(); 282 ErrorInstance* thisObject = jsCast<ErrorInstance*>(cell); 283 bool materializedProperties = thisObject->materializeErrorInfoIfNeeded(vm, propertyName); 284 if (materializedProperties) 285 slot.disableCaching(); 286 return Base::put(thisObject, globalObject, propertyName, value, slot); 287 } 288 289 bool ErrorInstance::deleteProperty(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, DeletePropertySlot& slot) 290 { 291 VM& vm = globalObject->vm(); 292 ErrorInstance* thisObject = jsCast<ErrorInstance*>(cell); 293 thisObject->materializeErrorInfoIfNeeded(vm, propertyName); 294 return Base::deleteProperty(thisObject, globalObject, propertyName, slot); 295 } 296 297 String ErrorInstance::toStringName(const JSObject*, JSGlobalObject*) 298 { 299 return "Error"_s; 300 } 301 302 } // namespace JSC