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 | |
39 | namespace WebCore { |
40 | using namespace JSC; |
41 | |
42 | void 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 | |
56 | String 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 | |
73 | void 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 | |
114 | void 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 | |
123 | JSValue 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 | |
155 | JSValue createDOMException(ExecState& state, Exception&& exception) |
156 | { |
157 | return createDOMException(&state, exception.code(), exception.releaseMessage()); |
158 | } |
159 | |
160 | void propagateExceptionSlowPath(JSC::ExecState& state, JSC::ThrowScope& throwScope, Exception&& exception) |
161 | { |
162 | throwScope.assertNoException(); |
163 | throwException(&state, throwScope, createDOMException(state, WTFMove(exception))); |
164 | } |
165 | |
166 | static EncodedJSValue throwTypeError(JSC::ExecState& state, JSC::ThrowScope& scope, const String& errorMessage) |
167 | { |
168 | return throwVMTypeError(&state, scope, errorMessage); |
169 | } |
170 | |
171 | static 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 | |
190 | void throwNotSupportedError(JSC::ExecState& state, JSC::ThrowScope& scope, ASCIILiteral message) |
191 | { |
192 | scope.assertNoException(); |
193 | throwException(&state, scope, createDOMException(&state, NotSupportedError, message)); |
194 | } |
195 | |
196 | void throwInvalidStateError(JSC::ExecState& state, JSC::ThrowScope& scope, ASCIILiteral message) |
197 | { |
198 | scope.assertNoException(); |
199 | throwException(&state, scope, createDOMException(&state, InvalidStateError, message)); |
200 | } |
201 | |
202 | void throwSecurityError(JSC::ExecState& state, JSC::ThrowScope& scope, const String& message) |
203 | { |
204 | scope.assertNoException(); |
205 | throwException(&state, scope, createDOMException(&state, SecurityError, message)); |
206 | } |
207 | |
208 | JSC::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 | |
217 | JSC::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 | |
225 | JSC::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 | |
234 | void 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 | |
239 | JSC::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 | |
251 | JSC::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 | |
256 | void throwSequenceTypeError(JSC::ExecState& state, JSC::ThrowScope& scope) |
257 | { |
258 | throwTypeError(state, scope, "Value is not a sequence"_s ); |
259 | } |
260 | |
261 | void throwNonFiniteTypeError(ExecState& state, JSC::ThrowScope& scope) |
262 | { |
263 | throwTypeError(&state, scope, "The provided value is non-finite"_s ); |
264 | } |
265 | |
266 | String 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 | |
271 | JSC::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 | |
276 | JSC::EncodedJSValue rejectPromiseWithGetterTypeError(JSC::ExecState& state, const char* interfaceName, const char* attributeName) |
277 | { |
278 | return createRejectedPromiseWithTypeError(state, makeGetterTypeErrorMessage(interfaceName, attributeName), RejectedPromiseWithTypeErrorCause::NativeGetter); |
279 | } |
280 | |
281 | bool 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 | |
287 | String makeThisTypeErrorMessage(const char* interfaceName, const char* functionName) |
288 | { |
289 | return makeString("Can only call " , interfaceName, '.', functionName, " on instances of " , interfaceName); |
290 | } |
291 | |
292 | EncodedJSValue throwThisTypeError(JSC::ExecState& state, JSC::ThrowScope& scope, const char* interfaceName, const char* functionName) |
293 | { |
294 | return throwTypeError(state, scope, makeThisTypeErrorMessage(interfaceName, functionName)); |
295 | } |
296 | |
297 | JSC::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 | |
303 | JSC::EncodedJSValue rejectPromiseWithThisTypeError(JSC::ExecState& state, const char* interfaceName, const char* methodName) |
304 | { |
305 | return createRejectedPromiseWithTypeError(state, makeThisTypeErrorMessage(interfaceName, methodName), RejectedPromiseWithTypeErrorCause::InvalidThis); |
306 | } |
307 | |
308 | void throwDOMSyntaxError(JSC::ExecState& state, JSC::ThrowScope& scope, ASCIILiteral message) |
309 | { |
310 | scope.assertNoException(); |
311 | throwException(&state, scope, createDOMException(&state, SyntaxError, message)); |
312 | } |
313 | |
314 | void throwDataCloneError(JSC::ExecState& state, JSC::ThrowScope& scope) |
315 | { |
316 | scope.assertNoException(); |
317 | throwException(&state, scope, createDOMException(&state, DataCloneError)); |
318 | } |
319 | |
320 | } // namespace WebCore |
321 | |