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*, ExecState* exec, PreferredPrimitiveType hint) |
51 | { |
52 | if (hint == PreferString) |
53 | return jsNontrivialString(exec, 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(ExecState* exec) |
71 | { |
72 | return createStackOverflowError(exec, exec->lexicalGlobalObject()); |
73 | } |
74 | |
75 | JSObject* createStackOverflowError(ExecState* exec, JSGlobalObject* globalObject) |
76 | { |
77 | auto* error = createRangeError(exec, globalObject, "Maximum call stack size exceeded."_s ); |
78 | jsCast<ErrorInstance*>(error)->setStackOverflowError(); |
79 | return error; |
80 | } |
81 | |
82 | JSObject* createUndefinedVariableError(ExecState* exec, const Identifier& ident) |
83 | { |
84 | if (ident.isPrivateName()) { |
85 | String message(makeString("Can't find private variable: PrivateSymbol." , ident.string())); |
86 | return createReferenceError(exec, message); |
87 | } |
88 | String message(makeString("Can't find variable: " , ident.string())); |
89 | return createReferenceError(exec, message); |
90 | } |
91 | |
92 | String errorDescriptionForValue(ExecState* exec, JSValue v) |
93 | { |
94 | if (v.isString()) { |
95 | String string = asString(v)->value(exec); |
96 | if (!string) |
97 | return string; |
98 | return tryMakeString('"', string, '"'); |
99 | } |
100 | |
101 | if (v.isSymbol()) |
102 | return asSymbol(v)->descriptiveString(); |
103 | if (v.isObject()) { |
104 | VM& vm = exec->vm(); |
105 | CallData callData; |
106 | JSObject* object = asObject(v); |
107 | if (object->methodTable(vm)->getCallData(object, callData) != CallType::None) |
108 | return vm.smallStrings.functionString()->value(exec); |
109 | return JSObject::calculatedClassName(object); |
110 | } |
111 | return v.toString(exec)->value(exec); |
112 | } |
113 | |
114 | static String defaultApproximateSourceError(const String& originalMessage, const String& sourceText) |
115 | { |
116 | return makeString(originalMessage, " (near '..." , sourceText, "...')" ); |
117 | } |
118 | |
119 | String defaultSourceAppender(const String& originalMessage, const String& sourceText, RuntimeType, ErrorInstance::SourceTextWhereErrorOccurred occurrence) |
120 | { |
121 | if (occurrence == ErrorInstance::FoundApproximateSource) |
122 | return defaultApproximateSourceError(originalMessage, sourceText); |
123 | |
124 | ASSERT(occurrence == ErrorInstance::FoundExactSource); |
125 | return makeString(originalMessage, " (evaluating '" , sourceText, "')" ); |
126 | } |
127 | |
128 | static String functionCallBase(const String& sourceText) |
129 | { |
130 | // This function retrieves the 'foo.bar' substring from 'foo.bar(baz)'. |
131 | // FIXME: This function has simple processing of /* */ style comments. |
132 | // It doesn't properly handle embedded comments of string literals that contain |
133 | // parenthesis or comment constructs, e.g. foo.bar("/abc\)*/"). |
134 | // https://bugs.webkit.org/show_bug.cgi?id=146304 |
135 | |
136 | unsigned sourceLength = sourceText.length(); |
137 | unsigned idx = sourceLength - 1; |
138 | if (sourceLength < 2 || sourceText[idx] != ')') { |
139 | // For function calls that have many new lines in between their open parenthesis |
140 | // and their closing parenthesis, the text range passed into the message appender |
141 | // will not inlcude the text in between these parentheses, it will just be the desired |
142 | // text that precedes the parentheses. |
143 | return String(); |
144 | } |
145 | |
146 | unsigned parenStack = 1; |
147 | bool = false; |
148 | idx -= 1; |
149 | // Note that we're scanning text right to left instead of the more common left to right, |
150 | // so syntax detection is backwards. |
151 | while (parenStack && idx) { |
152 | UChar curChar = sourceText[idx]; |
153 | if (isInMultiLineComment) { |
154 | if (curChar == '*' && sourceText[idx - 1] == '/') { |
155 | isInMultiLineComment = false; |
156 | --idx; |
157 | } |
158 | } else if (curChar == '(') |
159 | --parenStack; |
160 | else if (curChar == ')') |
161 | ++parenStack; |
162 | else if (curChar == '/' && sourceText[idx - 1] == '*') { |
163 | isInMultiLineComment = true; |
164 | --idx; |
165 | } |
166 | |
167 | if (idx) |
168 | --idx; |
169 | } |
170 | |
171 | if (parenStack) { |
172 | // As noted in the FIXME at the top of this function, there are bugs |
173 | // in the above string processing. This algorithm is mostly best effort |
174 | // and it works for most JS text in practice. However, if we determine |
175 | // that the algorithm failed, we should just return the empty value. |
176 | return String(); |
177 | } |
178 | |
179 | return sourceText.left(idx + 1); |
180 | } |
181 | |
182 | static String notAFunctionSourceAppender(const String& originalMessage, const String& sourceText, RuntimeType type, ErrorInstance::SourceTextWhereErrorOccurred occurrence) |
183 | { |
184 | ASSERT(type != TypeFunction); |
185 | |
186 | if (occurrence == ErrorInstance::FoundApproximateSource) |
187 | return defaultApproximateSourceError(originalMessage, sourceText); |
188 | |
189 | ASSERT(occurrence == ErrorInstance::FoundExactSource); |
190 | auto notAFunctionIndex = originalMessage.reverseFind("is not a function" ); |
191 | RELEASE_ASSERT(notAFunctionIndex != notFound); |
192 | StringView displayValue; |
193 | if (originalMessage.is8Bit()) |
194 | displayValue = StringView(originalMessage.characters8(), notAFunctionIndex - 1); |
195 | else |
196 | displayValue = StringView(originalMessage.characters16(), notAFunctionIndex - 1); |
197 | |
198 | String base = functionCallBase(sourceText); |
199 | if (!base) |
200 | return defaultApproximateSourceError(originalMessage, sourceText); |
201 | StringBuilder builder(StringBuilder::OverflowHandler::RecordOverflow); |
202 | builder.append(base); |
203 | builder.appendLiteral(" is not a function. (In '" ); |
204 | builder.append(sourceText); |
205 | builder.appendLiteral("', '" ); |
206 | builder.append(base); |
207 | builder.appendLiteral("' is " ); |
208 | if (type == TypeSymbol) |
209 | builder.appendLiteral("a Symbol" ); |
210 | else { |
211 | if (type == TypeObject) |
212 | builder.appendLiteral("an instance of " ); |
213 | builder.append(displayValue); |
214 | } |
215 | builder.append(')'); |
216 | |
217 | if (builder.hasOverflowed()) |
218 | return makeString("object is not a function."_s ); |
219 | |
220 | return builder.toString(); |
221 | } |
222 | |
223 | static String invalidParameterInSourceAppender(const String& originalMessage, const String& sourceText, RuntimeType type, ErrorInstance::SourceTextWhereErrorOccurred occurrence) |
224 | { |
225 | ASSERT_UNUSED(type, type != TypeObject); |
226 | |
227 | if (occurrence == ErrorInstance::FoundApproximateSource) |
228 | return defaultApproximateSourceError(originalMessage, sourceText); |
229 | |
230 | ASSERT(occurrence == ErrorInstance::FoundExactSource); |
231 | auto inIndex = sourceText.reverseFind("in" ); |
232 | if (inIndex == notFound) { |
233 | // This should basically never happen, since JS code must use the literal |
234 | // text "in" for the `in` operation. However, if we fail to find "in" |
235 | // for any reason, just fail gracefully. |
236 | return originalMessage; |
237 | } |
238 | if (sourceText.find("in" ) != inIndex) |
239 | return makeString(originalMessage, " (evaluating '" , sourceText, "')" ); |
240 | |
241 | static const unsigned inLength = 2; |
242 | String rightHandSide = sourceText.substring(inIndex + inLength).simplifyWhiteSpace(); |
243 | return makeString(rightHandSide, " is not an Object. (evaluating '" , sourceText, "')" ); |
244 | } |
245 | |
246 | inline String invalidParameterInstanceofSourceAppender(const String& content, const String& originalMessage, const String& sourceText, RuntimeType, ErrorInstance::SourceTextWhereErrorOccurred occurrence) |
247 | { |
248 | if (occurrence == ErrorInstance::FoundApproximateSource) |
249 | return defaultApproximateSourceError(originalMessage, sourceText); |
250 | |
251 | ASSERT(occurrence == ErrorInstance::FoundExactSource); |
252 | auto instanceofIndex = sourceText.reverseFind("instanceof" ); |
253 | RELEASE_ASSERT(instanceofIndex != notFound); |
254 | if (sourceText.find("instanceof" ) != instanceofIndex) |
255 | return makeString(originalMessage, " (evaluating '" , sourceText, "')" ); |
256 | |
257 | static const unsigned instanceofLength = 10; |
258 | String rightHandSide = sourceText.substring(instanceofIndex + instanceofLength).simplifyWhiteSpace(); |
259 | return makeString(rightHandSide, content, ". (evaluating '" , sourceText, "')" ); |
260 | } |
261 | |
262 | static String invalidParameterInstanceofNotFunctionSourceAppender(const String& originalMessage, const String& sourceText, RuntimeType runtimeType, ErrorInstance::SourceTextWhereErrorOccurred occurrence) |
263 | { |
264 | return invalidParameterInstanceofSourceAppender(WTF::makeString(" is not a function" ), originalMessage, sourceText, runtimeType, occurrence); |
265 | } |
266 | |
267 | static String invalidParameterInstanceofhasInstanceValueNotFunctionSourceAppender(const String& originalMessage, const String& sourceText, RuntimeType runtimeType, ErrorInstance::SourceTextWhereErrorOccurred occurrence) |
268 | { |
269 | return invalidParameterInstanceofSourceAppender(WTF::makeString("[Symbol.hasInstance] is not a function, undefined, or null" ), originalMessage, sourceText, runtimeType, occurrence); |
270 | } |
271 | |
272 | JSObject* createError(ExecState* exec, JSValue value, const String& message, ErrorInstance::SourceAppender appender) |
273 | { |
274 | VM& vm = exec->vm(); |
275 | auto scope = DECLARE_CATCH_SCOPE(vm); |
276 | |
277 | String valueDescription = errorDescriptionForValue(exec, value); |
278 | ASSERT(scope.exception() || !!valueDescription); |
279 | if (!valueDescription) { |
280 | scope.clearException(); |
281 | return createOutOfMemoryError(exec); |
282 | } |
283 | String errorMessage = tryMakeString(valueDescription, ' ', message); |
284 | if (!errorMessage) |
285 | return createOutOfMemoryError(exec); |
286 | scope.assertNoException(); |
287 | JSObject* exception = createTypeError(exec, errorMessage, appender, runtimeTypeForValue(vm, value)); |
288 | ASSERT(exception->isErrorInstance()); |
289 | |
290 | return exception; |
291 | } |
292 | |
293 | JSObject* createInvalidFunctionApplyParameterError(ExecState* exec, JSValue value) |
294 | { |
295 | VM& vm = exec->vm(); |
296 | JSObject* exception = createTypeError(exec, makeString("second argument to Function.prototype.apply must be an Array-like object" ), defaultSourceAppender, runtimeTypeForValue(vm, value)); |
297 | ASSERT(exception->isErrorInstance()); |
298 | return exception; |
299 | } |
300 | |
301 | JSObject* createInvalidInParameterError(ExecState* exec, JSValue value) |
302 | { |
303 | return createError(exec, value, makeString("is not an Object." ), invalidParameterInSourceAppender); |
304 | } |
305 | |
306 | JSObject* createInvalidInstanceofParameterErrorNotFunction(ExecState* exec, JSValue value) |
307 | { |
308 | return createError(exec, value, makeString(" is not a function" ), invalidParameterInstanceofNotFunctionSourceAppender); |
309 | } |
310 | |
311 | JSObject* createInvalidInstanceofParameterErrorHasInstanceValueNotFunction(ExecState* exec, JSValue value) |
312 | { |
313 | return createError(exec, value, makeString("[Symbol.hasInstance] is not a function, undefined, or null" ), invalidParameterInstanceofhasInstanceValueNotFunctionSourceAppender); |
314 | } |
315 | |
316 | JSObject* createNotAConstructorError(ExecState* exec, JSValue value) |
317 | { |
318 | return createError(exec, value, "is not a constructor"_s , defaultSourceAppender); |
319 | } |
320 | |
321 | JSObject* createNotAFunctionError(ExecState* exec, JSValue value) |
322 | { |
323 | return createError(exec, value, "is not a function"_s , notAFunctionSourceAppender); |
324 | } |
325 | |
326 | JSObject* createNotAnObjectError(ExecState* exec, JSValue value) |
327 | { |
328 | return createError(exec, value, "is not an object"_s , defaultSourceAppender); |
329 | } |
330 | |
331 | JSObject* createErrorForInvalidGlobalAssignment(ExecState* exec, const String& propertyName) |
332 | { |
333 | return createReferenceError(exec, makeString("Strict mode forbids implicit creation of global property '" , propertyName, '\'')); |
334 | } |
335 | |
336 | JSObject* createTDZError(ExecState* exec) |
337 | { |
338 | return createReferenceError(exec, "Cannot access uninitialized variable." ); |
339 | } |
340 | |
341 | Exception* throwOutOfMemoryError(ExecState* exec, ThrowScope& scope) |
342 | { |
343 | return throwException(exec, scope, createOutOfMemoryError(exec)); |
344 | } |
345 | |
346 | Exception* throwStackOverflowError(ExecState* exec, ThrowScope& scope) |
347 | { |
348 | VM& vm = exec->vm(); |
349 | ErrorHandlingScope errorScope(vm); |
350 | return throwException(exec, scope, createStackOverflowError(exec)); |
351 | } |
352 | |
353 | Exception* throwTerminatedExecutionException(ExecState* exec, ThrowScope& scope) |
354 | { |
355 | VM& vm = exec->vm(); |
356 | ErrorHandlingScope errorScope(vm); |
357 | return throwException(exec, scope, createTerminatedExecutionException(&vm)); |
358 | } |
359 | |
360 | } // namespace JSC |
361 | |