1/*
2 * Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
3 * Copyright (C) 2004-2017 Apple Inc. All rights reserved.
4 * Copyright (C) 2007 Samuel Weinig <sam@webkit.org>
5 * Copyright (C) 2013 Michael Pruett <michael@68k.org>
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22#include "config.h"
23#include "JSDOMExceptionHandling.h"
24
25#include "CachedScript.h"
26#include "DOMException.h"
27#include "DOMWindow.h"
28#include "JSDOMException.h"
29#include "JSDOMPromiseDeferred.h"
30#include "JSDOMWindow.h"
31#include "ScriptExecutionContext.h"
32#include <JavaScriptCore/ErrorHandlingScope.h>
33#include <JavaScriptCore/Exception.h>
34#include <JavaScriptCore/ExceptionHelpers.h>
35#include <JavaScriptCore/ScriptCallStack.h>
36#include <JavaScriptCore/ScriptCallStackFactory.h>
37#include <wtf/text/StringBuilder.h>
38
39namespace WebCore {
40using namespace JSC;
41
42void reportException(ExecState* exec, JSValue exceptionValue, CachedScript* cachedScript)
43{
44 VM& vm = exec->vm();
45 RELEASE_ASSERT(vm.currentThreadIsHoldingAPILock());
46 auto* exception = jsDynamicCast<JSC::Exception*>(vm, exceptionValue);
47 if (!exception) {
48 exception = vm.lastException();
49 if (!exception)
50 exception = JSC::Exception::create(exec->vm(), exceptionValue, JSC::Exception::DoNotCaptureStack);
51 }
52
53 reportException(exec, exception, cachedScript);
54}
55
56String retrieveErrorMessage(ExecState& state, VM& vm, JSValue exception, CatchScope& catchScope)
57{
58 // FIXME: <http://webkit.org/b/115087> Web Inspector: WebCore::reportException should not evaluate JavaScript handling exceptions
59 // If this is a custom exception object, call toString on it to try and get a nice string representation for the exception.
60 String errorMessage;
61 if (auto* error = jsDynamicCast<ErrorInstance*>(vm, exception))
62 errorMessage = error->sanitizedToString(&state);
63 else
64 errorMessage = exception.toWTFString(&state);
65
66 // We need to clear any new exception that may be thrown in the toString() call above.
67 // reportException() is not supposed to be making new exceptions.
68 catchScope.clearException();
69 vm.clearLastException();
70 return errorMessage;
71}
72
73void reportException(ExecState* exec, JSC::Exception* exception, CachedScript* cachedScript, ExceptionDetails* exceptionDetails)
74{
75 VM& vm = exec->vm();
76 auto scope = DECLARE_CATCH_SCOPE(vm);
77
78 RELEASE_ASSERT(vm.currentThreadIsHoldingAPILock());
79 if (isTerminatedExecutionException(vm, exception))
80 return;
81
82 ErrorHandlingScope errorScope(exec->vm());
83
84 auto callStack = Inspector::createScriptCallStackFromException(exec, exception);
85 scope.clearException();
86 vm.clearLastException();
87
88 auto* globalObject = jsCast<JSDOMGlobalObject*>(exec->lexicalGlobalObject());
89 if (auto* window = jsDynamicCast<JSDOMWindow*>(vm, globalObject)) {
90 if (!window->wrapped().isCurrentlyDisplayedInFrame())
91 return;
92 }
93
94 int lineNumber = 0;
95 int columnNumber = 0;
96 String exceptionSourceURL;
97 if (auto* callFrame = callStack->firstNonNativeCallFrame()) {
98 lineNumber = callFrame->lineNumber();
99 columnNumber = callFrame->columnNumber();
100 exceptionSourceURL = callFrame->sourceURL();
101 }
102
103 auto errorMessage = retrieveErrorMessage(*exec, vm, exception->value(), scope);
104 globalObject->scriptExecutionContext()->reportException(errorMessage, lineNumber, columnNumber, exceptionSourceURL, exception, callStack->size() ? callStack.ptr() : nullptr, cachedScript);
105
106 if (exceptionDetails) {
107 exceptionDetails->message = errorMessage;
108 exceptionDetails->lineNumber = lineNumber;
109 exceptionDetails->columnNumber = columnNumber;
110 exceptionDetails->sourceURL = exceptionSourceURL;
111 }
112}
113
114void reportCurrentException(ExecState* exec)
115{
116 VM& vm = exec->vm();
117 auto scope = DECLARE_CATCH_SCOPE(vm);
118 auto* exception = scope.exception();
119 scope.clearException();
120 reportException(exec, exception);
121}
122
123JSValue createDOMException(ExecState* exec, ExceptionCode ec, const String& message)
124{
125 if (ec == ExistingExceptionError)
126 return jsUndefined();
127
128 // FIXME: Handle other WebIDL exception types.
129 if (ec == TypeError) {
130 if (message.isEmpty())
131 return createTypeError(exec);
132 return createTypeError(exec, message);
133 }
134
135 if (ec == RangeError) {
136 if (message.isEmpty())
137 return createRangeError(exec, "Bad value"_s);
138 return createRangeError(exec, message);
139 }
140
141 if (ec == StackOverflowError)
142 return createStackOverflowError(exec);
143
144 // FIXME: All callers to createDOMException need to pass in the correct global object.
145 // For now, we're going to assume the lexicalGlobalObject. Which is wrong in cases like this:
146 // frames[0].document.createElement(null, null); // throws an exception which should have the subframe's prototypes.
147 JSDOMGlobalObject* globalObject = deprecatedGlobalObjectForPrototype(exec);
148 JSValue errorObject = toJS(exec, globalObject, DOMException::create(ec, message));
149
150 ASSERT(errorObject);
151 addErrorInfo(exec, asObject(errorObject), true);
152 return errorObject;
153}
154
155JSValue createDOMException(ExecState& state, Exception&& exception)
156{
157 return createDOMException(&state, exception.code(), exception.releaseMessage());
158}
159
160void propagateExceptionSlowPath(JSC::ExecState& state, JSC::ThrowScope& throwScope, Exception&& exception)
161{
162 throwScope.assertNoException();
163 throwException(&state, throwScope, createDOMException(state, WTFMove(exception)));
164}
165
166static EncodedJSValue throwTypeError(JSC::ExecState& state, JSC::ThrowScope& scope, const String& errorMessage)
167{
168 return throwVMTypeError(&state, scope, errorMessage);
169}
170
171static void appendArgumentMustBe(StringBuilder& builder, unsigned argumentIndex, const char* argumentName, const char* interfaceName, const char* functionName)
172{
173 builder.appendLiteral("Argument ");
174 builder.appendNumber(argumentIndex + 1);
175 builder.appendLiteral(" ('");
176 builder.append(argumentName);
177 builder.appendLiteral("') to ");
178 if (!functionName) {
179 builder.appendLiteral("the ");
180 builder.append(interfaceName);
181 builder.appendLiteral(" constructor");
182 } else {
183 builder.append(interfaceName);
184 builder.append('.');
185 builder.append(functionName);
186 }
187 builder.appendLiteral(" must be ");
188}
189
190void throwNotSupportedError(JSC::ExecState& state, JSC::ThrowScope& scope, ASCIILiteral message)
191{
192 scope.assertNoException();
193 throwException(&state, scope, createDOMException(&state, NotSupportedError, message));
194}
195
196void throwInvalidStateError(JSC::ExecState& state, JSC::ThrowScope& scope, ASCIILiteral message)
197{
198 scope.assertNoException();
199 throwException(&state, scope, createDOMException(&state, InvalidStateError, message));
200}
201
202void throwSecurityError(JSC::ExecState& state, JSC::ThrowScope& scope, const String& message)
203{
204 scope.assertNoException();
205 throwException(&state, scope, createDOMException(&state, SecurityError, message));
206}
207
208JSC::EncodedJSValue throwArgumentMustBeEnumError(JSC::ExecState& state, JSC::ThrowScope& scope, unsigned argumentIndex, const char* argumentName, const char* functionInterfaceName, const char* functionName, const char* expectedValues)
209{
210 StringBuilder builder;
211 appendArgumentMustBe(builder, argumentIndex, argumentName, functionInterfaceName, functionName);
212 builder.appendLiteral("one of: ");
213 builder.append(expectedValues);
214 return throwVMTypeError(&state, scope, builder.toString());
215}
216
217JSC::EncodedJSValue throwArgumentMustBeFunctionError(JSC::ExecState& state, JSC::ThrowScope& scope, unsigned argumentIndex, const char* argumentName, const char* interfaceName, const char* functionName)
218{
219 StringBuilder builder;
220 appendArgumentMustBe(builder, argumentIndex, argumentName, interfaceName, functionName);
221 builder.appendLiteral("a function");
222 return throwVMTypeError(&state, scope, builder.toString());
223}
224
225JSC::EncodedJSValue throwArgumentTypeError(JSC::ExecState& state, JSC::ThrowScope& scope, unsigned argumentIndex, const char* argumentName, const char* functionInterfaceName, const char* functionName, const char* expectedType)
226{
227 StringBuilder builder;
228 appendArgumentMustBe(builder, argumentIndex, argumentName, functionInterfaceName, functionName);
229 builder.appendLiteral("an instance of ");
230 builder.append(expectedType);
231 return throwVMTypeError(&state, scope, builder.toString());
232}
233
234void throwAttributeTypeError(JSC::ExecState& state, JSC::ThrowScope& scope, const char* interfaceName, const char* attributeName, const char* expectedType)
235{
236 throwTypeError(state, scope, makeString("The ", interfaceName, '.', attributeName, " attribute must be an instance of ", expectedType));
237}
238
239JSC::EncodedJSValue throwRequiredMemberTypeError(JSC::ExecState& state, JSC::ThrowScope& scope, const char* memberName, const char* dictionaryName, const char* expectedType)
240{
241 StringBuilder builder;
242 builder.appendLiteral("Member ");
243 builder.append(dictionaryName);
244 builder.append('.');
245 builder.append(memberName);
246 builder.appendLiteral(" is required and must be an instance of ");
247 builder.append(expectedType);
248 return throwVMTypeError(&state, scope, builder.toString());
249}
250
251JSC::EncodedJSValue throwConstructorScriptExecutionContextUnavailableError(JSC::ExecState& state, JSC::ThrowScope& scope, const char* interfaceName)
252{
253 return throwVMError(&state, scope, createReferenceError(&state, makeString(interfaceName, " constructor associated execution context is unavailable")));
254}
255
256void throwSequenceTypeError(JSC::ExecState& state, JSC::ThrowScope& scope)
257{
258 throwTypeError(state, scope, "Value is not a sequence"_s);
259}
260
261void throwNonFiniteTypeError(ExecState& state, JSC::ThrowScope& scope)
262{
263 throwTypeError(&state, scope, "The provided value is non-finite"_s);
264}
265
266String makeGetterTypeErrorMessage(const char* interfaceName, const char* attributeName)
267{
268 return makeString("The ", interfaceName, '.', attributeName, " getter can only be used on instances of ", interfaceName);
269}
270
271JSC::EncodedJSValue throwGetterTypeError(JSC::ExecState& state, JSC::ThrowScope& scope, const char* interfaceName, const char* attributeName)
272{
273 return throwVMGetterTypeError(&state, scope, makeGetterTypeErrorMessage(interfaceName, attributeName));
274}
275
276JSC::EncodedJSValue rejectPromiseWithGetterTypeError(JSC::ExecState& state, const char* interfaceName, const char* attributeName)
277{
278 return createRejectedPromiseWithTypeError(state, makeGetterTypeErrorMessage(interfaceName, attributeName), RejectedPromiseWithTypeErrorCause::NativeGetter);
279}
280
281bool throwSetterTypeError(JSC::ExecState& state, JSC::ThrowScope& scope, const char* interfaceName, const char* attributeName)
282{
283 throwTypeError(state, scope, makeString("The ", interfaceName, '.', attributeName, " setter can only be used on instances of ", interfaceName));
284 return false;
285}
286
287String makeThisTypeErrorMessage(const char* interfaceName, const char* functionName)
288{
289 return makeString("Can only call ", interfaceName, '.', functionName, " on instances of ", interfaceName);
290}
291
292EncodedJSValue throwThisTypeError(JSC::ExecState& state, JSC::ThrowScope& scope, const char* interfaceName, const char* functionName)
293{
294 return throwTypeError(state, scope, makeThisTypeErrorMessage(interfaceName, functionName));
295}
296
297JSC::EncodedJSValue rejectPromiseWithThisTypeError(DeferredPromise& promise, const char* interfaceName, const char* methodName)
298{
299 promise.reject(TypeError, makeThisTypeErrorMessage(interfaceName, methodName));
300 return JSValue::encode(jsUndefined());
301}
302
303JSC::EncodedJSValue rejectPromiseWithThisTypeError(JSC::ExecState& state, const char* interfaceName, const char* methodName)
304{
305 return createRejectedPromiseWithTypeError(state, makeThisTypeErrorMessage(interfaceName, methodName), RejectedPromiseWithTypeErrorCause::InvalidThis);
306}
307
308void throwDOMSyntaxError(JSC::ExecState& state, JSC::ThrowScope& scope, ASCIILiteral message)
309{
310 scope.assertNoException();
311 throwException(&state, scope, createDOMException(&state, SyntaxError, message));
312}
313
314void throwDataCloneError(JSC::ExecState& state, JSC::ThrowScope& scope)
315{
316 scope.assertNoException();
317 throwException(&state, scope, createDOMException(&state, DataCloneError));
318}
319
320} // namespace WebCore
321