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
44namespace JSC {
45
46STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(TerminatedExecutionError);
47
48const ClassInfo TerminatedExecutionError::s_info = { "TerminatedExecutionError", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(TerminatedExecutionError) };
49
50JSValue 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
57JSObject* createTerminatedExecutionException(VM* vm)
58{
59 return TerminatedExecutionError::create(*vm);
60}
61
62bool isTerminatedExecutionException(VM& vm, Exception* exception)
63{
64 if (!exception->value().isObject())
65 return false;
66
67 return exception->value().inherits<TerminatedExecutionError>(vm);
68}
69
70JSObject* 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
77JSObject* 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
84String 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
106static String defaultApproximateSourceError(const String& originalMessage, const String& sourceText)
107{
108 return makeString(originalMessage, " (near '...", sourceText, "...')");
109}
110
111String 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
120static 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 isInMultiLineComment = 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
178static 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
214static 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
237inline 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
253static 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
258static 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
263JSObject* 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
284JSObject* 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
289JSObject* createInvalidInParameterError(JSGlobalObject* globalObject, JSValue value)
290{
291 return createError(globalObject, value, "is not an Object."_s, invalidParameterInSourceAppender);
292}
293
294JSObject* createInvalidInstanceofParameterErrorNotFunction(JSGlobalObject* globalObject, JSValue value)
295{
296 return createError(globalObject, value, " is not a function"_s, invalidParameterInstanceofNotFunctionSourceAppender);
297}
298
299JSObject* createInvalidInstanceofParameterErrorHasInstanceValueNotFunction(JSGlobalObject* globalObject, JSValue value)
300{
301 return createError(globalObject, value, "[Symbol.hasInstance] is not a function, undefined, or null"_s, invalidParameterInstanceofhasInstanceValueNotFunctionSourceAppender);
302}
303
304JSObject* createNotAConstructorError(JSGlobalObject* globalObject, JSValue value)
305{
306 return createError(globalObject, value, "is not a constructor"_s, defaultSourceAppender);
307}
308
309JSObject* createNotAFunctionError(JSGlobalObject* globalObject, JSValue value)
310{
311 return createError(globalObject, value, "is not a function"_s, notAFunctionSourceAppender);
312}
313
314JSObject* createNotAnObjectError(JSGlobalObject* globalObject, JSValue value)
315{
316 return createError(globalObject, value, "is not an object"_s, defaultSourceAppender);
317}
318
319JSObject* createErrorForInvalidGlobalAssignment(JSGlobalObject* globalObject, const String& propertyName)
320{
321 return createReferenceError(globalObject, makeString("Strict mode forbids implicit creation of global property '", propertyName, '\''));
322}
323
324JSObject* createTDZError(JSGlobalObject* globalObject)
325{
326 return createReferenceError(globalObject, "Cannot access uninitialized variable.");
327}
328
329Exception* throwOutOfMemoryError(JSGlobalObject* globalObject, ThrowScope& scope)
330{
331 return throwException(globalObject, scope, createOutOfMemoryError(globalObject));
332}
333
334Exception* throwStackOverflowError(JSGlobalObject* globalObject, ThrowScope& scope)
335{
336 VM& vm = globalObject->vm();
337 ErrorHandlingScope errorScope(vm);
338 return throwException(globalObject, scope, createStackOverflowError(globalObject));
339}
340
341Exception* 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