blob: 0afb531b5963ed498bc45edfe1a90c0be562dd37 [file] [log] [blame]
/*
* Copyright (C) 2008-2021 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#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 {
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())
return vm.smallStrings.functionString()->value(globalObject);
return JSObject::calculatedClassName(object);
}
return v.toString(globalObject)->value(globalObject);
}
static StringView clampErrorMessage(const String& originalMessage)
{
// Hopefully this is sufficiently long. Note, this is the length of the string not the number of bytes used.
constexpr unsigned maxLength = 2 * KB;
return StringView(originalMessage).left(maxLength);
}
static String defaultApproximateSourceError(const String& originalMessage, StringView sourceText)
{
return makeString(clampErrorMessage(originalMessage), " (near '...", sourceText, "...')");
}
String defaultSourceAppender(const String& originalMessage, StringView sourceText, RuntimeType, ErrorInstance::SourceTextWhereErrorOccurred occurrence)
{
if (occurrence == ErrorInstance::FoundApproximateSource)
return defaultApproximateSourceError(originalMessage, sourceText);
ASSERT(occurrence == ErrorInstance::FoundExactSource);
return makeString(clampErrorMessage(originalMessage), " (evaluating '", sourceText, "')");
}
static StringView functionCallBase(StringView sourceText)
{
// This function retrieves the 'foo.bar' substring from 'foo.bar(baz)'.
// FIXME: This function has simple processing of /* */ style comments.
// It doesn't properly handle embedded comments of string literals that contain
// parenthesis or comment constructs, e.g. foo.bar("/abc\)*/").
// https://bugs.webkit.org/show_bug.cgi?id=146304
unsigned sourceLength = sourceText.length();
unsigned idx = sourceLength - 1;
if (sourceLength < 2 || sourceText[idx] != ')') {
// For function calls that have many new lines in between their open parenthesis
// and their closing parenthesis, the text range passed into the message appender
// will not include the text in between these parentheses, it will just be the desired
// text that precedes the parentheses.
return String();
}
unsigned parenStack = 1;
bool isInMultiLineComment = false;
idx -= 1;
// Note that we're scanning text right to left instead of the more common left to right,
// so syntax detection is backwards.
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) {
// As noted in the FIXME at the top of this function, there are bugs
// in the above string processing. This algorithm is mostly best effort
// and it works for most JS text in practice. However, if we determine
// that the algorithm failed, we should just return the empty value.
return String();
}
// Don't display the ?. of an optional call.
if (idx > 1 && sourceText[idx] == '.' && sourceText[idx - 1] == '?')
idx -= 2;
return sourceText.left(idx + 1);
}
static String notAFunctionSourceAppender(const String& originalMessage, StringView 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"_s);
RELEASE_ASSERT(notAFunctionIndex != notFound);
StringView displayValue;
if (originalMessage.is8Bit())
displayValue = StringView(originalMessage.characters8(), notAFunctionIndex - 1);
else
displayValue = StringView(originalMessage.characters16(), notAFunctionIndex - 1);
StringView 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.append("a Symbol");
else {
if (type == TypeObject)
builder.append("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, StringView 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"_s);
if (inIndex == notFound) {
// This should basically never happen, since JS code must use the literal
// text "in" for the `in` operation. However, if we fail to find "in"
// for any reason, just fail gracefully.
return originalMessage;
}
if (sourceText.find("in"_s) != inIndex)
return makeString(originalMessage, " (evaluating '", sourceText, "')");
static constexpr unsigned inLength = 2;
StringView rightHandSide = sourceText.substring(inIndex + inLength).stripLeadingAndTrailingMatchedCharacters(isSpaceOrNewline);
return makeString(rightHandSide, " is not an Object. (evaluating '", sourceText, "')");
}
inline String invalidParameterInstanceofSourceAppender(const String& content, const String& originalMessage, StringView sourceText, RuntimeType, ErrorInstance::SourceTextWhereErrorOccurred occurrence)
{
if (occurrence == ErrorInstance::FoundApproximateSource)
return defaultApproximateSourceError(originalMessage, sourceText);
ASSERT(occurrence == ErrorInstance::FoundExactSource);
auto instanceofIndex = sourceText.reverseFind("instanceof"_s);
// This can happen when Symbol.hasInstance function is directly called.
if (instanceofIndex == notFound)
return originalMessage;
if (sourceText.find("instanceof"_s) != instanceofIndex)
return makeString(originalMessage, " (evaluating '", sourceText, "')");
static constexpr unsigned instanceofLength = 10;
StringView rightHandSide = sourceText.substring(instanceofIndex + instanceofLength).stripLeadingAndTrailingMatchedCharacters(isSpaceOrNewline);
return makeString(rightHandSide, content, ". (evaluating '", sourceText, "')");
}
static String invalidParameterInstanceofNotFunctionSourceAppender(const String& originalMessage, StringView sourceText, RuntimeType runtimeType, ErrorInstance::SourceTextWhereErrorOccurred occurrence)
{
return invalidParameterInstanceofSourceAppender(" is not a function"_s, originalMessage, sourceText, runtimeType, occurrence);
}
static String invalidParameterInstanceofhasInstanceValueNotFunctionSourceAppender(const String& originalMessage, StringView sourceText, RuntimeType runtimeType, ErrorInstance::SourceTextWhereErrorOccurred occurrence)
{
return invalidParameterInstanceofSourceAppender("[Symbol.hasInstance] is not a function, undefined, or null"_s, originalMessage, sourceText, runtimeType, occurrence);
}
static String invalidPrototypeSourceAppender(const String& originalMessage, StringView sourceText, RuntimeType, ErrorInstance::SourceTextWhereErrorOccurred occurrence)
{
if (occurrence == ErrorInstance::FoundApproximateSource)
return defaultApproximateSourceError(originalMessage, sourceText);
auto extendsIndex = sourceText.reverseFind("extends"_s);
if (extendsIndex == notFound || sourceText.find("extends"_s) != extendsIndex)
return makeString(originalMessage, " (evaluating '", sourceText, "')");
return "The value of the superclass's prototype property is not an object or null."_s;
}
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) {
// When we see an exception, we're not returning immediately because
// we're in a CatchScope, i.e. no exceptions are thrown past this scope.
// We're using a CatchScope because the contract for createError() is
// that it only creates an error object; it doesn't throw it.
scope.clearException();
return createOutOfMemoryError(globalObject);
}
String errorMessage = tryMakeString(valueDescription, ' ', message);
if (!errorMessage)
return createOutOfMemoryError(globalObject);
scope.assertNoException();
JSObject* exception = createTypeError(globalObject, errorMessage, appender, runtimeTypeForValue(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(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* createInvalidPrototypeError(JSGlobalObject* globalObject, JSValue value)
{
return createError(globalObject, value, "is not an object or null"_s, invalidPrototypeSourceAppender);
}
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."_s);
}
JSObject* createInvalidPrivateNameError(JSGlobalObject* globalObject)
{
return createTypeError(globalObject, "Cannot access invalid private field"_s, defaultSourceAppender, TypeNothing);
}
JSObject* createRedefinedPrivateNameError(JSGlobalObject* globalObject)
{
return createTypeError(globalObject, "Cannot redefine existing private field"_s, defaultSourceAppender, TypeNothing);
}
JSObject* createPrivateMethodAccessError(JSGlobalObject* globalObject)
{
return createTypeError(globalObject, "Cannot access private method or acessor"_s, defaultSourceAppender, TypeNothing);
}
JSObject* createReinstallPrivateMethodError(JSGlobalObject* globalObject)
{
return createTypeError(globalObject, "Cannot install same private methods on object more than once"_s, 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));
}
} // namespace JSC