Return the empty string when formatting recursive error messages. This
authorager@chromium.org <ager@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Fri, 21 Jan 2011 11:44:29 +0000 (11:44 +0000)
committerager@chromium.org <ager@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Fri, 21 Jan 2011 11:44:29 +0000 (11:44 +0000)
matches the behavior of Safari and Firefox. Our old behavior was to
throw a stack overflow exception.

BUG=crbug.com/70334
TEST=mjsunit/cyclic-error-to-string.js

Review URL: http://codereview.chromium.org/6261018

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@6428 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/messages.js
test/mjsunit/cyclic-error-to-string.js [new file with mode: 0644]

index e62f06f..d654255 100644 (file)
@@ -1012,19 +1012,47 @@ $Error.captureStackTrace = captureStackTrace;
 // Setup extra properties of the Error.prototype object.
 $Error.prototype.message = '';
 
+// Global list of error objects visited during errorToString. This is
+// used to detect cycles in error toString formatting.
+var visited_errors = new $Array();
+var cyclic_error_marker = new $Object();
+
+function errorToStringDetectCycle() {
+  if (!%PushIfAbsent(visited_errors, this)) throw cyclic_error_marker;
+  try {
+    var type = this.type;
+    if (type && !this.hasOwnProperty("message")) {
+      var formatted = FormatMessage({ type: type, args: this.arguments });
+      return this.name + ": " + formatted;
+    }
+    var message = this.hasOwnProperty("message") ? (": " + this.message) : "";
+    return this.name + message;
+  } finally {
+    visited_errors.pop();
+  }
+}
+
 function errorToString() {
-  var type = this.type;
-  if (type && !this.hasOwnProperty("message")) {
-    return this.name + ": " + FormatMessage({ type: type, args: this.arguments });
+  // These helper functions are needed because access to properties on
+  // the builtins object do not work inside of a catch clause.
+  function isCyclicErrorMarker(o) { return o === cyclic_error_marker; }
+  function isVisitedErrorsEmpty() { return visited_errors.length === 0; }
+
+  try {
+    return %_CallFunction(this, errorToStringDetectCycle);
+  } catch(e) {
+    // Propagate cyclic_error_marker exception until all error
+    // formatting is finished and then return the empty string. Safari
+    // and Firefox also returns the empty string when converting a
+    // cyclic error to a string.
+    if (isCyclicErrorMarker(e) && isVisitedErrorsEmpty()) return '';
+    else throw e;
   }
-  var message = this.hasOwnProperty("message") ? (": " + this.message) : "";
-  return this.name + message;
 }
 
 %FunctionSetName(errorToString, 'toString');
 %SetProperty($Error.prototype, 'toString', errorToString, DONT_ENUM);
 
-
 // Boilerplate for exceptions for stack overflows. Used from
 // Top::StackOverflow().
 const kStackOverflowBoilerplate = MakeRangeError('stack_overflow', []);
diff --git a/test/mjsunit/cyclic-error-to-string.js b/test/mjsunit/cyclic-error-to-string.js
new file mode 100644 (file)
index 0000000..7c43fc5
--- /dev/null
@@ -0,0 +1,46 @@
+// Copyright 2011 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+//       copyright notice, this list of conditions and the following
+//       disclaimer in the documentation and/or other materials provided
+//       with the distribution.
+//     * Neither the name of Google Inc. nor the names of its
+//       contributors may be used to endorse or promote products derived
+//       from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Test printing of cyclic errors which return the empty string for
+// compatibility with Safari and Firefox.
+
+var e = new Error();
+assertEquals('Error', e + '');
+
+e = new Error();
+e.name = e;
+e.message = e;
+e.stack = e;
+e.arguments = e;
+assertEquals('', e + '');
+
+e = new Error();
+e.name = [ e ];
+e.message = [ e ];
+e.stack = [ e ];
+e.arguments = [ e ];
+assertEquals('', e + '');