Bug 1623226 - Include mutedErrors in SavedFrame r=jwalden
authorTom Schuster <evilpies@gmail.com>
Tue, 31 Mar 2020 01:19:29 +0000
changeset 521192 41494fa6d116b9e338e00d61f30cb8e3e35bc9ca
parent 521191 cb87343d4976405a9d9eece15fc1afca7eb7b81d
child 521193 a9783d27bb78e3816693c59ac7b420f72f26013e
push id111552
push userevilpies@gmail.com
push dateTue, 31 Mar 2020 02:07:23 +0000
treeherderautoland@ca98d4d3023d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwalden
bugs1623226
milestone76.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1623226 - Include mutedErrors in SavedFrame r=jwalden There is definitely stuff that is missing here like structured cloning etc. Before implementing that I want to find someone who can tell me if this approach is valid. Differential Revision: https://phabricator.services.mozilla.com/D67953
js/src/jit-test/tests/structured-clone/saved-stack.js
js/src/vm/SavedFrame.h
js/src/vm/SavedStacks.cpp
js/src/vm/StructuredClone.cpp
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/structured-clone/saved-stack.js
@@ -0,0 +1,37 @@
+// The following binary data was created with:
+// JS_STRUCTURED_CLONE_VERSION = 8
+//
+// ```
+// function f() {
+//  return saveStack();
+// }
+// function g() {
+//  return f();
+// }
+//
+// let stack = g();
+// print(valueToSource(serialize(stack, undefined, {scope: "DifferentProcess"}).clonebuffer))
+// ```
+
+function checkStack(stack) {
+    print(stack.toString());
+
+    assertEq(stack.functionDisplayName, "f");
+    assertEq(stack.parent.functionDisplayName, "g");
+    assertEq(stack.parent.parent.functionDisplayName, null);
+    assertEq(stack.parent.parent.parent, null);
+}
+
+var clonebuffer = serialize("dummy");
+clonebuffer.clonebuffer = "\x02\x00\x00\x00\x00\x00\xF1\xFF\x18\x00\xFF\xFF\x16\x00\xFF\xFF \x00\x00\x80\x04\x00\xFF\xFF/home/tom/Desktop/saved-stack.js\x11\x00\x00\x00\x03\x00\xFF\xFF\t\x00\x00\x00\x03\x00\xFF\xFF\x01\x00\x00\x80\x04\x00\xFF\xFFf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\x18\x00\xFF\xFF\x16\x00\xFF\xFF \x00\x00\x80\x04\x00\xFF\xFF/home/tom/Desktop/saved-stack.js\x14\x00\x00\x00\x03\x00\xFF\xFF\t\x00\x00\x00\x03\x00\xFF\xFF\x01\x00\x00\x80\x04\x00\xFF\xFFg\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\x18\x00\xFF\xFF\x16\x00\xFF\xFF \x00\x00\x80\x04\x00\xFF\xFF/home/tom/Desktop/saved-stack.js\x17\x00\x00\x00\x03\x00\xFF\xFF\r\x00\x00\x00\x03\x00\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\xFF\x00\x00\x00\x00\x13\x00\xFF\xFF\x00\x00\x00\x00\x13\x00\xFF\xFF\x00\x00\x00\x00\x13\x00\xFF\xFF";
+var stack = deserialize(clonebuffer);
+checkStack(stack);
+
+function f() {
+ return saveStack();
+}
+function g() {
+ return f();
+}
+stack = deserialize(serialize(g()));
+checkStack(stack);
--- a/js/src/vm/SavedFrame.h
+++ b/js/src/vm/SavedFrame.h
@@ -47,16 +47,17 @@ class SavedFrame : public NativeObject {
   JSAtom* getSource();
   uint32_t getSourceId();
   uint32_t getLine();
   uint32_t getColumn();
   JSAtom* getFunctionDisplayName();
   JSAtom* getAsyncCause();
   SavedFrame* getParent() const;
   JSPrincipals* getPrincipals();
+  bool getMutedErrors();
   bool isSelfHosted(JSContext* cx);
   bool isWasm();
 
   // When isWasm():
   uint32_t wasmFuncIndex();
   uint32_t wasmBytecodeOffset();
 
   // Iterator for use with C++11 range based for loops, eg:
@@ -117,18 +118,19 @@ class SavedFrame : public NativeObject {
   void initFromLookup(JSContext* cx, Handle<Lookup> lookup);
   void initSource(JSAtom* source);
   void initSourceId(uint32_t id);
   void initLine(uint32_t line);
   void initColumn(uint32_t column);
   void initFunctionDisplayName(JSAtom* maybeName);
   void initAsyncCause(JSAtom* maybeCause);
   void initParent(SavedFrame* maybeParent);
-  void initPrincipalsAlreadyHeld(JSPrincipals* principals);
-  void initPrincipals(JSPrincipals* principals);
+  void initPrincipalsAlreadyHeldAndMutedErrors(JSPrincipals* principals,
+                                               bool mutedErrors);
+  void initPrincipalsAndMutedErrors(JSPrincipals* principals, bool mutedErrors);
 
   enum {
     // The reserved slots in the SavedFrame class.
     JSSLOT_SOURCE,
     JSSLOT_SOURCEID,
     JSSLOT_LINE,
     JSSLOT_COLUMN,
     JSSLOT_FUNCTIONDISPLAYNAME,
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -170,27 +170,28 @@ void LiveSavedFrameCache::findWithoutInv
   }
 
   frame.set(nullptr);
 }
 
 struct MOZ_STACK_CLASS SavedFrame::Lookup {
   Lookup(JSAtom* source, uint32_t sourceId, uint32_t line, uint32_t column,
          JSAtom* functionDisplayName, JSAtom* asyncCause, SavedFrame* parent,
-         JSPrincipals* principals,
+         JSPrincipals* principals, bool mutedErrors,
          const Maybe<LiveSavedFrameCache::FramePtr>& framePtr = Nothing(),
          jsbytecode* pc = nullptr, Activation* activation = nullptr)
       : source(source),
         sourceId(sourceId),
         line(line),
         column(column),
         functionDisplayName(functionDisplayName),
         asyncCause(asyncCause),
         parent(parent),
         principals(principals),
+        mutedErrors(mutedErrors),
         framePtr(framePtr),
         pc(pc),
         activation(activation) {
     MOZ_ASSERT(source);
     MOZ_ASSERT_IF(framePtr.isSome(), activation);
 #ifdef JS_MORE_DETERMINISTIC
     column = 0;
 #endif
@@ -200,30 +201,32 @@ struct MOZ_STACK_CLASS SavedFrame::Looku
       : source(savedFrame.getSource()),
         sourceId(savedFrame.getSourceId()),
         line(savedFrame.getLine()),
         column(savedFrame.getColumn()),
         functionDisplayName(savedFrame.getFunctionDisplayName()),
         asyncCause(savedFrame.getAsyncCause()),
         parent(savedFrame.getParent()),
         principals(savedFrame.getPrincipals()),
+        mutedErrors(savedFrame.getMutedErrors()),
         framePtr(Nothing()),
         pc(nullptr),
         activation(nullptr) {
     MOZ_ASSERT(source);
   }
 
   JSAtom* source;
   uint32_t sourceId;
   uint32_t line;
   uint32_t column;
   JSAtom* functionDisplayName;
   JSAtom* asyncCause;
   SavedFrame* parent;
   JSPrincipals* principals;
+  bool mutedErrors;
 
   // These are used only by the LiveSavedFrameCache and not used for identity or
   // hashing.
   Maybe<LiveSavedFrameCache::FramePtr> framePtr;
   jsbytecode* pc;
   Activation* activation;
 
   void trace(JSTracer* trc) {
@@ -248,16 +251,17 @@ class WrappedPtrOperations<SavedFrame::L
   JSAtom* source() { return value().source; }
   uint32_t sourceId() { return value().sourceId; }
   uint32_t line() { return value().line; }
   uint32_t column() { return value().column; }
   JSAtom* functionDisplayName() { return value().functionDisplayName; }
   JSAtom* asyncCause() { return value().asyncCause; }
   SavedFrame* parent() { return value().parent; }
   JSPrincipals* principals() { return value().principals; }
+  bool mutedErrors() { return value().mutedErrors; }
   Maybe<LiveSavedFrameCache::FramePtr> framePtr() { return value().framePtr; }
   jsbytecode* pc() { return value().pc; }
   Activation* activation() { return value().activation; }
 };
 
 template <typename Wrapper>
 class MutableWrappedPtrOperations<SavedFrame::Lookup, Wrapper>
     : public WrappedPtrOperations<SavedFrame::Lookup, Wrapper> {
@@ -282,17 +286,17 @@ bool SavedFrame::HashPolicy::ensureHash(
 /* static */
 HashNumber SavedFrame::HashPolicy::hash(const Lookup& lookup) {
   JS::AutoCheckCannotGC nogc;
   // Assume that we can take line mod 2^32 without losing anything of
   // interest.  If that assumption changes, we'll just need to start with 0
   // and add another overload of AddToHash with more arguments.
   return AddToHash(lookup.line, lookup.column, lookup.source,
                    lookup.functionDisplayName, lookup.asyncCause,
-                   SavedFramePtrHasher::hash(lookup.parent),
+                   lookup.mutedErrors, SavedFramePtrHasher::hash(lookup.parent),
                    JSPrincipalsPtrHasher::hash(lookup.principals));
 }
 
 /* static */
 bool SavedFrame::HashPolicy::match(SavedFrame* existing, const Lookup& lookup) {
   MOZ_ASSERT(existing);
 
   if (existing->getLine() != lookup.line) {
@@ -446,17 +450,25 @@ SavedFrame* SavedFrame::getParent() cons
   return v.isObject() ? &v.toObject().as<SavedFrame>() : nullptr;
 }
 
 JSPrincipals* SavedFrame::getPrincipals() {
   const Value& v = getReservedSlot(JSSLOT_PRINCIPALS);
   if (v.isUndefined()) {
     return nullptr;
   }
-  return static_cast<JSPrincipals*>(v.toPrivate());
+  return reinterpret_cast<JSPrincipals*>(uintptr_t(v.toPrivate()) & ~0b1);
+}
+
+bool SavedFrame::getMutedErrors() {
+  const Value& v = getReservedSlot(JSSLOT_PRINCIPALS);
+  if (v.isUndefined()) {
+    return true;
+  }
+  return bool(uintptr_t(v.toPrivate()) & 0b1);
 }
 
 void SavedFrame::initSource(JSAtom* source) {
   MOZ_ASSERT(source);
   initReservedSlot(JSSLOT_SOURCE, StringValue(source));
 }
 
 void SavedFrame::initSourceId(uint32_t sourceId) {
@@ -469,26 +481,30 @@ void SavedFrame::initLine(uint32_t line)
 
 void SavedFrame::initColumn(uint32_t column) {
 #ifdef JS_MORE_DETERMINISTIC
   column = 0;
 #endif
   initReservedSlot(JSSLOT_COLUMN, PrivateUint32Value(column));
 }
 
-void SavedFrame::initPrincipals(JSPrincipals* principals) {
+void SavedFrame::initPrincipalsAndMutedErrors(JSPrincipals* principals,
+                                              bool mutedErrors) {
   if (principals) {
     JS_HoldPrincipals(principals);
   }
-  initPrincipalsAlreadyHeld(principals);
+  initPrincipalsAlreadyHeldAndMutedErrors(principals, mutedErrors);
 }
 
-void SavedFrame::initPrincipalsAlreadyHeld(JSPrincipals* principals) {
+void SavedFrame::initPrincipalsAlreadyHeldAndMutedErrors(
+    JSPrincipals* principals, bool mutedErrors) {
   MOZ_ASSERT_IF(principals, principals->refcount > 0);
-  initReservedSlot(JSSLOT_PRINCIPALS, PrivateValue(principals));
+  uintptr_t ptr = uintptr_t(principals) | mutedErrors;
+  initReservedSlot(JSSLOT_PRINCIPALS,
+                   PrivateValue(reinterpret_cast<void*>(ptr)));
 }
 
 void SavedFrame::initFunctionDisplayName(JSAtom* maybeName) {
   initReservedSlot(JSSLOT_FUNCTIONDISPLAYNAME,
                    maybeName ? StringValue(maybeName) : NullValue());
 }
 
 void SavedFrame::initAsyncCause(JSAtom* maybeCause) {
@@ -518,17 +534,17 @@ void SavedFrame::initFromLookup(JSContex
 
   initSource(lookup.source());
   initSourceId(lookup.sourceId());
   initLine(lookup.line());
   initColumn(lookup.column());
   initFunctionDisplayName(lookup.functionDisplayName());
   initAsyncCause(lookup.asyncCause());
   initParent(lookup.parent());
-  initPrincipals(lookup.principals());
+  initPrincipalsAndMutedErrors(lookup.principals(), lookup.mutedErrors());
 }
 
 /* static */
 SavedFrame* SavedFrame::create(JSContext* cx) {
   RootedGlobalObject global(cx, cx->global());
   cx->check(global);
 
   // Ensure that we don't try to capture the stack again in the
@@ -1461,17 +1477,18 @@ bool SavedStacks::insertFrames(JSContext
 
     auto principals = iter.realm()->principals();
     MOZ_ASSERT_IF(framePtr && !iter.isWasm(), iter.pc());
 
     if (!stackChain.emplaceBack(location.source(), location.sourceId(),
                                 location.line(), location.column(), displayAtom,
                                 nullptr,  // asyncCause
                                 nullptr,  // parent (not known yet)
-                                principals, framePtr, iter.pc(), &activation)) {
+                                principals, iter.mutedErrors(), framePtr,
+                                iter.pc(), &activation)) {
       ReportOutOfMemory(cx);
       return false;
     }
 
     if (captureIsSatisfied(cx, principals, location.source(), capture)) {
       // The stack should end after the frame we just saved.
       parent.set(nullptr);
       break;
@@ -1973,17 +1990,18 @@ JS_PUBLIC_API bool ConstructSavedFrameSt
 
     auto principals =
         js::ReconstructedSavedFramePrincipals::getSingleton(ubiFrame.get());
 
     if (!stackChain.emplaceBack(source, ubiFrame.get().sourceId(),
                                 ubiFrame.get().line(), ubiFrame.get().column(),
                                 functionDisplayName,
                                 /* asyncCause */ nullptr,
-                                /* parent */ nullptr, principals)) {
+                                /* parent */ nullptr, principals,
+                                /* mutedErrors */ true)) {
       ReportOutOfMemory(cx);
       return false;
     }
 
     ubiFrame = ubiFrame.get().parent();
   }
 
   js::RootedSavedFrame parentFrame(cx);
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -1629,16 +1629,21 @@ bool JSStructuredCloneWriter::traverseSa
     }
   }
 
   // Write the SavedFrame's reserved slots, except for the parent, which is
   // queued on objs for further traversal.
 
   RootedValue val(context());
 
+  val = BooleanValue(savedFrame->getMutedErrors());
+  if (!startWrite(val)) {
+    return false;
+  }
+
   context()->markAtom(savedFrame->getSource());
   val = StringValue(savedFrame->getSource());
   if (!startWrite(val)) {
     return false;
   }
 
   val = NumberValue(savedFrame->getLine());
   if (!startWrite(val)) {
@@ -2907,22 +2912,46 @@ JSObject* JSStructuredCloneReader::readS
   } else if (principalsTag == SCTAG_NULL_JSPRINCIPALS) {
     principals = nullptr;
   } else {
     JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
                               JSMSG_SC_BAD_SERIALIZED_DATA,
                               "bad SavedFrame principals");
     return nullptr;
   }
-  savedFrame->initPrincipalsAlreadyHeld(principals);
-
+
+  RootedValue mutedErrors(context());
   RootedValue source(context());
-  if (!startRead(&source) || !source.isString()) {
-    return nullptr;
+  {
+    // Read a |mutedErrors| boolean followed by a |source| string.
+    // The |mutedErrors| boolean is present in all new structured-clone data,
+    // but in older data it will be absent and only the |source| string will be
+    // found.
+    if (!startRead(&mutedErrors)) {
+      return nullptr;
+    }
+
+    if (mutedErrors.isBoolean()) {
+      if (!startRead(&source) || !source.isString()) {
+        return nullptr;
+      }
+    } else if (mutedErrors.isString()) {
+      // Backwards compatibility: Handle missing |mutedErrors| boolean,
+      // this is actually just a |source| string.
+      source = mutedErrors;
+      mutedErrors.setBoolean(true);  // Safe default value.
+    } else {
+      // Invalid type.
+      return nullptr;
+    }
   }
+
+  savedFrame->initPrincipalsAlreadyHeldAndMutedErrors(principals,
+                                                      mutedErrors.toBoolean());
+
   auto atomSource = AtomizeString(context(), source.toString());
   if (!atomSource) {
     return nullptr;
   }
   savedFrame->initSource(atomSource);
 
   RootedValue lineVal(context());
   uint32_t line;