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*, ExecState* exec, PreferredPrimitiveType hint)
51{
52 if (hint == PreferString)
53 return jsNontrivialString(exec, 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(ExecState* exec)
71{
72 return createStackOverflowError(exec, exec->lexicalGlobalObject());
73}
74
75JSObject* 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
82JSObject* 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
92String 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
114static String defaultApproximateSourceError(const String& originalMessage, const String& sourceText)
115{
116 return makeString(originalMessage, " (near '...", sourceText, "...')");
117}
118
119String 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
128static 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 isInMultiLineComment = 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
182static 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
223static 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
246inline 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
262static 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
267static 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
272JSObject* 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
293JSObject* 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
301JSObject* createInvalidInParameterError(ExecState* exec, JSValue value)
302{
303 return createError(exec, value, makeString("is not an Object."), invalidParameterInSourceAppender);
304}
305
306JSObject* createInvalidInstanceofParameterErrorNotFunction(ExecState* exec, JSValue value)
307{
308 return createError(exec, value, makeString(" is not a function"), invalidParameterInstanceofNotFunctionSourceAppender);
309}
310
311JSObject* createInvalidInstanceofParameterErrorHasInstanceValueNotFunction(ExecState* exec, JSValue value)
312{
313 return createError(exec, value, makeString("[Symbol.hasInstance] is not a function, undefined, or null"), invalidParameterInstanceofhasInstanceValueNotFunctionSourceAppender);
314}
315
316JSObject* createNotAConstructorError(ExecState* exec, JSValue value)
317{
318 return createError(exec, value, "is not a constructor"_s, defaultSourceAppender);
319}
320
321JSObject* createNotAFunctionError(ExecState* exec, JSValue value)
322{
323 return createError(exec, value, "is not a function"_s, notAFunctionSourceAppender);
324}
325
326JSObject* createNotAnObjectError(ExecState* exec, JSValue value)
327{
328 return createError(exec, value, "is not an object"_s, defaultSourceAppender);
329}
330
331JSObject* createErrorForInvalidGlobalAssignment(ExecState* exec, const String& propertyName)
332{
333 return createReferenceError(exec, makeString("Strict mode forbids implicit creation of global property '", propertyName, '\''));
334}
335
336JSObject* createTDZError(ExecState* exec)
337{
338 return createReferenceError(exec, "Cannot access uninitialized variable.");
339}
340
341Exception* throwOutOfMemoryError(ExecState* exec, ThrowScope& scope)
342{
343 return throwException(exec, scope, createOutOfMemoryError(exec));
344}
345
346Exception* throwStackOverflowError(ExecState* exec, ThrowScope& scope)
347{
348 VM& vm = exec->vm();
349 ErrorHandlingScope errorScope(vm);
350 return throwException(exec, scope, createStackOverflowError(exec));
351}
352
353Exception* 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