JSC::createError needs to check for OOM in errorDescriptionForValue
[WebKit-https.git] / Source / JavaScriptCore / runtime / ExceptionHelpers.cpp
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 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
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     if (!valueDescription)
279         return createOutOfMemoryError(exec);
280     String errorMessage = tryMakeString(valueDescription, ' ', message);
281     if (!errorMessage)
282         return createOutOfMemoryError(exec);
283     scope.assertNoException();
284     JSObject* exception = createTypeError(exec, errorMessage, appender, runtimeTypeForValue(vm, value));
285     ASSERT(exception->isErrorInstance());
286
287     return exception;
288 }
289
290 JSObject* createInvalidFunctionApplyParameterError(ExecState* exec, JSValue value)
291 {
292     VM& vm = exec->vm();
293     JSObject* exception = createTypeError(exec, makeString("second argument to Function.prototype.apply must be an Array-like object"), defaultSourceAppender, runtimeTypeForValue(vm, value));
294     ASSERT(exception->isErrorInstance());
295     return exception;
296 }
297
298 JSObject* createInvalidInParameterError(ExecState* exec, JSValue value)
299 {
300     return createError(exec, value, makeString("is not an Object."), invalidParameterInSourceAppender);
301 }
302
303 JSObject* createInvalidInstanceofParameterErrorNotFunction(ExecState* exec, JSValue value)
304 {
305     return createError(exec, value, makeString(" is not a function"), invalidParameterInstanceofNotFunctionSourceAppender);
306 }
307
308 JSObject* createInvalidInstanceofParameterErrorHasInstanceValueNotFunction(ExecState* exec, JSValue value)
309 {
310     return createError(exec, value, makeString("[Symbol.hasInstance] is not a function, undefined, or null"), invalidParameterInstanceofhasInstanceValueNotFunctionSourceAppender);
311 }
312
313 JSObject* createNotAConstructorError(ExecState* exec, JSValue value)
314 {
315     return createError(exec, value, "is not a constructor"_s, defaultSourceAppender);
316 }
317
318 JSObject* createNotAFunctionError(ExecState* exec, JSValue value)
319 {
320     return createError(exec, value, "is not a function"_s, notAFunctionSourceAppender);
321 }
322
323 JSObject* createNotAnObjectError(ExecState* exec, JSValue value)
324 {
325     return createError(exec, value, "is not an object"_s, defaultSourceAppender);
326 }
327
328 JSObject* createErrorForInvalidGlobalAssignment(ExecState* exec, const String& propertyName)
329 {
330     return createReferenceError(exec, makeString("Strict mode forbids implicit creation of global property '", propertyName, '\''));
331 }
332
333 JSObject* createTDZError(ExecState* exec)
334 {
335     return createReferenceError(exec, "Cannot access uninitialized variable.");
336 }
337
338 Exception* throwOutOfMemoryError(ExecState* exec, ThrowScope& scope)
339 {
340     return throwException(exec, scope, createOutOfMemoryError(exec));
341 }
342
343 Exception* throwStackOverflowError(ExecState* exec, ThrowScope& scope)
344 {
345     VM& vm = exec->vm();
346     ErrorHandlingScope errorScope(vm);
347     return throwException(exec, scope, createStackOverflowError(exec));
348 }
349
350 Exception* throwTerminatedExecutionException(ExecState* exec, ThrowScope& scope)
351 {
352     VM& vm = exec->vm();
353     ErrorHandlingScope errorScope(vm);
354     return throwException(exec, scope, createTerminatedExecutionException(&vm));
355 }
356
357 } // namespace JSC