/* * Copyright (C) 1999-2001 Harri Porten (porten@kde.org) * Copyright (C) 2004-2011, 2013, 2016 Apple Inc. All rights reserved. * Copyright (C) 2007 Samuel Weinig * Copyright (C) 2013 Michael Pruett * * 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 "JSDOMExceptionHandling.h" #include "CachedScript.h" #include "DOMWindow.h" #include "ExceptionCodeDescription.h" #include "ExceptionHeaders.h" #include "ExceptionInterfaces.h" #include "JSDOMPromise.h" #include "JSDOMWindow.h" #include "JSDynamicDowncast.h" #include "JSExceptionBase.h" #include #include #include #include #include #include #if ENABLE(INDEXED_DATABASE) #include "IDBDatabaseException.h" #endif using namespace JSC; namespace WebCore { void reportException(ExecState* exec, JSValue exceptionValue, CachedScript* cachedScript) { VM& vm = exec->vm(); RELEASE_ASSERT(vm.currentThreadIsHoldingAPILock()); auto* exception = jsDynamicDowncast(vm, exceptionValue); if (!exception) { exception = vm.lastException(); if (!exception) exception = JSC::Exception::create(exec->vm(), exceptionValue, JSC::Exception::DoNotCaptureStack); } reportException(exec, exception, cachedScript); } String retrieveErrorMessage(ExecState& state, VM& vm, JSValue exception, CatchScope& catchScope) { if (auto* exceptionBase = toExceptionBase(vm, exception)) return exceptionBase->toString(); // FIXME: Web Inspector: WebCore::reportException should not evaluate JavaScript handling exceptions // If this is a custom exception object, call toString on it to try and get a nice string representation for the exception. String errorMessage; if (auto* error = jsDynamicDowncast(vm, exception)) errorMessage = error->sanitizedToString(&state); else errorMessage = exception.toWTFString(&state); // We need to clear any new exception that may be thrown in the toString() call above. // reportException() is not supposed to be making new exceptions. catchScope.clearException(); vm.clearLastException(); return errorMessage; } void reportException(ExecState* exec, JSC::Exception* exception, CachedScript* cachedScript, ExceptionDetails* exceptionDetails) { VM& vm = exec->vm(); auto scope = DECLARE_CATCH_SCOPE(vm); RELEASE_ASSERT(vm.currentThreadIsHoldingAPILock()); if (isTerminatedExecutionException(vm, exception)) return; ErrorHandlingScope errorScope(exec->vm()); auto callStack = Inspector::createScriptCallStackFromException(exec, exception, Inspector::ScriptCallStack::maxCallStackSizeToCapture); scope.clearException(); vm.clearLastException(); auto* globalObject = jsCast(exec->lexicalGlobalObject()); if (auto* window = jsDynamicDowncast(vm, globalObject)) { if (!window->wrapped().isCurrentlyDisplayedInFrame()) return; } int lineNumber = 0; int columnNumber = 0; String exceptionSourceURL; if (auto* callFrame = callStack->firstNonNativeCallFrame()) { lineNumber = callFrame->lineNumber(); columnNumber = callFrame->columnNumber(); exceptionSourceURL = callFrame->sourceURL(); } auto errorMessage = retrieveErrorMessage(*exec, vm, exception->value(), scope); globalObject->scriptExecutionContext()->reportException(errorMessage, lineNumber, columnNumber, exceptionSourceURL, exception, callStack->size() ? callStack.ptr() : nullptr, cachedScript); if (exceptionDetails) { exceptionDetails->message = errorMessage; exceptionDetails->lineNumber = lineNumber; exceptionDetails->columnNumber = columnNumber; exceptionDetails->sourceURL = exceptionSourceURL; } } void reportCurrentException(ExecState* exec) { VM& vm = exec->vm(); auto scope = DECLARE_CATCH_SCOPE(vm); auto* exception = scope.exception(); scope.clearException(); reportException(exec, exception); } static JSValue createDOMException(ExecState* exec, ExceptionCode ec, const String* message = nullptr) { if (!ec || ec == ExistingExceptionError) return jsUndefined(); // FIXME: Handle other WebIDL exception types. if (ec == TypeError) { if (!message || message->isEmpty()) return createTypeError(exec); return createTypeError(exec, *message); } if (ec == RangeError) { if (!message || message->isEmpty()) return createRangeError(exec, ASCIILiteral("Bad value")); return createRangeError(exec, *message); } if (ec == StackOverflowError) return createStackOverflowError(exec); // FIXME: All callers to createDOMException need to pass in the correct global object. // For now, we're going to assume the lexicalGlobalObject. Which is wrong in cases like this: // frames[0].document.createElement(null, null); // throws an exception which should have the subframe's prototypes. JSDOMGlobalObject* globalObject = deprecatedGlobalObjectForPrototype(exec); ExceptionCodeDescription description(ec); CString messageCString; if (message) messageCString = message->utf8(); if (message && !message->isEmpty()) { // It is safe to do this because the char* contents of the CString are copied into a new WTF::String before the CString is destroyed. description.description = messageCString.data(); } JSValue errorObject; switch (description.type) { case DOMCoreExceptionType: #if ENABLE(INDEXED_DATABASE) case IDBDatabaseExceptionType: #endif errorObject = toJS(exec, globalObject, DOMCoreException::create(description)); break; case FileExceptionType: errorObject = toJS(exec, globalObject, FileException::create(description)); break; case SQLExceptionType: errorObject = toJS(exec, globalObject, SQLException::create(description)); break; case SVGExceptionType: errorObject = toJS(exec, globalObject, SVGException::create(description)); break; case XPathExceptionType: errorObject = toJS(exec, globalObject, XPathException::create(description)); break; } ASSERT(errorObject); addErrorInfo(exec, asObject(errorObject), true); return errorObject; } JSValue createDOMException(ExecState* exec, ExceptionCode ec, const String& message) { return createDOMException(exec, ec, &message); } JSValue createDOMException(ExecState& state, Exception&& exception) { return createDOMException(&state, exception.code(), exception.releaseMessage()); } void propagateExceptionSlowPath(JSC::ExecState& state, JSC::ThrowScope& throwScope, Exception&& exception) { ASSERT(!throwScope.exception()); throwException(&state, throwScope, createDOMException(state, WTFMove(exception))); } static EncodedJSValue throwTypeError(JSC::ExecState& state, JSC::ThrowScope& scope, const String& errorMessage) { return throwVMTypeError(&state, scope, errorMessage); } static void appendArgumentMustBe(StringBuilder& builder, unsigned argumentIndex, const char* argumentName, const char* interfaceName, const char* functionName) { builder.appendLiteral("Argument "); builder.appendNumber(argumentIndex + 1); builder.appendLiteral(" ('"); builder.append(argumentName); builder.appendLiteral("') to "); if (!functionName) { builder.appendLiteral("the "); builder.append(interfaceName); builder.appendLiteral(" constructor"); } else { builder.append(interfaceName); builder.append('.'); builder.append(functionName); } builder.appendLiteral(" must be "); } JSC::EncodedJSValue reportDeprecatedGetterError(JSC::ExecState& state, const char* interfaceName, const char* attributeName) { auto& context = *jsCast(state.lexicalGlobalObject())->scriptExecutionContext(); context.addConsoleMessage(MessageSource::JS, MessageLevel::Error, makeString("Deprecated attempt to access property '", attributeName, "' on a non-", interfaceName, " object.")); return JSValue::encode(jsUndefined()); } void reportDeprecatedSetterError(JSC::ExecState& state, const char* interfaceName, const char* attributeName) { auto& context = *jsCast(state.lexicalGlobalObject())->scriptExecutionContext(); context.addConsoleMessage(MessageSource::JS, MessageLevel::Error, makeString("Deprecated attempt to set property '", attributeName, "' on a non-", interfaceName, " object.")); } void throwNotSupportedError(JSC::ExecState& state, JSC::ThrowScope& scope) { ASSERT(!scope.exception()); throwException(&state, scope, createDOMException(&state, NOT_SUPPORTED_ERR)); } void throwNotSupportedError(JSC::ExecState& state, JSC::ThrowScope& scope, const char* message) { ASSERT(!scope.exception()); String messageString(message); throwException(&state, scope, createDOMException(&state, NOT_SUPPORTED_ERR, &messageString)); } void throwInvalidStateError(JSC::ExecState& state, JSC::ThrowScope& scope, const char* message) { ASSERT(!scope.exception()); String messageString(message); throwException(&state, scope, createDOMException(&state, INVALID_STATE_ERR, &messageString)); } void throwSecurityError(JSC::ExecState& state, JSC::ThrowScope& scope, const String& message) { ASSERT(!scope.exception()); throwException(&state, scope, createDOMException(&state, SECURITY_ERR, message)); } JSC::EncodedJSValue throwArgumentMustBeEnumError(JSC::ExecState& state, JSC::ThrowScope& scope, unsigned argumentIndex, const char* argumentName, const char* functionInterfaceName, const char* functionName, const char* expectedValues) { StringBuilder builder; appendArgumentMustBe(builder, argumentIndex, argumentName, functionInterfaceName, functionName); builder.appendLiteral("one of: "); builder.append(expectedValues); return throwVMTypeError(&state, scope, builder.toString()); } JSC::EncodedJSValue throwArgumentMustBeFunctionError(JSC::ExecState& state, JSC::ThrowScope& scope, unsigned argumentIndex, const char* argumentName, const char* interfaceName, const char* functionName) { StringBuilder builder; appendArgumentMustBe(builder, argumentIndex, argumentName, interfaceName, functionName); builder.appendLiteral("a function"); return throwVMTypeError(&state, scope, builder.toString()); } JSC::EncodedJSValue throwArgumentTypeError(JSC::ExecState& state, JSC::ThrowScope& scope, unsigned argumentIndex, const char* argumentName, const char* functionInterfaceName, const char* functionName, const char* expectedType) { StringBuilder builder; appendArgumentMustBe(builder, argumentIndex, argumentName, functionInterfaceName, functionName); builder.appendLiteral("an instance of "); builder.append(expectedType); return throwVMTypeError(&state, scope, builder.toString()); } void throwArrayElementTypeError(JSC::ExecState& state, JSC::ThrowScope& scope) { throwTypeError(state, scope, ASCIILiteral("Invalid Array element type")); } void throwAttributeTypeError(JSC::ExecState& state, JSC::ThrowScope& scope, const char* interfaceName, const char* attributeName, const char* expectedType) { throwTypeError(state, scope, makeString("The ", interfaceName, '.', attributeName, " attribute must be an instance of ", expectedType)); } JSC::EncodedJSValue throwRequiredMemberTypeError(JSC::ExecState& state, JSC::ThrowScope& scope, const char* memberName, const char* dictionaryName, const char* expectedType) { StringBuilder builder; builder.appendLiteral("Member "); builder.append(dictionaryName); builder.append('.'); builder.append(memberName); builder.appendLiteral(" is required and must be an instance of "); builder.append(expectedType); return throwVMTypeError(&state, scope, builder.toString()); } JSC::EncodedJSValue throwConstructorScriptExecutionContextUnavailableError(JSC::ExecState& state, JSC::ThrowScope& scope, const char* interfaceName) { return throwVMError(&state, scope, createReferenceError(&state, makeString(interfaceName, " constructor associated execution context is unavailable"))); } void throwSequenceTypeError(JSC::ExecState& state, JSC::ThrowScope& scope) { throwTypeError(state, scope, ASCIILiteral("Value is not a sequence")); } void throwNonFiniteTypeError(ExecState& state, JSC::ThrowScope& scope) { throwTypeError(&state, scope, ASCIILiteral("The provided value is non-finite")); } String makeGetterTypeErrorMessage(const char* interfaceName, const char* attributeName) { return makeString("The ", interfaceName, '.', attributeName, " getter can only be used on instances of ", interfaceName); } JSC::EncodedJSValue throwGetterTypeError(JSC::ExecState& state, JSC::ThrowScope& scope, const char* interfaceName, const char* attributeName) { return throwVMTypeError(&state, scope, makeGetterTypeErrorMessage(interfaceName, attributeName)); } JSC::EncodedJSValue rejectPromiseWithGetterTypeError(JSC::ExecState& state, const char* interfaceName, const char* attributeName) { return createRejectedPromiseWithTypeError(state, makeGetterTypeErrorMessage(interfaceName, attributeName)); } bool throwSetterTypeError(JSC::ExecState& state, JSC::ThrowScope& scope, const char* interfaceName, const char* attributeName) { throwTypeError(state, scope, makeString("The ", interfaceName, '.', attributeName, " setter can only be used on instances of ", interfaceName)); return false; } String makeThisTypeErrorMessage(const char* interfaceName, const char* functionName) { return makeString("Can only call ", interfaceName, '.', functionName, " on instances of ", interfaceName); } EncodedJSValue throwThisTypeError(JSC::ExecState& state, JSC::ThrowScope& scope, const char* interfaceName, const char* functionName) { return throwTypeError(state, scope, makeThisTypeErrorMessage(interfaceName, functionName)); } JSC::EncodedJSValue rejectPromiseWithThisTypeError(DeferredPromise& promise, const char* interfaceName, const char* methodName) { promise.reject(TypeError, makeThisTypeErrorMessage(interfaceName, methodName)); return JSValue::encode(jsUndefined()); } JSC::EncodedJSValue rejectPromiseWithThisTypeError(JSC::ExecState& state, const char* interfaceName, const char* methodName) { return createRejectedPromiseWithTypeError(state, makeThisTypeErrorMessage(interfaceName, methodName)); } void throwDOMSyntaxError(JSC::ExecState& state, JSC::ThrowScope& scope) { ASSERT(!scope.exception()); throwException(&state, scope, createDOMException(&state, SYNTAX_ERR)); } void throwDataCloneError(JSC::ExecState& state, JSC::ThrowScope& scope) { ASSERT(!scope.exception()); throwException(&state, scope, createDOMException(&state, DATA_CLONE_ERR)); } void throwIndexSizeError(JSC::ExecState& state, JSC::ThrowScope& scope) { ASSERT(!scope.exception()); throwException(&state, scope, createDOMException(&state, INDEX_SIZE_ERR)); } void throwTypeMismatchError(JSC::ExecState& state, JSC::ThrowScope& scope) { ASSERT(!scope.exception()); throwException(&state, scope, createDOMException(&state, TYPE_MISMATCH_ERR)); } } // namespace WebCore