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