ExceptionHelpers.cpp [plain text]
#include "config.h"
#include "ExceptionHelpers.h"
#include "CatchScope.h"
#include "ErrorHandlingScope.h"
#include "Exception.h"
#include "JSCInlines.h"
#include "RuntimeType.h"
#include <wtf/text/StringBuilder.h>
#include <wtf/text/StringView.h>
namespace JSC {
STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(TerminatedExecutionError);
const ClassInfo TerminatedExecutionError::s_info = { "TerminatedExecutionError", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(TerminatedExecutionError) };
JSValue TerminatedExecutionError::defaultValue(const JSObject*, JSGlobalObject* globalObject, PreferredPrimitiveType hint)
{
if (hint == PreferString)
return jsNontrivialString(globalObject->vm(), String("JavaScript execution terminated."_s));
return JSValue(PNaN);
}
JSObject* createTerminatedExecutionException(VM* vm)
{
return TerminatedExecutionError::create(*vm);
}
bool isTerminatedExecutionException(VM& vm, Exception* exception)
{
if (!exception->value().isObject())
return false;
return exception->value().inherits<TerminatedExecutionError>(vm);
}
JSObject* createStackOverflowError(JSGlobalObject* globalObject)
{
auto* error = createRangeError(globalObject, "Maximum call stack size exceeded."_s);
jsCast<ErrorInstance*>(error)->setStackOverflowError();
return error;
}
JSObject* createUndefinedVariableError(JSGlobalObject* globalObject, const Identifier& ident)
{
if (ident.isPrivateName())
return createReferenceError(globalObject, makeString("Can't find private variable: PrivateSymbol.", ident.string()));
return createReferenceError(globalObject, makeString("Can't find variable: ", ident.string()));
}
String errorDescriptionForValue(JSGlobalObject* globalObject, JSValue v)
{
if (v.isString()) {
String string = asString(v)->value(globalObject);
if (!string)
return string;
return tryMakeString('"', string, '"');
}
if (v.isSymbol())
return asSymbol(v)->descriptiveString();
if (v.isObject()) {
VM& vm = globalObject->vm();
JSObject* object = asObject(v);
if (object->isCallable(vm))
return vm.smallStrings.functionString()->value(globalObject);
return JSObject::calculatedClassName(object);
}
return v.toString(globalObject)->value(globalObject);
}
static String defaultApproximateSourceError(const String& originalMessage, const String& sourceText)
{
return makeString(originalMessage, " (near '...", sourceText, "...')");
}
String defaultSourceAppender(const String& originalMessage, const String& sourceText, RuntimeType, ErrorInstance::SourceTextWhereErrorOccurred occurrence)
{
if (occurrence == ErrorInstance::FoundApproximateSource)
return defaultApproximateSourceError(originalMessage, sourceText);
ASSERT(occurrence == ErrorInstance::FoundExactSource);
return makeString(originalMessage, " (evaluating '", sourceText, "')");
}
static String functionCallBase(const String& sourceText)
{
unsigned sourceLength = sourceText.length();
unsigned idx = sourceLength - 1;
if (sourceLength < 2 || sourceText[idx] != ')') {
return String();
}
unsigned parenStack = 1;
bool isInMultiLineComment = false;
idx -= 1;
while (parenStack && idx) {
UChar curChar = sourceText[idx];
if (isInMultiLineComment) {
if (curChar == '*' && sourceText[idx - 1] == '/') {
isInMultiLineComment = false;
--idx;
}
} else if (curChar == '(')
--parenStack;
else if (curChar == ')')
++parenStack;
else if (curChar == '/' && sourceText[idx - 1] == '*') {
isInMultiLineComment = true;
--idx;
}
if (idx)
--idx;
}
if (parenStack) {
return String();
}
if (idx > 1 && sourceText[idx] == '.' && sourceText[idx - 1] == '?')
idx -= 2;
return sourceText.left(idx + 1);
}
static String notAFunctionSourceAppender(const String& originalMessage, const String& sourceText, RuntimeType type, ErrorInstance::SourceTextWhereErrorOccurred occurrence)
{
ASSERT(type != TypeFunction);
if (occurrence == ErrorInstance::FoundApproximateSource)
return defaultApproximateSourceError(originalMessage, sourceText);
ASSERT(occurrence == ErrorInstance::FoundExactSource);
auto notAFunctionIndex = originalMessage.reverseFind("is not a function");
RELEASE_ASSERT(notAFunctionIndex != notFound);
StringView displayValue;
if (originalMessage.is8Bit())
displayValue = StringView(originalMessage.characters8(), notAFunctionIndex - 1);
else
displayValue = StringView(originalMessage.characters16(), notAFunctionIndex - 1);
String base = functionCallBase(sourceText);
if (!base)
return defaultApproximateSourceError(originalMessage, sourceText);
StringBuilder builder(StringBuilder::OverflowHandler::RecordOverflow);
builder.append(base, " is not a function. (In '", sourceText, "', '", base, "' is ");
if (type == TypeSymbol)
builder.appendLiteral("a Symbol");
else {
if (type == TypeObject)
builder.appendLiteral("an instance of ");
builder.append(displayValue);
}
builder.append(')');
if (builder.hasOverflowed())
return "object is not a function."_s;
return builder.toString();
}
static String invalidParameterInSourceAppender(const String& originalMessage, const String& sourceText, RuntimeType type, ErrorInstance::SourceTextWhereErrorOccurred occurrence)
{
ASSERT_UNUSED(type, type != TypeObject);
if (occurrence == ErrorInstance::FoundApproximateSource)
return defaultApproximateSourceError(originalMessage, sourceText);
ASSERT(occurrence == ErrorInstance::FoundExactSource);
auto inIndex = sourceText.reverseFind("in");
if (inIndex == notFound) {
return originalMessage;
}
if (sourceText.find("in") != inIndex)
return makeString(originalMessage, " (evaluating '", sourceText, "')");
static constexpr unsigned inLength = 2;
String rightHandSide = sourceText.substring(inIndex + inLength).simplifyWhiteSpace();
return makeString(rightHandSide, " is not an Object. (evaluating '", sourceText, "')");
}
inline String invalidParameterInstanceofSourceAppender(const String& content, const String& originalMessage, const String& sourceText, RuntimeType, ErrorInstance::SourceTextWhereErrorOccurred occurrence)
{
if (occurrence == ErrorInstance::FoundApproximateSource)
return defaultApproximateSourceError(originalMessage, sourceText);
ASSERT(occurrence == ErrorInstance::FoundExactSource);
auto instanceofIndex = sourceText.reverseFind("instanceof");
RELEASE_ASSERT(instanceofIndex != notFound);
if (sourceText.find("instanceof") != instanceofIndex)
return makeString(originalMessage, " (evaluating '", sourceText, "')");
static constexpr unsigned instanceofLength = 10;
String rightHandSide = sourceText.substring(instanceofIndex + instanceofLength).simplifyWhiteSpace();
return makeString(rightHandSide, content, ". (evaluating '", sourceText, "')");
}
static String invalidParameterInstanceofNotFunctionSourceAppender(const String& originalMessage, const String& sourceText, RuntimeType runtimeType, ErrorInstance::SourceTextWhereErrorOccurred occurrence)
{
return invalidParameterInstanceofSourceAppender(" is not a function"_s, originalMessage, sourceText, runtimeType, occurrence);
}
static String invalidParameterInstanceofhasInstanceValueNotFunctionSourceAppender(const String& originalMessage, const String& sourceText, RuntimeType runtimeType, ErrorInstance::SourceTextWhereErrorOccurred occurrence)
{
return invalidParameterInstanceofSourceAppender("[Symbol.hasInstance] is not a function, undefined, or null"_s, originalMessage, sourceText, runtimeType, occurrence);
}
JSObject* createError(JSGlobalObject* globalObject, JSValue value, const String& message, ErrorInstance::SourceAppender appender)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_CATCH_SCOPE(vm);
String valueDescription = errorDescriptionForValue(globalObject, value);
if (scope.exception() || !valueDescription) {
scope.clearException();
return createOutOfMemoryError(globalObject);
}
String errorMessage = tryMakeString(valueDescription, ' ', message);
if (!errorMessage)
return createOutOfMemoryError(globalObject);
scope.assertNoException();
JSObject* exception = createTypeError(globalObject, errorMessage, appender, runtimeTypeForValue(vm, value));
ASSERT(exception->isErrorInstance());
return exception;
}
JSObject* createInvalidFunctionApplyParameterError(JSGlobalObject* globalObject, JSValue value)
{
return createTypeError(globalObject, "second argument to Function.prototype.apply must be an Array-like object"_s, defaultSourceAppender, runtimeTypeForValue(globalObject->vm(), value));
}
JSObject* createInvalidInParameterError(JSGlobalObject* globalObject, JSValue value)
{
return createError(globalObject, value, "is not an Object."_s, invalidParameterInSourceAppender);
}
JSObject* createInvalidInstanceofParameterErrorNotFunction(JSGlobalObject* globalObject, JSValue value)
{
return createError(globalObject, value, " is not a function"_s, invalidParameterInstanceofNotFunctionSourceAppender);
}
JSObject* createInvalidInstanceofParameterErrorHasInstanceValueNotFunction(JSGlobalObject* globalObject, JSValue value)
{
return createError(globalObject, value, "[Symbol.hasInstance] is not a function, undefined, or null"_s, invalidParameterInstanceofhasInstanceValueNotFunctionSourceAppender);
}
JSObject* createNotAConstructorError(JSGlobalObject* globalObject, JSValue value)
{
return createError(globalObject, value, "is not a constructor"_s, defaultSourceAppender);
}
JSObject* createNotAFunctionError(JSGlobalObject* globalObject, JSValue value)
{
return createError(globalObject, value, "is not a function"_s, notAFunctionSourceAppender);
}
JSObject* createNotAnObjectError(JSGlobalObject* globalObject, JSValue value)
{
return createError(globalObject, value, "is not an object"_s, defaultSourceAppender);
}
JSObject* createErrorForInvalidGlobalAssignment(JSGlobalObject* globalObject, const String& propertyName)
{
return createReferenceError(globalObject, makeString("Strict mode forbids implicit creation of global property '", propertyName, '\''));
}
JSObject* createTDZError(JSGlobalObject* globalObject)
{
return createReferenceError(globalObject, "Cannot access uninitialized variable.");
}
JSObject* createInvalidPrivateNameError(JSGlobalObject* globalObject)
{
return createTypeError(globalObject, makeString("Cannot access invalid private field"), defaultSourceAppender, TypeNothing);
}
JSObject* createRedefinedPrivateNameError(JSGlobalObject* globalObject)
{
return createTypeError(globalObject, makeString("Cannot redefine existing private field"), defaultSourceAppender, TypeNothing);
}
Exception* throwOutOfMemoryError(JSGlobalObject* globalObject, ThrowScope& scope)
{
return throwException(globalObject, scope, createOutOfMemoryError(globalObject));
}
Exception* throwOutOfMemoryError(JSGlobalObject* globalObject, ThrowScope& scope, const String& message)
{
return throwException(globalObject, scope, createOutOfMemoryError(globalObject, message));
}
Exception* throwStackOverflowError(JSGlobalObject* globalObject, ThrowScope& scope)
{
VM& vm = globalObject->vm();
ErrorHandlingScope errorScope(vm);
return throwException(globalObject, scope, createStackOverflowError(globalObject));
}
Exception* throwTerminatedExecutionException(JSGlobalObject* globalObject, ThrowScope& scope)
{
VM& vm = globalObject->vm();
ErrorHandlingScope errorScope(vm);
return throwException(globalObject, scope, createTerminatedExecutionException(&vm));
}
}