Stack trace API: poison stack frames below the first strict mode frame.
authoryangguo@chromium.org <yangguo@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 28 Mar 2013 10:40:07 +0000 (10:40 +0000)
committeryangguo@chromium.org <yangguo@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 28 Mar 2013 10:40:07 +0000 (10:40 +0000)
Function and receiver objects are not accessible for poisoned frames.

R=rossberg@chromium.org
BUG=v8:2564

Review URL: https://chromiumcodereview.appspot.com/13150003

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

src/isolate.cc
src/messages.js
test/mjsunit/regress/regress-2564.js [new file with mode: 0644]

index 632ecdc..a1d70a5 100644 (file)
@@ -612,13 +612,16 @@ Handle<JSArray> Isolate::CaptureSimpleStackTrace(Handle<JSObject> error_object,
   limit = Max(limit, 0);  // Ensure that limit is not negative.
   int initial_size = Min(limit, 10);
   Handle<FixedArray> elements =
-      factory()->NewFixedArrayWithHoles(initial_size * 4);
+      factory()->NewFixedArrayWithHoles(initial_size * 4 + 1);
 
   // If the caller parameter is a function we skip frames until we're
   // under it before starting to collect.
   bool seen_caller = !caller->IsJSFunction();
-  int cursor = 0;
+  // First element is reserved to store the number of non-strict frames.
+  int cursor = 1;
   int frames_seen = 0;
+  int non_strict_frames = 0;
+  bool encountered_strict_function = false;
   for (StackFrameIterator iter(this);
        !iter.done() && frames_seen < limit;
        iter.Advance()) {
@@ -646,6 +649,17 @@ Handle<JSArray> Isolate::CaptureSimpleStackTrace(Handle<JSObject> error_object,
         Handle<JSFunction> fun = frames[i].function();
         Handle<Code> code = frames[i].code();
         Handle<Smi> offset(Smi::FromInt(frames[i].offset()), this);
+        // The stack trace API should not expose receivers and function
+        // objects on frames deeper than the top-most one with a strict
+        // mode function.  The number of non-strict frames is stored as
+        // first element in the result array.
+        if (!encountered_strict_function) {
+          if (!fun->shared()->is_classic_mode()) {
+            encountered_strict_function = true;
+          } else {
+            non_strict_frames++;
+          }
+        }
         elements->set(cursor++, *recv);
         elements->set(cursor++, *fun);
         elements->set(cursor++, *code);
@@ -653,6 +667,7 @@ Handle<JSArray> Isolate::CaptureSimpleStackTrace(Handle<JSObject> error_object,
       }
     }
   }
+  elements->set(0, Smi::FromInt(non_strict_frames));
   Handle<JSArray> result = factory()->NewJSArrayWithElements(elements);
   result->set_length(Smi::FromInt(cursor));
   return result;
index cbb5000..d03628a 100644 (file)
@@ -746,64 +746,70 @@ function GetPositionInLine(message) {
 
 
 function GetStackTraceLine(recv, fun, pos, isGlobal) {
-  return new CallSite(recv, fun, pos).toString();
+  return new CallSite(recv, fun, pos, false).toString();
 }
 
 // ----------------------------------------------------------------------------
 // Error implementation
 
-function CallSite(receiver, fun, pos) {
-  this.receiver = receiver;
-  this.fun = fun;
-  this.pos = pos;
+var CallSiteReceiverKey = %CreateSymbol("receiver");
+var CallSiteFunctionKey = %CreateSymbol("function");
+var CallSitePositionKey = %CreateSymbol("position");
+var CallSiteStrictModeKey = %CreateSymbol("strict mode");
+
+function CallSite(receiver, fun, pos, strict_mode) {
+  this[CallSiteReceiverKey] = receiver;
+  this[CallSiteFunctionKey] = fun;
+  this[CallSitePositionKey] = pos;
+  this[CallSiteStrictModeKey] = strict_mode;
 }
 
 function CallSiteGetThis() {
-  return this.receiver;
+  return this[CallSiteStrictModeKey] ? void 0 : this[CallSiteReceiverKey];
 }
 
 function CallSiteGetTypeName() {
-  return GetTypeName(this, false);
+  return GetTypeName(this[CallSiteReceiverKey], false);
 }
 
 function CallSiteIsToplevel() {
-  if (this.receiver == null) {
+  if (this[CallSiteReceiverKey] == null) {
     return true;
   }
-  return IS_GLOBAL(this.receiver);
+  return IS_GLOBAL(this[CallSiteReceiverKey]);
 }
 
 function CallSiteIsEval() {
-  var script = %FunctionGetScript(this.fun);
+  var script = %FunctionGetScript(this[CallSiteFunctionKey]);
   return script && script.compilation_type == COMPILATION_TYPE_EVAL;
 }
 
 function CallSiteGetEvalOrigin() {
-  var script = %FunctionGetScript(this.fun);
+  var script = %FunctionGetScript(this[CallSiteFunctionKey]);
   return FormatEvalOrigin(script);
 }
 
 function CallSiteGetScriptNameOrSourceURL() {
-  var script = %FunctionGetScript(this.fun);
+  var script = %FunctionGetScript(this[CallSiteFunctionKey]);
   return script ? script.nameOrSourceURL() : null;
 }
 
 function CallSiteGetFunction() {
-  return this.fun;
+  return this[CallSiteStrictModeKey] ? void 0 : this[CallSiteFunctionKey];
 }
 
 function CallSiteGetFunctionName() {
   // See if the function knows its own name
-  var name = this.fun.name;
+  var name = this[CallSiteFunctionKey].name;
   if (name) {
     return name;
   }
-  name = %FunctionGetInferredName(this.fun);
+  name = %FunctionGetInferredName(this[CallSiteFunctionKey]);
   if (name) {
     return name;
   }
   // Maybe this is an evaluation?
-  var script = %FunctionGetScript(this.fun);
+  var script = %FunctionGetScript(this[CallSiteFunctionKey]);
   if (script && script.compilation_type == COMPILATION_TYPE_EVAL) {
     return "eval";
   }
@@ -813,26 +819,22 @@ function CallSiteGetFunctionName() {
 function CallSiteGetMethodName() {
   // See if we can find a unique property on the receiver that holds
   // this function.
-  var ownName = this.fun.name;
-  if (ownName && this.receiver &&
-      (%_CallFunction(this.receiver,
-                      ownName,
-                      ObjectLookupGetter) === this.fun ||
-       %_CallFunction(this.receiver,
-                      ownName,
-                      ObjectLookupSetter) === this.fun ||
-       (IS_OBJECT(this.receiver) &&
-        %GetDataProperty(this.receiver, ownName) === this.fun))) {
+  var receiver = this[CallSiteReceiverKey];
+  var fun = this[CallSiteFunctionKey];
+  var ownName = fun.name;
+  if (ownName && receiver &&
+      (%_CallFunction(receiver, ownName, ObjectLookupGetter) === fun ||
+       %_CallFunction(receiver, ownName, ObjectLookupSetter) === fun ||
+       (IS_OBJECT(receiver) && %GetDataProperty(receiver, ownName) === fun))) {
     // To handle DontEnum properties we guess that the method has
     // the same name as the function.
     return ownName;
   }
   var name = null;
-  for (var prop in this.receiver) {
-    if (%_CallFunction(this.receiver, prop, ObjectLookupGetter) === this.fun ||
-        %_CallFunction(this.receiver, prop, ObjectLookupSetter) === this.fun ||
-        (IS_OBJECT(this.receiver) &&
-         %GetDataProperty(this.receiver, prop) === this.fun)) {
+  for (var prop in receiver) {
+    if (%_CallFunction(receiver, prop, ObjectLookupGetter) === fun ||
+        %_CallFunction(receiver, prop, ObjectLookupSetter) === fun ||
+        (IS_OBJECT(receiver) && %GetDataProperty(receiver, prop) === fun)) {
       // If we find more than one match bail out to avoid confusion.
       if (name) {
         return null;
@@ -847,49 +849,49 @@ function CallSiteGetMethodName() {
 }
 
 function CallSiteGetFileName() {
-  var script = %FunctionGetScript(this.fun);
+  var script = %FunctionGetScript(this[CallSiteFunctionKey]);
   return script ? script.name : null;
 }
 
 function CallSiteGetLineNumber() {
-  if (this.pos == -1) {
+  if (this[CallSitePositionKey] == -1) {
     return null;
   }
-  var script = %FunctionGetScript(this.fun);
+  var script = %FunctionGetScript(this[CallSiteFunctionKey]);
   var location = null;
   if (script) {
-    location = script.locationFromPosition(this.pos, true);
+    location = script.locationFromPosition(this[CallSitePositionKey], true);
   }
   return location ? location.line + 1 : null;
 }
 
 function CallSiteGetColumnNumber() {
-  if (this.pos == -1) {
+  if (this[CallSitePositionKey] == -1) {
     return null;
   }
-  var script = %FunctionGetScript(this.fun);
+  var script = %FunctionGetScript(this[CallSiteFunctionKey]);
   var location = null;
   if (script) {
-    location = script.locationFromPosition(this.pos, true);
+    location = script.locationFromPosition(this[CallSitePositionKey], true);
   }
   return location ? location.column + 1: null;
 }
 
 function CallSiteIsNative() {
-  var script = %FunctionGetScript(this.fun);
+  var script = %FunctionGetScript(this[CallSiteFunctionKey]);
   return script ? (script.type == TYPE_NATIVE) : false;
 }
 
 function CallSiteGetPosition() {
-  return this.pos;
+  return this[CallSitePositionKey];
 }
 
 function CallSiteIsConstructor() {
-  var receiver = this.receiver;
+  var receiver = this[CallSiteReceiverKey];
   var constructor =
       IS_OBJECT(receiver) ? %GetDataProperty(receiver, "constructor") : null;
   if (!constructor) return false;
-  return this.fun === constructor;
+  return this[CallSiteFunctionKey] === constructor;
 }
 
 function CallSiteToString() {
@@ -932,7 +934,7 @@ function CallSiteToString() {
   var isConstructor = this.isConstructor();
   var isMethodCall = !(this.isToplevel() || isConstructor);
   if (isMethodCall) {
-    var typeName = GetTypeName(this, true);
+    var typeName = GetTypeName(this[CallSiteReceiverKey], true);
     var methodName = this.getMethodName();
     if (functionName) {
       if (typeName &&
@@ -1036,13 +1038,15 @@ function FormatErrorString(error) {
 
 function GetStackFrames(raw_stack) {
   var frames = new InternalArray();
-  for (var i = 0; i < raw_stack.length; i += 4) {
+  var non_strict_frames = raw_stack[0];
+  for (var i = 1; i < raw_stack.length; i += 4) {
     var recv = raw_stack[i];
     var fun = raw_stack[i + 1];
     var code = raw_stack[i + 2];
     var pc = raw_stack[i + 3];
     var pos = %FunctionGetPositionForOffset(code, pc);
-    frames.push(new CallSite(recv, fun, pos));
+    non_strict_frames--;
+    frames.push(new CallSite(recv, fun, pos, (non_strict_frames < 0)));
   }
   return frames;
 }
@@ -1070,16 +1074,16 @@ function FormatStackTrace(error_string, frames) {
 }
 
 
-function GetTypeName(obj, requireConstructor) {
-  var constructor = obj.receiver.constructor;
+function GetTypeName(receiver, requireConstructor) {
+  var constructor = receiver.constructor;
   if (!constructor) {
     return requireConstructor ? null :
-        %_CallFunction(obj.receiver, ObjectToString);
+        %_CallFunction(receiver, ObjectToString);
   }
   var constructorName = constructor.name;
   if (!constructorName) {
     return requireConstructor ? null :
-        %_CallFunction(obj.receiver, ObjectToString);
+        %_CallFunction(receiver, ObjectToString);
   }
   return constructorName;
 }
diff --git a/test/mjsunit/regress/regress-2564.js b/test/mjsunit/regress/regress-2564.js
new file mode 100644 (file)
index 0000000..1d7cdae
--- /dev/null
@@ -0,0 +1,122 @@
+// Copyright 2013 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.
+
+var o = [ function f0() { throw new Error(); },
+          function f1() { o[0](); },
+          function f2() { o[1](); },
+          function f3() { o[2](); } ];
+
+Error.prepareStackTrace = function(error, frames) {
+  Error.prepareStackTrace = undefined;  // Prevent recursion.
+  try {
+    assertEquals(5, frames.length);
+    // Don't check the last frame since that's the top-level code.
+    for (var i = 0; i < frames.length - 1; i++) {
+      assertEquals(o[i], frames[i].getFunction());
+      assertEquals(o, frames[i].getThis());
+      // Private fields are no longer accessible.
+      assertEquals(undefined, frames[i].receiver);
+      assertEquals(undefined, frames[i].fun);
+      assertEquals(undefined, frames[i].pos);
+    }
+    return "success";
+  } catch (e) {
+    return "fail";
+  }
+}
+
+try {
+  o[3]();
+} catch (e) {
+  assertEquals("success", e.stack);
+};
+
+
+var o = [ function f0() { throw new Error(); },
+          function f1() { o[0](); },
+          function f2() { "use strict"; o[1](); },
+          function f3() { o[2](); } ];
+
+Error.prepareStackTrace = function(error, frames) {
+  Error.prepareStackTrace = undefined;  // Prevent recursion.
+  try {
+    assertEquals(5, frames.length);
+    for (var i = 0; i < 2; i++) {
+      // The first two frames are still classic mode.
+      assertEquals(o[i], frames[i].getFunction());
+      assertEquals(o, frames[i].getThis());
+    }
+    for (var i = 2; i < frames.length; i++) {
+      // The rest are poisoned by the first strict mode function.
+      assertEquals(undefined, frames[i].getFunction());
+      assertEquals(undefined, frames[i].getThis());
+    }
+    for (var i = 0; i < frames.length - 1; i++) {
+      // Function name remains accessible.
+      assertEquals("f"+i, frames[i].getFunctionName());
+    }
+    return "success";
+  } catch (e) {
+    return e;
+  }
+}
+
+try {
+  o[3]();
+} catch (e) {
+  assertEquals("success", e.stack);
+};
+
+
+var o = [ function f0() { "use strict"; throw new Error(); },
+          function f1() { o[0](); },
+          function f2() { o[1](); },
+          function f3() { o[2](); } ];
+
+Error.prepareStackTrace = function(error, frames) {
+  Error.prepareStackTrace = undefined;  // Prevent recursion.
+  try {
+    assertEquals(5, frames.length);
+    for (var i = 0; i < frames.length; i++) {
+      // The rest are poisoned by the first strict mode function.
+      assertEquals(undefined, frames[i].getFunction());
+      assertEquals(undefined, frames[i].getThis());
+      if (i < frames.length - 1) {  // Function name remains accessible.
+        assertEquals("f"+i, frames[i].getFunctionName());
+      }
+    }
+    return "success";
+  } catch (e) {
+    return e;
+  }
+}
+
+try {
+  o[3]();
+} catch (e) {
+  assertEquals("success", e.stack);
+};