/ runtime / ExceptionHelpers.cpp
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