1 | /* |
2 | * Copyright (C) 2008-2019 Apple Inc. All rights reserved. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
7 | * |
8 | * 1. Redistributions of source code must retain the above copyright |
9 | * notice, this list of conditions and the following disclaimer. |
10 | * 2. Redistributions in binary form must reproduce the above copyright |
11 | * notice, this list of conditions and the following disclaimer in the |
12 | * documentation and/or other materials provided with the distribution. |
13 | * 3. Neither the name of Apple Inc. ("Apple") nor the names of |
14 | * its contributors may be used to endorse or promote products derived |
15 | * from this software without specific prior written permission. |
16 | * |
17 | * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
18 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
19 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
20 | * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
21 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
22 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
23 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
24 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
26 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
27 | */ |
28 | |
29 | #include "config.h" |
30 | #include "ExceptionHelpers.h" |
31 | |
32 | #include "CallFrame.h" |
33 | #include "CatchScope.h" |
34 | #include "CodeBlock.h" |
35 | #include "ErrorHandlingScope.h" |
36 | #include "Exception.h" |
37 | #include "Interpreter.h" |
38 | #include "JSCInlines.h" |
39 | #include "JSGlobalObjectFunctions.h" |
40 | #include "RuntimeType.h" |
41 | #include <wtf/text/StringBuilder.h> |
42 | #include <wtf/text/StringView.h> |
43 | |
44 | namespace JSC { |
45 | |
46 | STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(TerminatedExecutionError); |
47 | |
48 | const ClassInfo TerminatedExecutionError::s_info = { "TerminatedExecutionError" , &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(TerminatedExecutionError) }; |
49 | |
50 | JSValue TerminatedExecutionError::defaultValue(const JSObject*, JSGlobalObject* globalObject, PreferredPrimitiveType hint) |
51 | { |
52 | if (hint == PreferString) |
53 | return jsNontrivialString(globalObject->vm(), String("JavaScript execution terminated."_s )); |
54 | return JSValue(PNaN); |
55 | } |
56 | |
57 | JSObject* createTerminatedExecutionException(VM* vm) |
58 | { |
59 | return TerminatedExecutionError::create(*vm); |
60 | } |
61 | |
62 | bool isTerminatedExecutionException(VM& vm, Exception* exception) |
63 | { |
64 | if (!exception->value().isObject()) |
65 | return false; |
66 | |
67 | return exception->value().inherits<TerminatedExecutionError>(vm); |
68 | } |
69 | |
70 | JSObject* createStackOverflowError(JSGlobalObject* globalObject) |
71 | { |
72 | auto* error = createRangeError(globalObject, "Maximum call stack size exceeded."_s ); |
73 | jsCast<ErrorInstance*>(error)->setStackOverflowError(); |
74 | return error; |
75 | } |
76 | |
77 | JSObject* createUndefinedVariableError(JSGlobalObject* globalObject, const Identifier& ident) |
78 | { |
79 | if (ident.isPrivateName()) |
80 | return createReferenceError(globalObject, makeString("Can't find private variable: PrivateSymbol." , ident.string())); |
81 | return createReferenceError(globalObject, makeString("Can't find variable: " , ident.string())); |
82 | } |
83 | |
84 | String errorDescriptionForValue(JSGlobalObject* globalObject, JSValue v) |
85 | { |
86 | if (v.isString()) { |
87 | String string = asString(v)->value(globalObject); |
88 | if (!string) |
89 | return string; |
90 | return tryMakeString('"', string, '"'); |
91 | } |
92 | |
93 | if (v.isSymbol()) |
94 | return asSymbol(v)->descriptiveString(); |
95 | if (v.isObject()) { |
96 | VM& vm = globalObject->vm(); |
97 | CallData callData; |
98 | JSObject* object = asObject(v); |
99 | if (object->methodTable(vm)->getCallData(object, callData) != CallType::None) |
100 | return vm.smallStrings.functionString()->value(globalObject); |
101 | return JSObject::calculatedClassName(object); |
102 | } |
103 | return v.toString(globalObject)->value(globalObject); |
104 | } |
105 | |
106 | static String defaultApproximateSourceError(const String& originalMessage, const String& sourceText) |
107 | { |
108 | return makeString(originalMessage, " (near '..." , sourceText, "...')" ); |
109 | } |
110 | |
111 | String defaultSourceAppender(const String& originalMessage, const String& sourceText, RuntimeType, ErrorInstance::SourceTextWhereErrorOccurred occurrence) |
112 | { |
113 | if (occurrence == ErrorInstance::FoundApproximateSource) |
114 | return defaultApproximateSourceError(originalMessage, sourceText); |
115 | |
116 | ASSERT(occurrence == ErrorInstance::FoundExactSource); |
117 | return makeString(originalMessage, " (evaluating '" , sourceText, "')" ); |
118 | } |
119 | |
120 | static String functionCallBase(const String& sourceText) |
121 | { |
122 | // This function retrieves the 'foo.bar' substring from 'foo.bar(baz)'. |
123 | // FIXME: This function has simple processing of /* */ style comments. |
124 | // It doesn't properly handle embedded comments of string literals that contain |
125 | // parenthesis or comment constructs, e.g. foo.bar("/abc\)*/"). |
126 | // https://bugs.webkit.org/show_bug.cgi?id=146304 |
127 | |
128 | unsigned sourceLength = sourceText.length(); |
129 | unsigned idx = sourceLength - 1; |
130 | if (sourceLength < 2 || sourceText[idx] != ')') { |
131 | // For function calls that have many new lines in between their open parenthesis |
132 | // and their closing parenthesis, the text range passed into the message appender |
133 | // will not inlcude the text in between these parentheses, it will just be the desired |
134 | // text that precedes the parentheses. |
135 | return String(); |
136 | } |
137 | |
138 | unsigned parenStack = 1; |
139 | bool = false; |
140 | idx -= 1; |
141 | // Note that we're scanning text right to left instead of the more common left to right, |
142 | // so syntax detection is backwards. |
143 | while (parenStack && idx) { |
144 | UChar curChar = sourceText[idx]; |
145 | if (isInMultiLineComment) { |
146 | if (curChar == '*' && sourceText[idx - 1] == '/') { |
147 | isInMultiLineComment = false; |
148 | --idx; |
149 | } |
150 | } else if (curChar == '(') |
151 | --parenStack; |
152 | else if (curChar == ')') |
153 | ++parenStack; |
154 | else if (curChar == '/' && sourceText[idx - 1] == '*') { |
155 | isInMultiLineComment = true; |
156 | --idx; |
157 | } |
158 | |
159 | if (idx) |
160 | --idx; |
161 | } |
162 | |
163 | if (parenStack) { |
164 | // As noted in the FIXME at the top of this function, there are bugs |
165 | // in the above string processing. This algorithm is mostly best effort |
166 | // and it works for most JS text in practice. However, if we determine |
167 | // that the algorithm failed, we should just return the empty value. |
168 | return String(); |
169 | } |
170 | |
171 | // Don't display the ?. of an optional call. |
172 | if (idx > 1 && sourceText[idx] == '.' && sourceText[idx - 1] == '?') |
173 | idx -= 2; |
174 | |
175 | return sourceText.left(idx + 1); |
176 | } |
177 | |
178 | static String notAFunctionSourceAppender(const String& originalMessage, const String& sourceText, RuntimeType type, ErrorInstance::SourceTextWhereErrorOccurred occurrence) |
179 | { |
180 | ASSERT(type != TypeFunction); |
181 | |
182 | if (occurrence == ErrorInstance::FoundApproximateSource) |
183 | return defaultApproximateSourceError(originalMessage, sourceText); |
184 | |
185 | ASSERT(occurrence == ErrorInstance::FoundExactSource); |
186 | auto notAFunctionIndex = originalMessage.reverseFind("is not a function" ); |
187 | RELEASE_ASSERT(notAFunctionIndex != notFound); |
188 | StringView displayValue; |
189 | if (originalMessage.is8Bit()) |
190 | displayValue = StringView(originalMessage.characters8(), notAFunctionIndex - 1); |
191 | else |
192 | displayValue = StringView(originalMessage.characters16(), notAFunctionIndex - 1); |
193 | |
194 | String base = functionCallBase(sourceText); |
195 | if (!base) |
196 | return defaultApproximateSourceError(originalMessage, sourceText); |
197 | StringBuilder builder(StringBuilder::OverflowHandler::RecordOverflow); |
198 | builder.append(base, " is not a function. (In '" , sourceText, "', '" , base, "' is " ); |
199 | if (type == TypeSymbol) |
200 | builder.appendLiteral("a Symbol" ); |
201 | else { |
202 | if (type == TypeObject) |
203 | builder.appendLiteral("an instance of " ); |
204 | builder.append(displayValue); |
205 | } |
206 | builder.append(')'); |
207 | |
208 | if (builder.hasOverflowed()) |
209 | return "object is not a function."_s ; |
210 | |
211 | return builder.toString(); |
212 | } |
213 | |
214 | static String invalidParameterInSourceAppender(const String& originalMessage, const String& sourceText, RuntimeType type, ErrorInstance::SourceTextWhereErrorOccurred occurrence) |
215 | { |
216 | ASSERT_UNUSED(type, type != TypeObject); |
217 | |
218 | if (occurrence == ErrorInstance::FoundApproximateSource) |
219 | return defaultApproximateSourceError(originalMessage, sourceText); |
220 | |
221 | ASSERT(occurrence == ErrorInstance::FoundExactSource); |
222 | auto inIndex = sourceText.reverseFind("in" ); |
223 | if (inIndex == notFound) { |
224 | // This should basically never happen, since JS code must use the literal |
225 | // text "in" for the `in` operation. However, if we fail to find "in" |
226 | // for any reason, just fail gracefully. |
227 | return originalMessage; |
228 | } |
229 | if (sourceText.find("in" ) != inIndex) |
230 | return makeString(originalMessage, " (evaluating '" , sourceText, "')" ); |
231 | |
232 | static constexpr unsigned inLength = 2; |
233 | String rightHandSide = sourceText.substring(inIndex + inLength).simplifyWhiteSpace(); |
234 | return makeString(rightHandSide, " is not an Object. (evaluating '" , sourceText, "')" ); |
235 | } |
236 | |
237 | inline String invalidParameterInstanceofSourceAppender(const String& content, const String& originalMessage, const String& sourceText, RuntimeType, ErrorInstance::SourceTextWhereErrorOccurred occurrence) |
238 | { |
239 | if (occurrence == ErrorInstance::FoundApproximateSource) |
240 | return defaultApproximateSourceError(originalMessage, sourceText); |
241 | |
242 | ASSERT(occurrence == ErrorInstance::FoundExactSource); |
243 | auto instanceofIndex = sourceText.reverseFind("instanceof" ); |
244 | RELEASE_ASSERT(instanceofIndex != notFound); |
245 | if (sourceText.find("instanceof" ) != instanceofIndex) |
246 | return makeString(originalMessage, " (evaluating '" , sourceText, "')" ); |
247 | |
248 | static constexpr unsigned instanceofLength = 10; |
249 | String rightHandSide = sourceText.substring(instanceofIndex + instanceofLength).simplifyWhiteSpace(); |
250 | return makeString(rightHandSide, content, ". (evaluating '" , sourceText, "')" ); |
251 | } |
252 | |
253 | static String invalidParameterInstanceofNotFunctionSourceAppender(const String& originalMessage, const String& sourceText, RuntimeType runtimeType, ErrorInstance::SourceTextWhereErrorOccurred occurrence) |
254 | { |
255 | return invalidParameterInstanceofSourceAppender(" is not a function"_s , originalMessage, sourceText, runtimeType, occurrence); |
256 | } |
257 | |
258 | static String invalidParameterInstanceofhasInstanceValueNotFunctionSourceAppender(const String& originalMessage, const String& sourceText, RuntimeType runtimeType, ErrorInstance::SourceTextWhereErrorOccurred occurrence) |
259 | { |
260 | return invalidParameterInstanceofSourceAppender("[Symbol.hasInstance] is not a function, undefined, or null"_s , originalMessage, sourceText, runtimeType, occurrence); |
261 | } |
262 | |
263 | JSObject* createError(JSGlobalObject* globalObject, JSValue value, const String& message, ErrorInstance::SourceAppender appender) |
264 | { |
265 | VM& vm = globalObject->vm(); |
266 | auto scope = DECLARE_CATCH_SCOPE(vm); |
267 | |
268 | String valueDescription = errorDescriptionForValue(globalObject, value); |
269 | ASSERT(scope.exception() || !!valueDescription); |
270 | if (!valueDescription) { |
271 | scope.clearException(); |
272 | return createOutOfMemoryError(globalObject); |
273 | } |
274 | String errorMessage = tryMakeString(valueDescription, ' ', message); |
275 | if (!errorMessage) |
276 | return createOutOfMemoryError(globalObject); |
277 | scope.assertNoException(); |
278 | JSObject* exception = createTypeError(globalObject, errorMessage, appender, runtimeTypeForValue(vm, value)); |
279 | ASSERT(exception->isErrorInstance()); |
280 | |
281 | return exception; |
282 | } |
283 | |
284 | JSObject* createInvalidFunctionApplyParameterError(JSGlobalObject* globalObject, JSValue value) |
285 | { |
286 | return createTypeError(globalObject, "second argument to Function.prototype.apply must be an Array-like object"_s , defaultSourceAppender, runtimeTypeForValue(globalObject->vm(), value)); |
287 | } |
288 | |
289 | JSObject* createInvalidInParameterError(JSGlobalObject* globalObject, JSValue value) |
290 | { |
291 | return createError(globalObject, value, "is not an Object."_s , invalidParameterInSourceAppender); |
292 | } |
293 | |
294 | JSObject* createInvalidInstanceofParameterErrorNotFunction(JSGlobalObject* globalObject, JSValue value) |
295 | { |
296 | return createError(globalObject, value, " is not a function"_s , invalidParameterInstanceofNotFunctionSourceAppender); |
297 | } |
298 | |
299 | JSObject* createInvalidInstanceofParameterErrorHasInstanceValueNotFunction(JSGlobalObject* globalObject, JSValue value) |
300 | { |
301 | return createError(globalObject, value, "[Symbol.hasInstance] is not a function, undefined, or null"_s , invalidParameterInstanceofhasInstanceValueNotFunctionSourceAppender); |
302 | } |
303 | |
304 | JSObject* createNotAConstructorError(JSGlobalObject* globalObject, JSValue value) |
305 | { |
306 | return createError(globalObject, value, "is not a constructor"_s , defaultSourceAppender); |
307 | } |
308 | |
309 | JSObject* createNotAFunctionError(JSGlobalObject* globalObject, JSValue value) |
310 | { |
311 | return createError(globalObject, value, "is not a function"_s , notAFunctionSourceAppender); |
312 | } |
313 | |
314 | JSObject* createNotAnObjectError(JSGlobalObject* globalObject, JSValue value) |
315 | { |
316 | return createError(globalObject, value, "is not an object"_s , defaultSourceAppender); |
317 | } |
318 | |
319 | JSObject* createErrorForInvalidGlobalAssignment(JSGlobalObject* globalObject, const String& propertyName) |
320 | { |
321 | return createReferenceError(globalObject, makeString("Strict mode forbids implicit creation of global property '" , propertyName, '\'')); |
322 | } |
323 | |
324 | JSObject* createTDZError(JSGlobalObject* globalObject) |
325 | { |
326 | return createReferenceError(globalObject, "Cannot access uninitialized variable." ); |
327 | } |
328 | |
329 | Exception* throwOutOfMemoryError(JSGlobalObject* globalObject, ThrowScope& scope) |
330 | { |
331 | return throwException(globalObject, scope, createOutOfMemoryError(globalObject)); |
332 | } |
333 | |
334 | Exception* throwStackOverflowError(JSGlobalObject* globalObject, ThrowScope& scope) |
335 | { |
336 | VM& vm = globalObject->vm(); |
337 | ErrorHandlingScope errorScope(vm); |
338 | return throwException(globalObject, scope, createStackOverflowError(globalObject)); |
339 | } |
340 | |
341 | Exception* throwTerminatedExecutionException(JSGlobalObject* globalObject, ThrowScope& scope) |
342 | { |
343 | VM& vm = globalObject->vm(); |
344 | ErrorHandlingScope errorScope(vm); |
345 | return throwException(globalObject, scope, createTerminatedExecutionException(&vm)); |
346 | } |
347 | |
348 | } // namespace JSC |
349 | |