/ runtime / ErrorInstance.cpp
ErrorInstance.cpp
  1  /*
  2   *  Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
  3   *  Copyright (C) 2003-2020 Apple Inc. All rights reserved.
  4   *
  5   *  This library is free software; you can redistribute it and/or
  6   *  modify it under the terms of the GNU Lesser General Public
  7   *  License as published by the Free Software Foundation; either
  8   *  version 2 of the License, or (at your option) any later version.
  9   *
 10   *  This library is distributed in the hope that it will be useful,
 11   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 12   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13   *  Lesser General Public License for more details.
 14   *
 15   *  You should have received a copy of the GNU Lesser General Public
 16   *  License along with this library; if not, write to the Free Software
 17   *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 18   *
 19   */
 20  
 21  #include "config.h"
 22  #include "ErrorInstance.h"
 23  
 24  #include "CodeBlock.h"
 25  #include "InlineCallFrame.h"
 26  #include "IntegrityInlines.h"
 27  #include "Interpreter.h"
 28  #include "JSCInlines.h"
 29  #include "ParseInt.h"
 30  #include "StackFrame.h"
 31  #include <wtf/text/StringBuilder.h>
 32  
 33  namespace JSC {
 34  
 35  const ClassInfo ErrorInstance::s_info = { "Error", &JSNonFinalObject::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(ErrorInstance) };
 36  
 37  ErrorInstance::ErrorInstance(VM& vm, Structure* structure)
 38      : Base(vm, structure)
 39  {
 40  }
 41  
 42  ErrorInstance* ErrorInstance::create(JSGlobalObject* globalObject, Structure* structure, JSValue message, SourceAppender appender, RuntimeType type, bool useCurrentFrame)
 43  {
 44      VM& vm = globalObject->vm();
 45      auto scope = DECLARE_THROW_SCOPE(vm);
 46      String messageString = message.isUndefined() ? String() : message.toWTFString(globalObject);
 47      RETURN_IF_EXCEPTION(scope, nullptr);
 48      return create(globalObject, vm, structure, messageString, appender, type, useCurrentFrame);
 49  }
 50  
 51  static String appendSourceToErrorMessage(CallFrame* callFrame, ErrorInstance* exception, BytecodeIndex bytecodeIndex, const String& message)
 52  {
 53      ErrorInstance::SourceAppender appender = exception->sourceAppender();
 54      exception->clearSourceAppender();
 55      RuntimeType type = exception->runtimeTypeForCause();
 56      exception->clearRuntimeTypeForCause();
 57  
 58      if (!callFrame->codeBlock()->hasExpressionInfo() || message.isNull())
 59          return message;
 60      
 61      int startOffset = 0;
 62      int endOffset = 0;
 63      int divotPoint = 0;
 64      unsigned line = 0;
 65      unsigned column = 0;
 66  
 67      CodeBlock* codeBlock;
 68      CodeOrigin codeOrigin = callFrame->codeOrigin();
 69      if (codeOrigin && codeOrigin.inlineCallFrame())
 70          codeBlock = baselineCodeBlockForInlineCallFrame(codeOrigin.inlineCallFrame());
 71      else
 72          codeBlock = callFrame->codeBlock();
 73  
 74      codeBlock->expressionRangeForBytecodeIndex(bytecodeIndex, divotPoint, startOffset, endOffset, line, column);
 75      
 76      int expressionStart = divotPoint - startOffset;
 77      int expressionStop = divotPoint + endOffset;
 78  
 79      StringView sourceString = codeBlock->source().provider()->source();
 80      if (!expressionStop || expressionStart > static_cast<int>(sourceString.length()))
 81          return message;
 82      
 83      if (expressionStart < expressionStop)
 84          return appender(message, codeBlock->source().provider()->getRange(expressionStart, expressionStop).toString(), type, ErrorInstance::FoundExactSource);
 85  
 86      // No range information, so give a few characters of context.
 87      int dataLength = sourceString.length();
 88      int start = expressionStart;
 89      int stop = expressionStart;
 90      // Get up to 20 characters of context to the left and right of the divot, clamping to the line.
 91      // Then strip whitespace.
 92      while (start > 0 && (expressionStart - start < 20) && sourceString[start - 1] != '\n')
 93          start--;
 94      while (start < (expressionStart - 1) && isStrWhiteSpace(sourceString[start]))
 95          start++;
 96      while (stop < dataLength && (stop - expressionStart < 20) && sourceString[stop] != '\n')
 97          stop++;
 98      while (stop > expressionStart && isStrWhiteSpace(sourceString[stop - 1]))
 99          stop--;
100      return appender(message, codeBlock->source().provider()->getRange(start, stop).toString(), type, ErrorInstance::FoundApproximateSource);
101  }
102  
103  void ErrorInstance::finishCreation(VM& vm, JSGlobalObject* globalObject, const String& message, SourceAppender appender, RuntimeType type, bool useCurrentFrame)
104  {
105      Base::finishCreation(vm);
106      ASSERT(inherits(vm, info()));
107  
108      m_sourceAppender = appender;
109      m_runtimeTypeForCause = type;
110  
111      std::unique_ptr<Vector<StackFrame>> stackTrace = getStackTrace(globalObject, vm, this, useCurrentFrame);
112      {
113          auto locker = holdLock(cellLock());
114          m_stackTrace = WTFMove(stackTrace);
115      }
116      vm.heap.writeBarrier(this);
117  
118      String messageWithSource = message;
119      if (m_stackTrace && !m_stackTrace->isEmpty() && hasSourceAppender()) {
120          BytecodeIndex bytecodeIndex;
121          CallFrame* callFrame;
122          getBytecodeIndex(vm, vm.topCallFrame, m_stackTrace.get(), callFrame, bytecodeIndex);
123          if (callFrame && callFrame->codeBlock() && !callFrame->callee().isWasm())
124              messageWithSource = appendSourceToErrorMessage(callFrame, this, bytecodeIndex, message);
125      }
126  
127      if (!messageWithSource.isNull())
128          putDirect(vm, vm.propertyNames->message, jsString(vm, messageWithSource), static_cast<unsigned>(PropertyAttribute::DontEnum));
129  }
130  
131  // Based on ErrorPrototype's errorProtoFuncToString(), but is modified to
132  // have no observable side effects to the user (i.e. does not call proxies,
133  // and getters).
134  String ErrorInstance::sanitizedToString(JSGlobalObject* globalObject)
135  {
136      VM& vm = globalObject->vm();
137      auto scope = DECLARE_THROW_SCOPE(vm);
138      Integrity::auditStructureID(vm, structureID());
139  
140      JSValue nameValue;
141      auto namePropertName = vm.propertyNames->name;
142      PropertySlot nameSlot(this, PropertySlot::InternalMethodType::VMInquiry, &vm);
143  
144      JSValue currentObj = this;
145      unsigned prototypeDepth = 0;
146  
147      // We only check the current object and its prototype (2 levels) because normal
148      // Error objects may have a name property, and if not, its prototype should have
149      // a name property for the type of error e.g. "SyntaxError".
150      while (currentObj.isCell() && prototypeDepth++ < 2) {
151          JSObject* obj = jsCast<JSObject*>(currentObj);
152          if (JSObject::getOwnPropertySlot(obj, globalObject, namePropertName, nameSlot) && nameSlot.isValue()) {
153              nameValue = nameSlot.getValue(globalObject, namePropertName);
154              break;
155          }
156          currentObj = obj->getPrototypeDirect(vm);
157      }
158      scope.assertNoException();
159  
160      String nameString;
161      if (!nameValue)
162          nameString = "Error"_s;
163      else {
164          nameString = nameValue.toWTFString(globalObject);
165          RETURN_IF_EXCEPTION(scope, String());
166      }
167  
168      JSValue messageValue;
169      auto messagePropertName = vm.propertyNames->message;
170      PropertySlot messageSlot(this, PropertySlot::InternalMethodType::VMInquiry, &vm);
171      if (JSObject::getOwnPropertySlot(this, globalObject, messagePropertName, messageSlot) && messageSlot.isValue())
172          messageValue = messageSlot.getValue(globalObject, messagePropertName);
173      scope.assertNoException();
174  
175      String messageString;
176      if (!messageValue)
177          messageString = String();
178      else {
179          messageString = messageValue.toWTFString(globalObject);
180          RETURN_IF_EXCEPTION(scope, String());
181      }
182  
183      if (!nameString.length())
184          return messageString;
185  
186      if (!messageString.length())
187          return nameString;
188  
189      StringBuilder builder;
190      builder.append(nameString);
191      builder.appendLiteral(": ");
192      builder.append(messageString);
193      return builder.toString();
194  }
195  
196  void ErrorInstance::finalizeUnconditionally(VM& vm)
197  {
198      if (!m_stackTrace)
199          return;
200  
201      // We don't want to keep our stack traces alive forever if the user doesn't access the stack trace.
202      // If we did, we might end up keeping functions (and their global objects) alive that happened to
203      // get caught in a trace.
204      for (const auto& frame : *m_stackTrace.get()) {
205          if (!frame.isMarked(vm)) {
206              computeErrorInfo(vm);
207              return;
208          }
209      }
210  }
211  
212  void ErrorInstance::computeErrorInfo(VM& vm)
213  {
214      ASSERT(!m_errorInfoMaterialized);
215  
216      if (m_stackTrace && !m_stackTrace->isEmpty()) {
217          getLineColumnAndSource(m_stackTrace.get(), m_line, m_column, m_sourceURL);
218          m_stackString = Interpreter::stackTraceAsString(vm, *m_stackTrace.get());
219          m_stackTrace = nullptr;
220      }
221  }
222  
223  bool ErrorInstance::materializeErrorInfoIfNeeded(VM& vm)
224  {
225      if (m_errorInfoMaterialized)
226          return false;
227  
228      computeErrorInfo(vm);
229  
230      if (!m_stackString.isNull()) {
231          auto attributes = static_cast<unsigned>(PropertyAttribute::DontEnum);
232  
233          putDirect(vm, vm.propertyNames->line, jsNumber(m_line), attributes);
234          putDirect(vm, vm.propertyNames->column, jsNumber(m_column), attributes);
235          if (!m_sourceURL.isEmpty())
236              putDirect(vm, vm.propertyNames->sourceURL, jsString(vm, WTFMove(m_sourceURL)), attributes);
237  
238          putDirect(vm, vm.propertyNames->stack, jsString(vm, WTFMove(m_stackString)), attributes);
239      }
240  
241      m_errorInfoMaterialized = true;
242      return true;
243  }
244  
245  bool ErrorInstance::materializeErrorInfoIfNeeded(VM& vm, PropertyName propertyName)
246  {
247      if (propertyName == vm.propertyNames->line
248          || propertyName == vm.propertyNames->column
249          || propertyName == vm.propertyNames->sourceURL
250          || propertyName == vm.propertyNames->stack)
251          return materializeErrorInfoIfNeeded(vm);
252      return false;
253  }
254  
255  bool ErrorInstance::getOwnPropertySlot(JSObject* object, JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot)
256  {
257      VM& vm = globalObject->vm();
258      ErrorInstance* thisObject = jsCast<ErrorInstance*>(object);
259      thisObject->materializeErrorInfoIfNeeded(vm, propertyName);
260      return Base::getOwnPropertySlot(thisObject, globalObject, propertyName, slot);
261  }
262  
263  void ErrorInstance::getOwnSpecialPropertyNames(JSObject* object, JSGlobalObject* globalObject, PropertyNameArray&, DontEnumPropertiesMode mode)
264  {
265      VM& vm = globalObject->vm();
266      ErrorInstance* thisObject = jsCast<ErrorInstance*>(object);
267      if (mode == DontEnumPropertiesMode::Include)
268          thisObject->materializeErrorInfoIfNeeded(vm);
269  }
270  
271  bool ErrorInstance::defineOwnProperty(JSObject* object, JSGlobalObject* globalObject, PropertyName propertyName, const PropertyDescriptor& descriptor, bool shouldThrow)
272  {
273      VM& vm = globalObject->vm();
274      ErrorInstance* thisObject = jsCast<ErrorInstance*>(object);
275      thisObject->materializeErrorInfoIfNeeded(vm, propertyName);
276      return Base::defineOwnProperty(thisObject, globalObject, propertyName, descriptor, shouldThrow);
277  }
278  
279  bool ErrorInstance::put(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
280  {
281      VM& vm = globalObject->vm();
282      ErrorInstance* thisObject = jsCast<ErrorInstance*>(cell);
283      bool materializedProperties = thisObject->materializeErrorInfoIfNeeded(vm, propertyName);
284      if (materializedProperties)
285          slot.disableCaching();
286      return Base::put(thisObject, globalObject, propertyName, value, slot);
287  }
288  
289  bool ErrorInstance::deleteProperty(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, DeletePropertySlot& slot)
290  {
291      VM& vm = globalObject->vm();
292      ErrorInstance* thisObject = jsCast<ErrorInstance*>(cell);
293      thisObject->materializeErrorInfoIfNeeded(vm, propertyName);
294      return Base::deleteProperty(thisObject, globalObject, propertyName, slot);
295  }
296  
297  String ErrorInstance::toStringName(const JSObject*, JSGlobalObject*)
298  {
299      return "Error"_s;
300  }
301  
302  } // namespace JSC