blob: 2cecccb0e9dcf49052304ef952651a7c550eb7c7 [file] [log] [blame]
/*
* Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
* Copyright (C) 2003-2021 Apple Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "config.h"
#include "ErrorInstance.h"
#include "CodeBlock.h"
#include "InlineCallFrame.h"
#include "IntegrityInlines.h"
#include "Interpreter.h"
#include "JSCInlines.h"
#include "ParseInt.h"
#include "StackFrame.h"
namespace JSC {
const ClassInfo ErrorInstance::s_info = { "Error"_s, &JSNonFinalObject::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(ErrorInstance) };
ErrorInstance::ErrorInstance(VM& vm, Structure* structure, ErrorType errorType)
: Base(vm, structure)
, m_errorType(errorType)
, m_stackOverflowError(false)
, m_outOfMemoryError(false)
, m_errorInfoMaterialized(false)
, m_nativeGetterTypeError(false)
#if ENABLE(WEBASSEMBLY)
, m_catchableFromWasm(true)
#endif // ENABLE(WEBASSEMBLY)
{
}
ErrorInstance* ErrorInstance::create(JSGlobalObject* globalObject, Structure* structure, JSValue message, JSValue options, SourceAppender appender, RuntimeType type, ErrorType errorType, bool useCurrentFrame)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
String messageString = message.isUndefined() ? String() : message.toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, nullptr);
JSValue cause;
if (options.isObject()) {
// Since `throw undefined;` is valid, we need to distinguish the case where `cause` is an explicit undefined.
cause = asObject(options)->getIfPropertyExists(globalObject, vm.propertyNames->cause);
RETURN_IF_EXCEPTION(scope, nullptr);
}
return create(globalObject, vm, structure, messageString, cause, appender, type, errorType, useCurrentFrame);
}
static String appendSourceToErrorMessage(CallFrame* callFrame, ErrorInstance* exception, BytecodeIndex bytecodeIndex, const String& message)
{
ErrorInstance::SourceAppender appender = exception->sourceAppender();
exception->clearSourceAppender();
RuntimeType type = exception->runtimeTypeForCause();
exception->clearRuntimeTypeForCause();
if (!callFrame->codeBlock()->hasExpressionInfo() || message.isNull())
return message;
int startOffset = 0;
int endOffset = 0;
int divotPoint = 0;
unsigned line = 0;
unsigned column = 0;
CodeBlock* codeBlock;
CodeOrigin codeOrigin = callFrame->codeOrigin();
if (codeOrigin && codeOrigin.inlineCallFrame())
codeBlock = baselineCodeBlockForInlineCallFrame(codeOrigin.inlineCallFrame());
else
codeBlock = callFrame->codeBlock();
codeBlock->expressionRangeForBytecodeIndex(bytecodeIndex, divotPoint, startOffset, endOffset, line, column);
int expressionStart = divotPoint - startOffset;
int expressionStop = divotPoint + endOffset;
StringView sourceString = codeBlock->source().provider()->source();
if (!expressionStop || expressionStart > static_cast<int>(sourceString.length()))
return message;
if (expressionStart < expressionStop)
return appender(message, codeBlock->source().provider()->getRange(expressionStart, expressionStop), type, ErrorInstance::FoundExactSource);
// No range information, so give a few characters of context.
int dataLength = sourceString.length();
int start = expressionStart;
int stop = expressionStart;
// Get up to 20 characters of context to the left and right of the divot, clamping to the line.
// Then strip whitespace.
while (start > 0 && (expressionStart - start < 20) && sourceString[start - 1] != '\n')
start--;
while (start < (expressionStart - 1) && isStrWhiteSpace(sourceString[start]))
start++;
while (stop < dataLength && (stop - expressionStart < 20) && sourceString[stop] != '\n')
stop++;
while (stop > expressionStart && isStrWhiteSpace(sourceString[stop - 1]))
stop--;
return appender(message, codeBlock->source().provider()->getRange(start, stop), type, ErrorInstance::FoundApproximateSource);
}
void ErrorInstance::finishCreation(VM& vm, JSGlobalObject* globalObject, const String& message, JSValue cause, SourceAppender appender, RuntimeType type, bool useCurrentFrame)
{
Base::finishCreation(vm);
ASSERT(inherits(info()));
m_sourceAppender = appender;
m_runtimeTypeForCause = type;
std::unique_ptr<Vector<StackFrame>> stackTrace = getStackTrace(globalObject, vm, this, useCurrentFrame);
{
Locker locker { cellLock() };
m_stackTrace = WTFMove(stackTrace);
}
vm.writeBarrier(this);
String messageWithSource = message;
if (m_stackTrace && !m_stackTrace->isEmpty() && hasSourceAppender()) {
BytecodeIndex bytecodeIndex;
CallFrame* callFrame;
getBytecodeIndex(vm, vm.topCallFrame, m_stackTrace.get(), callFrame, bytecodeIndex);
if (callFrame && callFrame->codeBlock() && !callFrame->callee().isWasm())
messageWithSource = appendSourceToErrorMessage(callFrame, this, bytecodeIndex, message);
}
if (!messageWithSource.isNull())
putDirect(vm, vm.propertyNames->message, jsString(vm, WTFMove(messageWithSource)), static_cast<unsigned>(PropertyAttribute::DontEnum));
if (!cause.isEmpty())
putDirect(vm, vm.propertyNames->cause, cause, static_cast<unsigned>(PropertyAttribute::DontEnum));
}
// Based on ErrorPrototype's errorProtoFuncToString(), but is modified to
// have no observable side effects to the user (i.e. does not call proxies,
// and getters).
String ErrorInstance::sanitizedMessageString(JSGlobalObject* globalObject)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
Integrity::auditStructureID(structureID());
JSValue messageValue;
auto messagePropertName = vm.propertyNames->message;
PropertySlot messageSlot(this, PropertySlot::InternalMethodType::VMInquiry, &vm);
if (JSObject::getOwnPropertySlot(this, globalObject, messagePropertName, messageSlot) && messageSlot.isValue())
messageValue = messageSlot.getValue(globalObject, messagePropertName);
RETURN_IF_EXCEPTION(scope, { });
if (!messageValue || !messageValue.isPrimitive())
return { };
RELEASE_AND_RETURN(scope, messageValue.toWTFString(globalObject));
}
String ErrorInstance::sanitizedNameString(JSGlobalObject* globalObject)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
Integrity::auditStructureID(structureID());
JSValue nameValue;
auto namePropertName = vm.propertyNames->name;
PropertySlot nameSlot(this, PropertySlot::InternalMethodType::VMInquiry, &vm);
JSValue currentObj = this;
unsigned prototypeDepth = 0;
// We only check the current object and its prototype (2 levels) because normal
// Error objects may have a name property, and if not, its prototype should have
// a name property for the type of error e.g. "SyntaxError".
while (currentObj.isCell() && prototypeDepth++ < 2) {
JSObject* obj = jsCast<JSObject*>(currentObj);
if (JSObject::getOwnPropertySlot(obj, globalObject, namePropertName, nameSlot) && nameSlot.isValue()) {
nameValue = nameSlot.getValue(globalObject, namePropertName);
break;
}
currentObj = obj->getPrototypeDirect();
}
RETURN_IF_EXCEPTION(scope, { });
if (!nameValue || !nameValue.isPrimitive())
return "Error"_s;
RELEASE_AND_RETURN(scope, nameValue.toWTFString(globalObject));
}
String ErrorInstance::sanitizedToString(JSGlobalObject* globalObject)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
Integrity::auditStructureID(structureID());
String nameString = sanitizedNameString(globalObject);
RETURN_IF_EXCEPTION(scope, String());
String messageString = sanitizedMessageString(globalObject);
RETURN_IF_EXCEPTION(scope, String());
return makeString(nameString, nameString.isEmpty() || messageString.isEmpty() ? "" : ": ", messageString);
}
void ErrorInstance::finalizeUnconditionally(VM& vm)
{
if (!m_stackTrace)
return;
// We don't want to keep our stack traces alive forever if the user doesn't access the stack trace.
// If we did, we might end up keeping functions (and their global objects) alive that happened to
// get caught in a trace.
for (const auto& frame : *m_stackTrace.get()) {
if (!frame.isMarked(vm)) {
computeErrorInfo(vm);
return;
}
}
}
void ErrorInstance::computeErrorInfo(VM& vm)
{
ASSERT(!m_errorInfoMaterialized);
if (m_stackTrace && !m_stackTrace->isEmpty()) {
getLineColumnAndSource(m_stackTrace.get(), m_line, m_column, m_sourceURL);
m_stackString = Interpreter::stackTraceAsString(vm, *m_stackTrace.get());
m_stackTrace = nullptr;
}
}
bool ErrorInstance::materializeErrorInfoIfNeeded(VM& vm)
{
if (m_errorInfoMaterialized)
return false;
computeErrorInfo(vm);
if (!m_stackString.isNull()) {
auto attributes = static_cast<unsigned>(PropertyAttribute::DontEnum);
putDirect(vm, vm.propertyNames->line, jsNumber(m_line), attributes);
putDirect(vm, vm.propertyNames->column, jsNumber(m_column), attributes);
if (!m_sourceURL.isEmpty())
putDirect(vm, vm.propertyNames->sourceURL, jsString(vm, WTFMove(m_sourceURL)), attributes);
putDirect(vm, vm.propertyNames->stack, jsString(vm, WTFMove(m_stackString)), attributes);
}
m_errorInfoMaterialized = true;
return true;
}
bool ErrorInstance::materializeErrorInfoIfNeeded(VM& vm, PropertyName propertyName)
{
if (propertyName == vm.propertyNames->line
|| propertyName == vm.propertyNames->column
|| propertyName == vm.propertyNames->sourceURL
|| propertyName == vm.propertyNames->stack)
return materializeErrorInfoIfNeeded(vm);
return false;
}
bool ErrorInstance::getOwnPropertySlot(JSObject* object, JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot)
{
VM& vm = globalObject->vm();
ErrorInstance* thisObject = jsCast<ErrorInstance*>(object);
thisObject->materializeErrorInfoIfNeeded(vm, propertyName);
return Base::getOwnPropertySlot(thisObject, globalObject, propertyName, slot);
}
void ErrorInstance::getOwnSpecialPropertyNames(JSObject* object, JSGlobalObject* globalObject, PropertyNameArray&, DontEnumPropertiesMode mode)
{
VM& vm = globalObject->vm();
ErrorInstance* thisObject = jsCast<ErrorInstance*>(object);
if (mode == DontEnumPropertiesMode::Include)
thisObject->materializeErrorInfoIfNeeded(vm);
}
bool ErrorInstance::defineOwnProperty(JSObject* object, JSGlobalObject* globalObject, PropertyName propertyName, const PropertyDescriptor& descriptor, bool shouldThrow)
{
VM& vm = globalObject->vm();
ErrorInstance* thisObject = jsCast<ErrorInstance*>(object);
thisObject->materializeErrorInfoIfNeeded(vm, propertyName);
return Base::defineOwnProperty(thisObject, globalObject, propertyName, descriptor, shouldThrow);
}
bool ErrorInstance::put(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
{
VM& vm = globalObject->vm();
ErrorInstance* thisObject = jsCast<ErrorInstance*>(cell);
bool materializedProperties = thisObject->materializeErrorInfoIfNeeded(vm, propertyName);
if (materializedProperties)
slot.disableCaching();
return Base::put(thisObject, globalObject, propertyName, value, slot);
}
bool ErrorInstance::deleteProperty(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, DeletePropertySlot& slot)
{
VM& vm = globalObject->vm();
ErrorInstance* thisObject = jsCast<ErrorInstance*>(cell);
thisObject->materializeErrorInfoIfNeeded(vm, propertyName);
return Base::deleteProperty(thisObject, globalObject, propertyName, slot);
}
} // namespace JSC