Bug 1117242 - SavedFrame objects should do principal checks for every accessor; r=jimb,jandem,bz

This commit is contained in:
Nick Fitzgerald 2015-02-13 09:21:50 -08:00
parent c9c82c55da
commit 770748abfb
20 changed files with 469 additions and 156 deletions

View File

@ -739,11 +739,10 @@ DOMException::Sanitize(JSContext* aCx,
// Now it's possible that the stack on retval still starts with // Now it's possible that the stack on retval still starts with
// stuff aCx is not supposed to touch; it depends on what's on the // stuff aCx is not supposed to touch; it depends on what's on the
// stack right this second. Walk past all of that. // stack right this second. Walk past all of that.
while (retval->mLocation && !retval->mLocation->CallerSubsumes(aCx)) { nsCOMPtr<nsIStackFrame> stack;
nsCOMPtr<nsIStackFrame> caller; nsresult rv = retval->mLocation->GetSanitized(aCx, getter_AddRefs(stack));
retval->mLocation->GetCaller(getter_AddRefs(caller)); NS_ENSURE_SUCCESS(rv, false);
retval->mLocation.swap(caller); retval->mLocation.swap(stack);
}
} }
return ToJSValue(aCx, retval, aSanitizedValue); return ToJSValue(aCx, retval, aSanitizedValue);

View File

@ -6,6 +6,7 @@
#include "mozilla/dom/Exceptions.h" #include "mozilla/dom/Exceptions.h"
#include "js/GCAPI.h" #include "js/GCAPI.h"
#include "js/TypeDecls.h"
#include "jsapi.h" #include "jsapi.h"
#include "jsprf.h" #include "jsprf.h"
#include "mozilla/CycleCollectedJSRuntime.h" #include "mozilla/CycleCollectedJSRuntime.h"
@ -299,6 +300,8 @@ public:
NS_IMETHOD GetCaller(nsIStackFrame** aCaller) MOZ_OVERRIDE; NS_IMETHOD GetCaller(nsIStackFrame** aCaller) MOZ_OVERRIDE;
NS_IMETHOD GetFormattedStack(nsAString& aStack) MOZ_OVERRIDE; NS_IMETHOD GetFormattedStack(nsAString& aStack) MOZ_OVERRIDE;
virtual bool CallerSubsumes(JSContext* aCx) MOZ_OVERRIDE; virtual bool CallerSubsumes(JSContext* aCx) MOZ_OVERRIDE;
NS_IMETHOD GetSanitized(JSContext* aCx,
nsIStackFrame** aSanitized) MOZ_OVERRIDE;
protected: protected:
virtual bool IsJSFrame() const MOZ_OVERRIDE { virtual bool IsJSFrame() const MOZ_OVERRIDE {
@ -390,15 +393,22 @@ NS_IMETHODIMP JSStackFrame::GetFilename(nsAString& aFilename)
JS::Rooted<JSObject*> stack(cx, mStack); JS::Rooted<JSObject*> stack(cx, mStack);
JS::ExposeObjectToActiveJS(mStack); JS::ExposeObjectToActiveJS(mStack);
JSAutoCompartment ac(cx, stack); JSAutoCompartment ac(cx, stack);
JS::Rooted<JS::Value> filenameVal(cx); JS::Rooted<JS::Value> filenameVal(cx);
if (!JS_GetProperty(cx, stack, "source", &filenameVal) || if (!JS_GetProperty(cx, stack, "source", &filenameVal)) {
!filenameVal.isString()) {
return NS_ERROR_UNEXPECTED; return NS_ERROR_UNEXPECTED;
} }
if (filenameVal.isNull()) {
filenameVal = JS_GetEmptyStringValue(cx);
}
MOZ_ASSERT(filenameVal.isString());
nsAutoJSString str; nsAutoJSString str;
if (!str.init(cx, filenameVal.toString())) { if (!str.init(cx, filenameVal.toString())) {
return NS_ERROR_OUT_OF_MEMORY; return NS_ERROR_OUT_OF_MEMORY;
} }
mFilename = str; mFilename = str;
mFilenameInitialized = true; mFilenameInitialized = true;
} }
@ -471,11 +481,15 @@ JSStackFrame::GetLineno(int32_t* aLineNo)
JS::ExposeObjectToActiveJS(mStack); JS::ExposeObjectToActiveJS(mStack);
JSAutoCompartment ac(cx, stack); JSAutoCompartment ac(cx, stack);
JS::Rooted<JS::Value> lineVal(cx); JS::Rooted<JS::Value> lineVal(cx);
if (!JS_GetProperty(cx, stack, "line", &lineVal) || if (!JS_GetProperty(cx, stack, "line", &lineVal)) {
!lineVal.isNumber()) {
return NS_ERROR_UNEXPECTED; return NS_ERROR_UNEXPECTED;
} }
mLineno = lineVal.toNumber(); if (lineVal.isNumber()) {
mLineno = lineVal.toNumber();
} else {
MOZ_ASSERT(lineVal.isNull());
mLineno = 0;
}
mLinenoInitialized = true; mLinenoInitialized = true;
} }
@ -500,11 +514,15 @@ JSStackFrame::GetColNo(int32_t* aColNo)
JS::ExposeObjectToActiveJS(mStack); JS::ExposeObjectToActiveJS(mStack);
JSAutoCompartment ac(cx, stack); JSAutoCompartment ac(cx, stack);
JS::Rooted<JS::Value> colVal(cx); JS::Rooted<JS::Value> colVal(cx);
if (!JS_GetProperty(cx, stack, "column", &colVal) || if (!JS_GetProperty(cx, stack, "column", &colVal)) {
!colVal.isNumber()) {
return NS_ERROR_UNEXPECTED; return NS_ERROR_UNEXPECTED;
} }
mColNo = colVal.toNumber(); if (colVal.isNumber()) {
mColNo = colVal.toNumber();
} else {
MOZ_ASSERT(colVal.isNull());
mColNo = 0;
}
mColNoInitialized = true; mColNoInitialized = true;
} }
@ -524,6 +542,35 @@ NS_IMETHODIMP StackFrame::GetSourceLine(nsACString& aSourceLine)
return NS_OK; return NS_OK;
} }
/* [noscript] readonly attribute nsIStackFrame sanitized */
NS_IMETHODIMP StackFrame::GetSanitized(JSContext*, nsIStackFrame** aSanitized)
{
NS_ADDREF(*aSanitized = this);
return NS_OK;
}
/* [noscript] readonly attribute nsIStackFrame sanitized */
NS_IMETHODIMP JSStackFrame::GetSanitized(JSContext* aCx, nsIStackFrame** aSanitized)
{
// NB: Do _not_ enter the compartment of the SavedFrame object here, because
// we are checking against the caller's compartment's principals in
// GetFirstSubsumedSavedFrame.
JS::RootedObject savedFrame(aCx, mStack);
JS::ExposeObjectToActiveJS(mStack);
savedFrame = js::GetFirstSubsumedSavedFrame(aCx, savedFrame);
nsCOMPtr<nsIStackFrame> stackFrame;
if (savedFrame) {
stackFrame = new JSStackFrame(savedFrame);
} else {
stackFrame = new StackFrame();
}
stackFrame.forget(aSanitized);
return NS_OK;
}
/* readonly attribute nsIStackFrame caller; */ /* readonly attribute nsIStackFrame caller; */
NS_IMETHODIMP JSStackFrame::GetCaller(nsIStackFrame** aCaller) NS_IMETHODIMP JSStackFrame::GetCaller(nsIStackFrame** aCaller)
{ {

View File

@ -967,9 +967,30 @@ SaveStack(JSContext *cx, unsigned argc, jsval *vp)
maxFrameCount = d; maxFrameCount = d;
} }
Rooted<JSObject*> stack(cx); JSCompartment *targetCompartment = cx->compartment();
if (!JS::CaptureCurrentStack(cx, &stack, maxFrameCount)) if (args.length() >= 2) {
if (!args[1].isObject()) {
js_ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
JSDVG_SEARCH_STACK, args[0], JS::NullPtr(),
"not an object", NULL);
return false;
}
RootedObject obj(cx, UncheckedUnwrap(&args[1].toObject()));
if (!obj)
return false;
targetCompartment = obj->compartment();
}
RootedObject stack(cx);
{
AutoCompartment ac(cx, targetCompartment);
if (!JS::CaptureCurrentStack(cx, &stack, maxFrameCount))
return false;
}
if (stack && !cx->compartment()->wrap(cx, &stack))
return false; return false;
args.rval().setObjectOrNull(stack); args.rval().setObjectOrNull(stack);
return true; return true;
} }
@ -2396,13 +2417,15 @@ static const JSFunctionSpecWithHelp TestingFunctions[] = {
" SavedStacks cache."), " SavedStacks cache."),
JS_FN_HELP("saveStack", SaveStack, 0, 0, JS_FN_HELP("saveStack", SaveStack, 0, 0,
"saveStack()", "saveStack([maxDepth [, compartment]])",
" Capture a stack.\n"), " Capture a stack. If 'maxDepth' is given, capture at most 'maxDepth' number\n"
" of frames. If 'compartment' is given, allocate the js::SavedFrame instances\n"
" with the given object's compartment."),
JS_FN_HELP("enableTrackAllocations", EnableTrackAllocations, 0, 0, JS_FN_HELP("enableTrackAllocations", EnableTrackAllocations, 0, 0,
"enableTrackAllocations()", "enableTrackAllocations()",
" Start capturing the JS stack at every allocation. Note that this sets an " " Start capturing the JS stack at every allocation. Note that this sets an\n"
" object metadata callback that will override any other object metadata " " object metadata callback that will override any other object metadata\n"
" callback that may be set."), " callback that may be set."),
JS_FN_HELP("disableTrackAllocations", DisableTrackAllocations, 0, 0, JS_FN_HELP("disableTrackAllocations", DisableTrackAllocations, 0, 0,

View File

@ -5,6 +5,44 @@ JavaScript call stack at a past moment of execution. Younger frames hold a
reference to the frames that invoked them. The older tails are shared across reference to the frames that invoked them. The older tails are shared across
many younger frames. many younger frames.
`SavedFrame` stacks should generally be captured, allocated, and live within the
compartment that is being observed or debugged. Usually this is a content
compartment.
## Capturing `SavedFrame` Stacks
### From C++
Use `JS::CaptureCurrentStack` declared in `jsapi.h`.
### From JS
Use `saveStack`, accessible via `Components.utils.getJSTestingFunction()`.
## Including and Excluding Chrome Frames
Consider the following `SavedFrame` stack. Arrows represent links from child to
parent frame, `content.js` is from a compartment with content principals, and
`chrome.js` is from a compartment with chrome principals.
function A from content.js
|
V
function B from chrome.js
|
V
function C from content.js
The content compartment will ever have one view of this stack: `A -> C`.
However, a chrome compartment has a choice: it can either take the same view
that the content compartment has (`A -> C`), or it can view all stack frames,
including the frames from chrome compartments (`A -> B -> C`). To view
everything, use an `XrayWrapper`. This is the default wrapper. To see the stack
as the content compartment sees it, waive the xray wrapper with
`Components.utils.waiveXrays`:
const contentViewOfStack = Components.utils.waiveXrays(someStack);
## Accessor Properties of the `SavedFrame.prototype` Object ## Accessor Properties of the `SavedFrame.prototype` Object

View File

@ -17,6 +17,7 @@ namespace js {
class PropertyName; class PropertyName;
class NativeObject; class NativeObject;
class ArrayObject; class ArrayObject;
class GlobalObject;
class PlainObject; class PlainObject;
class ScriptSourceObject; class ScriptSourceObject;
class Shape; class Shape;
@ -45,6 +46,7 @@ typedef JS::Rooted<JSAtom*> RootedAtom;
typedef JS::Rooted<JSLinearString*> RootedLinearString; typedef JS::Rooted<JSLinearString*> RootedLinearString;
typedef JS::Rooted<PropertyName*> RootedPropertyName; typedef JS::Rooted<PropertyName*> RootedPropertyName;
typedef JS::Rooted<ArrayObject*> RootedArrayObject; typedef JS::Rooted<ArrayObject*> RootedArrayObject;
typedef JS::Rooted<GlobalObject*> RootedGlobalObject;
typedef JS::Rooted<PlainObject*> RootedPlainObject; typedef JS::Rooted<PlainObject*> RootedPlainObject;
typedef JS::Rooted<ScriptSourceObject*> RootedScriptSource; typedef JS::Rooted<ScriptSourceObject*> RootedScriptSource;

View File

@ -42,7 +42,7 @@ var count = 0;
low .eval('function b() { check("b", extract(saveStack())); c(); }'); low .eval('function b() { check("b", extract(saveStack())); c(); }');
mid .eval('function c() { check("cba", extract(saveStack())); d(); }'); mid .eval('function c() { check("cba", extract(saveStack())); d(); }');
high.eval('function d() { check("dcba", extract(saveStack())); e(); }'); high.eval('function d() { check("dcba", extract(saveStack())); e(); }');
eval('function e() { check("edcba", extract(saveStack())); f(); }'); // no principal, so checks skipped eval('function e() { check("ecba", extract(saveStack())); f(); }');
low .eval('function f() { check("fb", extract(saveStack())); g(); }'); low .eval('function f() { check("fb", extract(saveStack())); g(); }');
mid .eval('function g() { check("gfecba", extract(saveStack())); h(); }'); mid .eval('function g() { check("gfecba", extract(saveStack())); h(); }');
high.eval('function h() { check("hgfedcba", extract(saveStack())); }'); high.eval('function h() { check("hgfedcba", extract(saveStack())); }');

View File

@ -33,7 +33,7 @@ var high = newGlobal({ principal: 0xfffff });
low .eval('function b() { check("b", saveStack().toString()); c(); }'); low .eval('function b() { check("b", saveStack().toString()); c(); }');
mid .eval('function c() { check("cba", saveStack().toString()); d(); }'); mid .eval('function c() { check("cba", saveStack().toString()); d(); }');
high.eval('function d() { check("dcba", saveStack().toString()); e(); }'); high.eval('function d() { check("dcba", saveStack().toString()); e(); }');
eval('function e() { check("edcba", saveStack().toString()); f(); }'); // no principal, so checks skipped eval('function e() { check("ecba", saveStack().toString()); f(); }');
low .eval('function f() { check("fb", saveStack().toString()); g(); }'); low .eval('function f() { check("fb", saveStack().toString()); g(); }');
mid .eval('function g() { check("gfecba", saveStack().toString()); h(); }'); mid .eval('function g() { check("gfecba", saveStack().toString()); h(); }');
high.eval('function h() { check("hgfedcba", saveStack().toString()); }'); high.eval('function h() { check("hgfedcba", saveStack().toString()); }');

View File

@ -0,0 +1,23 @@
// With arrows representing child-to-parent links, create a SavedFrame stack
// like this:
//
// high.a -> low.b
//
// in `low`'s compartment and give `low` a reference to this stack. Assert the
// stack's youngest frame's properties doesn't leak information about `high.a`
// that `low` shouldn't have access to, and instead returns information about
// `low.b`.
var low = newGlobal({ principal: 0 });
var high = newGlobal({ principal: 0xfffff });
low.high = high;
high.low = low;
high.eval("function a() { return saveStack(0, low); }");
low.eval("function b() { return high.a(); }")
var stack = low.b();
assertEq(stack.functionDisplayName, "b");
assertEq(stack.parent, null);

View File

@ -0,0 +1,15 @@
// Test what happens when a compartment gets a SavedFrame that it doesn't have
// the principals to access any of its frames.
var low = newGlobal({ principal: 0 });
var high = newGlobal({ principal: 0xfffff });
low.high = high;
high.low = low;
high.eval("function a() { return saveStack(1, low); }");
var stack = low.eval("high.a();")
assertEq(stack.functionDisplayName, null);
assertEq(stack.parent, null);
assertEq(stack.toString(), "");

View File

@ -2622,6 +2622,17 @@ GetObjectEnvironmentObjectForFunction(JSFunction *fun);
extern JS_FRIEND_API(JSPrincipals *) extern JS_FRIEND_API(JSPrincipals *)
GetSavedFramePrincipals(JS::HandleObject savedFrame); GetSavedFramePrincipals(JS::HandleObject savedFrame);
/*
* Get the first SavedFrame object in this SavedFrame stack whose principals are
* subsumed by the cx's principals. If there is no such frame, return nullptr.
*
* Do NOT pass a non-SavedFrame object here.
*
* The savedFrame and cx do not need to be in the same compartment.
*/
extern JS_FRIEND_API(JSObject *)
GetFirstSubsumedSavedFrame(JSContext *cx, JS::HandleObject savedFrame);
} /* namespace js */ } /* namespace js */
extern JS_FRIEND_API(bool) extern JS_FRIEND_API(bool)

View File

@ -111,6 +111,8 @@ IF_SAB(real,imaginary)(SharedFloat64Array, 50, js_InitViaClassSpec,
IF_SAB(real,imaginary)(SharedUint8ClampedArray, 51, js_InitViaClassSpec, SHARED_TYPED_ARRAY_CLASP(Uint8Clamped)) \ IF_SAB(real,imaginary)(SharedUint8ClampedArray, 51, js_InitViaClassSpec, SHARED_TYPED_ARRAY_CLASP(Uint8Clamped)) \
real(TypedArray, 52, js_InitViaClassSpec, &js::TypedArrayObject::sharedTypedArrayPrototypeClass) \ real(TypedArray, 52, js_InitViaClassSpec, &js::TypedArrayObject::sharedTypedArrayPrototypeClass) \
IF_SAB(real,imaginary)(Atomics, 53, js_InitAtomicsClass, OCLASP(Atomics)) \ IF_SAB(real,imaginary)(Atomics, 53, js_InitAtomicsClass, OCLASP(Atomics)) \
real(SavedFrame, 54, js_InitViaClassSpec, &js::SavedFrame::class_) \
#define JS_FOR_EACH_PROTOTYPE(macro) JS_FOR_PROTOTYPES(macro,macro) #define JS_FOR_EACH_PROTOTYPE(macro) JS_FOR_PROTOTYPES(macro,macro)

View File

@ -60,8 +60,6 @@ using mozilla::PodCopy;
using mozilla::PodZero; using mozilla::PodZero;
using mozilla::RotateLeft; using mozilla::RotateLeft;
typedef Rooted<GlobalObject *> RootedGlobalObject;
/* static */ BindingIter /* static */ BindingIter
Bindings::argumentsBinding(ExclusiveContext *cx, InternalBindingsHandle bindings) Bindings::argumentsBinding(ExclusiveContext *cx, InternalBindingsHandle bindings)
{ {

View File

@ -321,7 +321,7 @@ class GlobalObject : public NativeObject
NativeObject *getOrCreateObjectPrototype(JSContext *cx) { NativeObject *getOrCreateObjectPrototype(JSContext *cx) {
if (functionObjectClassesInitialized()) if (functionObjectClassesInitialized())
return &getPrototype(JSProto_Object).toObject().as<NativeObject>(); return &getPrototype(JSProto_Object).toObject().as<NativeObject>();
Rooted<GlobalObject*> self(cx, this); RootedGlobalObject self(cx, this);
if (!ensureConstructor(cx, self, JSProto_Object)) if (!ensureConstructor(cx, self, JSProto_Object))
return nullptr; return nullptr;
return &self->getPrototype(JSProto_Object).toObject().as<NativeObject>(); return &self->getPrototype(JSProto_Object).toObject().as<NativeObject>();
@ -330,7 +330,7 @@ class GlobalObject : public NativeObject
NativeObject *getOrCreateFunctionPrototype(JSContext *cx) { NativeObject *getOrCreateFunctionPrototype(JSContext *cx) {
if (functionObjectClassesInitialized()) if (functionObjectClassesInitialized())
return &getPrototype(JSProto_Function).toObject().as<NativeObject>(); return &getPrototype(JSProto_Function).toObject().as<NativeObject>();
Rooted<GlobalObject*> self(cx, this); RootedGlobalObject self(cx, this);
if (!ensureConstructor(cx, self, JSProto_Object)) if (!ensureConstructor(cx, self, JSProto_Object))
return nullptr; return nullptr;
return &self->getPrototype(JSProto_Function).toObject().as<NativeObject>(); return &self->getPrototype(JSProto_Function).toObject().as<NativeObject>();
@ -384,6 +384,13 @@ class GlobalObject : public NativeObject
return nullptr; return nullptr;
} }
static NativeObject *getOrCreateSavedFramePrototype(JSContext *cx,
Handle<GlobalObject*> global) {
if (!ensureConstructor(cx, global, JSProto_SavedFrame))
return nullptr;
return &global->getPrototype(JSProto_SavedFrame).toObject().as<NativeObject>();
}
static JSObject *getOrCreateArrayBufferPrototype(JSContext *cx, Handle<GlobalObject*> global) { static JSObject *getOrCreateArrayBufferPrototype(JSContext *cx, Handle<GlobalObject*> global) {
if (!ensureConstructor(cx, global, JSProto_ArrayBuffer)) if (!ensureConstructor(cx, global, JSProto_ArrayBuffer))
return nullptr; return nullptr;
@ -483,7 +490,7 @@ class GlobalObject : public NativeObject
Value v = getSlotRef(slot); Value v = getSlotRef(slot);
if (v.isObject()) if (v.isObject())
return &v.toObject(); return &v.toObject();
Rooted<GlobalObject*> self(cx, this); RootedGlobalObject self(cx, this);
if (!init(cx, self)) if (!init(cx, self))
return nullptr; return nullptr;
return &self->getSlot(slot).toObject(); return &self->getSlot(slot).toObject();
@ -561,7 +568,7 @@ class GlobalObject : public NativeObject
} }
JSObject *getOrCreateDataViewPrototype(JSContext *cx) { JSObject *getOrCreateDataViewPrototype(JSContext *cx) {
Rooted<GlobalObject*> self(cx, this); RootedGlobalObject self(cx, this);
if (!ensureConstructor(cx, self, JSProto_DataView)) if (!ensureConstructor(cx, self, JSProto_DataView))
return nullptr; return nullptr;
return &self->getPrototype(JSProto_DataView).toObject(); return &self->getPrototype(JSProto_DataView).toObject();

View File

@ -16,12 +16,13 @@
#include "jshashutil.h" #include "jshashutil.h"
#include "jsmath.h" #include "jsmath.h"
#include "jsnum.h" #include "jsnum.h"
#include "jsscript.h"
#include "prmjtime.h" #include "prmjtime.h"
#include "gc/Marking.h" #include "gc/Marking.h"
#include "gc/Rooting.h"
#include "js/Vector.h" #include "js/Vector.h"
#include "vm/Debugger.h" #include "vm/Debugger.h"
#include "vm/GlobalObject.h"
#include "vm/StringBuffer.h" #include "vm/StringBuffer.h"
#include "jscntxtinlines.h" #include "jscntxtinlines.h"
@ -136,18 +137,67 @@ SavedFrame::HashPolicy::rekey(Key &key, const Key &newKey)
key = newKey; key = newKey;
} }
/* static */ bool
SavedFrame::finishSavedFrameInit(JSContext *cx, HandleObject ctor, HandleObject proto)
{
// The only object with the SavedFrame::class_ that doesn't have a source
// should be the prototype.
proto->as<NativeObject>().setReservedSlot(SavedFrame::JSSLOT_SOURCE, NullValue());
return FreezeObject(cx, proto);
}
/* static */ const Class SavedFrame::class_ = { /* static */ const Class SavedFrame::class_ = {
"SavedFrame", "SavedFrame",
JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS | JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS |
JSCLASS_HAS_RESERVED_SLOTS(SavedFrame::JSSLOT_COUNT), JSCLASS_HAS_RESERVED_SLOTS(SavedFrame::JSSLOT_COUNT) |
nullptr, // addProperty JSCLASS_HAS_CACHED_PROTO(JSProto_SavedFrame) |
nullptr, // delProperty JSCLASS_IS_ANONYMOUS,
nullptr, // getProperty nullptr, // addProperty
nullptr, // setProperty nullptr, // delProperty
nullptr, // enumerate nullptr, // getProperty
nullptr, // resolve nullptr, // setProperty
nullptr, // convert nullptr, // enumerate
SavedFrame::finalize nullptr, // resolve
nullptr, // convert
SavedFrame::finalize, // finalize
nullptr, // call
nullptr, // hasInstance
nullptr, // construct
nullptr, // trace
// ClassSpec
{
GenericCreateConstructor<SavedFrame::construct, 0, JSFunction::FinalizeKind>,
GenericCreatePrototype,
SavedFrame::staticFunctions,
SavedFrame::protoFunctions,
SavedFrame::protoAccessors,
SavedFrame::finishSavedFrameInit,
ClassSpec::DontDefineConstructor
}
};
/* static */ const JSFunctionSpec
SavedFrame::staticFunctions[] = {
JS_FS_END
};
/* static */ const JSFunctionSpec
SavedFrame::protoFunctions[] = {
JS_FN("constructor", SavedFrame::construct, 0, 0),
JS_FN("toString", SavedFrame::toStringMethod, 0, 0),
JS_FS_END
};
/* static */ const JSPropertySpec
SavedFrame::protoAccessors[] = {
JS_PSG("source", SavedFrame::sourceProperty, 0),
JS_PSG("line", SavedFrame::lineProperty, 0),
JS_PSG("column", SavedFrame::columnProperty, 0),
JS_PSG("functionDisplayName", SavedFrame::functionDisplayNameProperty, 0),
JS_PSG("parent", SavedFrame::parentProperty, 0),
JS_PS_END
}; };
/* static */ void /* static */ void
@ -259,33 +309,67 @@ SavedFrame::construct(JSContext *cx, unsigned argc, Value *vp)
return false; return false;
} }
/* static */ SavedFrame * // Return the first SavedFrame in the chain that starts with |frame| whose
SavedFrame::checkThis(JSContext *cx, CallArgs &args, const char *fnName) // principals are subsumed by |principals|, according to |subsumes|. If there is
// no such frame, return nullptr.
static SavedFrame *
GetFirstSubsumedFrame(JSContext *cx, HandleSavedFrame frame)
{
JSSubsumesOp subsumes = cx->runtime()->securityCallbacks->subsumes;
if (!subsumes)
return frame;
JSPrincipals *principals = cx->compartment()->principals;
RootedSavedFrame rootedFrame(cx, frame);
while (rootedFrame && !subsumes(principals, rootedFrame->getPrincipals()))
rootedFrame = rootedFrame->getParent();
return rootedFrame;
}
JS_FRIEND_API(JSObject *)
GetFirstSubsumedSavedFrame(JSContext *cx, HandleObject savedFrame)
{
if (!savedFrame)
return nullptr;
RootedSavedFrame frame(cx, &savedFrame->as<SavedFrame>());
return GetFirstSubsumedFrame(cx, frame);
}
/* static */ bool
SavedFrame::checkThis(JSContext *cx, CallArgs &args, const char *fnName,
MutableHandleSavedFrame frame)
{ {
const Value &thisValue = args.thisv(); const Value &thisValue = args.thisv();
if (!thisValue.isObject()) { if (!thisValue.isObject()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT); JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT);
return nullptr; return false;
} }
JSObject &thisObject = thisValue.toObject(); JSObject *thisObject = CheckedUnwrap(&thisValue.toObject());
if (!thisObject.is<SavedFrame>()) { if (!thisObject || !thisObject->is<SavedFrame>()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
SavedFrame::class_.name, fnName, thisObject.getClass()->name); SavedFrame::class_.name, fnName,
return nullptr; thisObject ? thisObject->getClass()->name : "object");
return false;
} }
// Check for SavedFrame.prototype, which has the same class as SavedFrame // Check for SavedFrame.prototype, which has the same class as SavedFrame
// instances, however doesn't actually represent a captured stack frame. It // instances, however doesn't actually represent a captured stack frame. It
// is the only object that is<SavedFrame>() but doesn't have a source. // is the only object that is<SavedFrame>() but doesn't have a source.
if (thisObject.as<SavedFrame>().getReservedSlot(JSSLOT_SOURCE).isNull()) { if (thisObject->as<SavedFrame>().getReservedSlot(JSSLOT_SOURCE).isNull()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
SavedFrame::class_.name, fnName, "prototype object"); SavedFrame::class_.name, fnName, "prototype object");
return nullptr; return false;
} }
return &thisObject.as<SavedFrame>(); // The caller might not have the principals to see this frame's data, so get
// the first one they _do_ have access to.
RootedSavedFrame rooted(cx, &thisObject->as<SavedFrame>());
frame.set(GetFirstSubsumedFrame(cx, rooted));
return true;
} }
// Get the SavedFrame * from the current this value and handle any errors that // Get the SavedFrame * from the current this value and handle any errors that
@ -296,19 +380,24 @@ SavedFrame::checkThis(JSContext *cx, CallArgs &args, const char *fnName)
// - unsigned argc // - unsigned argc
// - Value *vp // - Value *vp
// - const char *fnName // - const char *fnName
// - Value defaultVal
// These parameters will be defined after calling this macro: // These parameters will be defined after calling this macro:
// - CallArgs args // - CallArgs args
// - Rooted<SavedFrame *> frame (will be non-null) // - Rooted<SavedFrame *> frame (will be non-null)
#define THIS_SAVEDFRAME(cx, argc, vp, fnName, args, frame) \ #define THIS_SAVEDFRAME(cx, argc, vp, fnName, defaultVal, args, frame) \
CallArgs args = CallArgsFromVp(argc, vp); \ CallArgs args = CallArgsFromVp(argc, vp); \
RootedSavedFrame frame(cx, checkThis(cx, args, fnName)); \ RootedSavedFrame frame(cx); \
if (!frame) \ if (!checkThis(cx, args, fnName, &frame)) \
return false return false; \
if (!frame) { \
args.rval().set(defaultVal); \
return true; \
}
/* static */ bool /* static */ bool
SavedFrame::sourceProperty(JSContext *cx, unsigned argc, Value *vp) SavedFrame::sourceProperty(JSContext *cx, unsigned argc, Value *vp)
{ {
THIS_SAVEDFRAME(cx, argc, vp, "(get source)", args, frame); THIS_SAVEDFRAME(cx, argc, vp, "(get source)", NullValue(), args, frame);
args.rval().setString(frame->getSource()); args.rval().setString(frame->getSource());
return true; return true;
} }
@ -316,7 +405,7 @@ SavedFrame::sourceProperty(JSContext *cx, unsigned argc, Value *vp)
/* static */ bool /* static */ bool
SavedFrame::lineProperty(JSContext *cx, unsigned argc, Value *vp) SavedFrame::lineProperty(JSContext *cx, unsigned argc, Value *vp)
{ {
THIS_SAVEDFRAME(cx, argc, vp, "(get line)", args, frame); THIS_SAVEDFRAME(cx, argc, vp, "(get line)", NullValue(), args, frame);
uint32_t line = frame->getLine(); uint32_t line = frame->getLine();
args.rval().setNumber(line); args.rval().setNumber(line);
return true; return true;
@ -325,7 +414,7 @@ SavedFrame::lineProperty(JSContext *cx, unsigned argc, Value *vp)
/* static */ bool /* static */ bool
SavedFrame::columnProperty(JSContext *cx, unsigned argc, Value *vp) SavedFrame::columnProperty(JSContext *cx, unsigned argc, Value *vp)
{ {
THIS_SAVEDFRAME(cx, argc, vp, "(get column)", args, frame); THIS_SAVEDFRAME(cx, argc, vp, "(get column)", NullValue(), args, frame);
uint32_t column = frame->getColumn(); uint32_t column = frame->getColumn();
args.rval().setNumber(column); args.rval().setNumber(column);
return true; return true;
@ -334,7 +423,7 @@ SavedFrame::columnProperty(JSContext *cx, unsigned argc, Value *vp)
/* static */ bool /* static */ bool
SavedFrame::functionDisplayNameProperty(JSContext *cx, unsigned argc, Value *vp) SavedFrame::functionDisplayNameProperty(JSContext *cx, unsigned argc, Value *vp)
{ {
THIS_SAVEDFRAME(cx, argc, vp, "(get functionDisplayName)", args, frame); THIS_SAVEDFRAME(cx, argc, vp, "(get functionDisplayName)", NullValue(), args, frame);
RootedAtom name(cx, frame->getFunctionDisplayName()); RootedAtom name(cx, frame->getFunctionDisplayName());
if (name) if (name)
args.rval().setString(name); args.rval().setString(name);
@ -346,55 +435,45 @@ SavedFrame::functionDisplayNameProperty(JSContext *cx, unsigned argc, Value *vp)
/* static */ bool /* static */ bool
SavedFrame::parentProperty(JSContext *cx, unsigned argc, Value *vp) SavedFrame::parentProperty(JSContext *cx, unsigned argc, Value *vp)
{ {
THIS_SAVEDFRAME(cx, argc, vp, "(get parent)", args, frame); THIS_SAVEDFRAME(cx, argc, vp, "(get parent)", NullValue(), args, frame);
JSSubsumesOp subsumes = cx->runtime()->securityCallbacks->subsumes; RootedSavedFrame parent(cx, frame->getParent());
JSPrincipals *principals = cx->compartment()->principals; args.rval().setObjectOrNull(GetFirstSubsumedFrame(cx, parent));
do
frame = frame->getParent();
while (frame && principals && subsumes &&
!subsumes(principals, frame->getPrincipals()));
args.rval().setObjectOrNull(frame);
return true; return true;
} }
/* static */ const JSPropertySpec SavedFrame::properties[] = {
JS_PSG("source", SavedFrame::sourceProperty, 0),
JS_PSG("line", SavedFrame::lineProperty, 0),
JS_PSG("column", SavedFrame::columnProperty, 0),
JS_PSG("functionDisplayName", SavedFrame::functionDisplayNameProperty, 0),
JS_PSG("parent", SavedFrame::parentProperty, 0),
JS_PS_END
};
/* static */ bool /* static */ bool
SavedFrame::toStringMethod(JSContext *cx, unsigned argc, Value *vp) SavedFrame::toStringMethod(JSContext *cx, unsigned argc, Value *vp)
{ {
THIS_SAVEDFRAME(cx, argc, vp, "toString", args, frame); THIS_SAVEDFRAME(cx, argc, vp, "toString", StringValue(cx->runtime()->emptyString), args, frame);
StringBuffer sb(cx); StringBuffer sb(cx);
JSSubsumesOp subsumes = cx->runtime()->securityCallbacks->subsumes; DebugOnly<JSSubsumesOp> subsumes = cx->runtime()->securityCallbacks->subsumes;
JSPrincipals *principals = cx->compartment()->principals; DebugOnly<JSPrincipals *> principals = cx->compartment()->principals;
RootedSavedFrame parent(cx);
do { do {
if (principals && subsumes && !subsumes(principals, frame->getPrincipals())) MOZ_ASSERT_IF(subsumes, (*subsumes)(principals, frame->getPrincipals()));
continue;
if (frame->isSelfHosted()) if (frame->isSelfHosted())
continue; goto nextIteration;
RootedAtom name(cx, frame->getFunctionDisplayName());
if ((name && !sb.append(name))
|| !sb.append('@')
|| !sb.append(frame->getSource())
|| !sb.append(':')
|| !NumberValueToStringBuffer(cx, NumberValue(frame->getLine()), sb)
|| !sb.append(':')
|| !NumberValueToStringBuffer(cx, NumberValue(frame->getColumn()), sb)
|| !sb.append('\n'))
{ {
return false; RootedAtom name(cx, frame->getFunctionDisplayName());
if ((name && !sb.append(name))
|| !sb.append('@')
|| !sb.append(frame->getSource())
|| !sb.append(':')
|| !NumberValueToStringBuffer(cx, NumberValue(frame->getLine()), sb)
|| !sb.append(':')
|| !NumberValueToStringBuffer(cx, NumberValue(frame->getColumn()), sb)
|| !sb.append('\n'))
{
return false;
}
} }
} while ((frame = frame->getParent()));
nextIteration:
parent = frame->getParent();
frame = GetFirstSubsumedFrame(cx, parent);
} while (frame);
JSString *str = sb.finishString(); JSString *str = sb.finishString();
if (!str) if (!str)
@ -403,12 +482,6 @@ SavedFrame::toStringMethod(JSContext *cx, unsigned argc, Value *vp)
return true; return true;
} }
/* static */ const JSFunctionSpec SavedFrame::methods[] = {
JS_FN("constructor", SavedFrame::construct, 0, 0),
JS_FN("toString", SavedFrame::toStringMethod, 0, 0),
JS_FS_END
};
bool bool
SavedStacks::init() SavedStacks::init()
{ {
@ -460,12 +533,6 @@ SavedStacks::sweep(JSRuntime *rt)
} }
sweepPCLocationMap(); sweepPCLocationMap();
if (savedFrameProto.unbarrieredGet() &&
IsObjectAboutToBeFinalizedFromAnyThread(savedFrameProto.unsafeGet()))
{
savedFrameProto.set(nullptr);
}
} }
void void
@ -587,49 +654,17 @@ SavedStacks::getOrCreateSavedFrame(JSContext *cx, SavedFrame::HandleLookup looku
return frame; return frame;
} }
JSObject *
SavedStacks::getOrCreateSavedFramePrototype(JSContext *cx)
{
if (savedFrameProto)
return savedFrameProto;
Rooted<GlobalObject *> global(cx, cx->compartment()->maybeGlobal());
if (!global)
return nullptr;
Rooted<SavedFrame *> proto(cx,
NewObjectWithGivenProto<SavedFrame>(cx, global->getOrCreateObjectPrototype(cx), global));
if (!proto
|| !JS_DefineProperties(cx, proto, SavedFrame::properties)
|| !JS_DefineFunctions(cx, proto, SavedFrame::methods)
|| !FreezeObject(cx, proto))
{
return nullptr;
}
// The only object with the SavedFrame::class_ that doesn't have a source
// should be the prototype.
proto->setReservedSlot(SavedFrame::JSSLOT_SOURCE, NullValue());
savedFrameProto.set(proto);
return savedFrameProto;
}
SavedFrame * SavedFrame *
SavedStacks::createFrameFromLookup(JSContext *cx, SavedFrame::HandleLookup lookup) SavedStacks::createFrameFromLookup(JSContext *cx, SavedFrame::HandleLookup lookup)
{ {
RootedObject proto(cx, getOrCreateSavedFramePrototype(cx)); RootedGlobalObject global(cx, cx->global());
assertSameCompartment(cx, global);
RootedNativeObject proto(cx, GlobalObject::getOrCreateSavedFramePrototype(cx, global));
if (!proto) if (!proto)
return nullptr; return nullptr;
assertSameCompartment(cx, proto); assertSameCompartment(cx, proto);
RootedObject global(cx, cx->compartment()->maybeGlobal());
if (!global)
return nullptr;
assertSameCompartment(cx, global);
RootedObject frameObj(cx, NewObjectWithGivenProto(cx, &SavedFrame::class_, proto, global)); RootedObject frameObj(cx, NewObjectWithGivenProto(cx, &SavedFrame::class_, proto, global));
if (!frameObj) if (!frameObj)
return nullptr; return nullptr;

View File

@ -14,16 +14,22 @@
namespace js { namespace js {
class SavedFrame;
typedef JS::Handle<SavedFrame*> HandleSavedFrame;
typedef JS::MutableHandle<SavedFrame*> MutableHandleSavedFrame;
typedef JS::Rooted<SavedFrame*> RootedSavedFrame;
class SavedFrame : public NativeObject { class SavedFrame : public NativeObject {
friend class SavedStacks; friend class SavedStacks;
public: public:
static const Class class_; static const Class class_;
static void finalize(FreeOp *fop, JSObject *obj); static void finalize(FreeOp *fop, JSObject *obj);
static const JSPropertySpec protoAccessors[];
static const JSFunctionSpec protoFunctions[];
static const JSFunctionSpec staticFunctions[];
// Prototype methods and properties to be exposed to JS. // Prototype methods and properties to be exposed to JS.
static const JSPropertySpec properties[];
static const JSFunctionSpec methods[];
static bool construct(JSContext *cx, unsigned argc, Value *vp); static bool construct(JSContext *cx, unsigned argc, Value *vp);
static bool sourceProperty(JSContext *cx, unsigned argc, Value *vp); static bool sourceProperty(JSContext *cx, unsigned argc, Value *vp);
static bool lineProperty(JSContext *cx, unsigned argc, Value *vp); static bool lineProperty(JSContext *cx, unsigned argc, Value *vp);
@ -53,6 +59,7 @@ class SavedFrame : public NativeObject {
class HandleLookup; class HandleLookup;
private: private:
static bool finishSavedFrameInit(JSContext *cx, HandleObject ctor, HandleObject proto);
void initFromLookup(HandleLookup lookup); void initFromLookup(HandleLookup lookup);
enum { enum {
@ -77,16 +84,13 @@ class SavedFrame : public NativeObject {
// know that GC moved the parent and we need to update our private value and // know that GC moved the parent and we need to update our private value and
// rekey the saved frame in its hash set. These two methods are helpers for // rekey the saved frame in its hash set. These two methods are helpers for
// this process. // this process.
bool parentMoved(); bool parentMoved();
void updatePrivateParent(); void updatePrivateParent();
static SavedFrame *checkThis(JSContext *cx, CallArgs &args, const char *fnName); static bool checkThis(JSContext *cx, CallArgs &args, const char *fnName,
MutableHandleSavedFrame frame);
}; };
typedef JS::Handle<SavedFrame*> HandleSavedFrame;
typedef JS::MutableHandle<SavedFrame*> MutableHandleSavedFrame;
typedef JS::Rooted<SavedFrame*> RootedSavedFrame;
struct SavedFrame::HashPolicy struct SavedFrame::HashPolicy
{ {
typedef SavedFrame::Lookup Lookup; typedef SavedFrame::Lookup Lookup;
@ -106,7 +110,6 @@ class SavedStacks {
public: public:
SavedStacks() SavedStacks()
: frames(), : frames(),
savedFrameProto(nullptr),
allocationSamplingProbability(1.0), allocationSamplingProbability(1.0),
allocationSkipCount(0), allocationSkipCount(0),
// XXX: Initialize the RNG state to 0 so that random_initSeed is lazily // XXX: Initialize the RNG state to 0 so that random_initSeed is lazily
@ -129,7 +132,6 @@ class SavedStacks {
private: private:
SavedFrame::Set frames; SavedFrame::Set frames;
ReadBarrieredObject savedFrameProto;
double allocationSamplingProbability; double allocationSamplingProbability;
uint32_t allocationSkipCount; uint32_t allocationSkipCount;
uint64_t rngState; uint64_t rngState;
@ -137,9 +139,6 @@ class SavedStacks {
bool insertFrames(JSContext *cx, FrameIter &iter, MutableHandleSavedFrame frame, bool insertFrames(JSContext *cx, FrameIter &iter, MutableHandleSavedFrame frame,
unsigned maxFrameCount = 0); unsigned maxFrameCount = 0);
SavedFrame *getOrCreateSavedFrame(JSContext *cx, SavedFrame::HandleLookup lookup); SavedFrame *getOrCreateSavedFrame(JSContext *cx, SavedFrame::HandleLookup lookup);
// |SavedFrame.prototype| is created lazily and held weakly. It should only
// be accessed through this method.
JSObject *getOrCreateSavedFramePrototype(JSContext *cx);
SavedFrame *createFrameFromLookup(JSContext *cx, SavedFrame::HandleLookup lookup); SavedFrame *createFrameFromLookup(JSContext *cx, SavedFrame::HandleLookup lookup);
void chooseSamplingProbability(JSContext* cx); void chooseSamplingProbability(JSContext* cx);

View File

@ -507,7 +507,7 @@ public:
// Mapping of often used strings to jsid atoms that live 'forever'. // Mapping of often used strings to jsid atoms that live 'forever'.
// //
// To add a new string: add to this list and to XPCJSRuntime::mStrings // To add a new string: add to this list and to XPCJSRuntime::mStrings
// at the top of xpcjsruntime.cpp // at the top of XPCJSRuntime.cpp
enum { enum {
IDX_CONSTRUCTOR = 0 , IDX_CONSTRUCTOR = 0 ,
IDX_TO_STRING , IDX_TO_STRING ,

View File

@ -0,0 +1,108 @@
// Bug 1117242: Test calling SavedFrame getters from globals that don't subsume
// that frame's principals.
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/jsdebugger.jsm");
addDebuggerToGlobal(this);
const lowP = Cc["@mozilla.org/nullprincipal;1"].createInstance(Ci.nsIPrincipal);
const midP = [lowP, "http://other.com"];
const highP = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal);
const low = new Cu.Sandbox(lowP);
const mid = new Cu.Sandbox(midP);
const high = new Cu.Sandbox(highP);
function run_test() {
// Test that the priveleged view of a SavedFrame from a subsumed compartment
// is the same view that the subsumed compartment gets. Create the following
// chain of function calls (with some intermediate system-principaled frames
// due to implementation):
//
// low.lowF -> mid.midF -> high.highF -> high.saveStack
//
// Where high.saveStack gets monkey patched to create stacks in each of our
// sandboxes.
Cu.evalInSandbox("function highF() { return saveStack(); }", high);
mid.highF = () => high.highF();
Cu.evalInSandbox("function midF() { return highF(); }", mid);
low.midF = () => mid.midF();
Cu.evalInSandbox("function lowF() { return midF(); }", low);
const expected = [
{
sandbox: low,
frames: ["lowF"],
},
{
sandbox: mid,
frames: ["midF", "lowF"],
},
{
sandbox: high,
frames: ["getSavedFrameInstanceFromSandbox",
"saveStack",
"highF",
"run_test/mid.highF",
"midF",
"run_test/low.midF",
"lowF",
"run_test",
"_execute_test",
null],
}
];
for (let { sandbox, frames } of expected) {
high.saveStack = function saveStack() {
return getSavedFrameInstanceFromSandbox(sandbox);
};
const xrayStack = low.lowF();
equal(xrayStack.functionDisplayName, "getSavedFrameInstanceFromSandbox",
"Xrays should always be able to see everything.");
let waived = Cu.waiveXrays(xrayStack);
do {
ok(frames.length,
"There should still be more expected frames while we have actual frames.");
equal(waived.functionDisplayName, frames.shift(),
"The waived wrapper should give us the stack's compartment's view.");
waived = waived.parent;
} while (waived);
}
}
// Get a SavedFrame instance from inside the given sandbox.
//
// We can't use Cu.getJSTestingFunctions().saveStack() because Cu isn't
// available to sandboxes that don't have the system principal. The easiest way
// to get the SavedFrame is to use the Debugger API to track allocation sites
// and then do an allocation.
function getSavedFrameInstanceFromSandbox(sandbox) {
const dbg = new Debugger(sandbox);
dbg.memory.trackingAllocationSites = true;
Cu.evalInSandbox("new Object", sandbox);
const allocs = dbg.memory.drainAllocationsLog();
dbg.memory.trackingAllocationSites = false;
ok(allocs[0], "We should observe the allocation");
const { frame } = allocs[0];
if (sandbox !== high) {
ok(Cu.isXrayWrapper(frame), "`frame` should be an xray...");
equal(Object.prototype.toString.call(Cu.waiveXrays(frame)),
"[object SavedFrame]",
"...and that xray should wrap a SavedFrame");
}
return frame;
}

View File

@ -108,3 +108,4 @@ head = head_watchdog.js
head = head_watchdog.js head = head_watchdog.js
[test_writeToGlobalPrototype.js] [test_writeToGlobalPrototype.js]
[test_xrayed_iterator.js] [test_xrayed_iterator.js]
[test_xray_SavedFrame.js]

View File

@ -83,6 +83,7 @@ IsJSXraySupported(JSProtoKey key)
case JSProto_Array: case JSProto_Array:
case JSProto_Function: case JSProto_Function:
case JSProto_TypedArray: case JSProto_TypedArray:
case JSProto_SavedFrame:
return true; return true;
default: default:
return false; return false;

View File

@ -26,6 +26,10 @@ interface nsIStackFrame : nsISupports
readonly attribute AUTF8String sourceLine; readonly attribute AUTF8String sourceLine;
readonly attribute nsIStackFrame caller; readonly attribute nsIStackFrame caller;
// Returns the first frame whose principals are subsumed by the caller's
// principals.
[noscript, implicit_jscontext] readonly attribute nsIStackFrame sanitized;
// Returns a formatted stack string that looks like the sort of // Returns a formatted stack string that looks like the sort of
// string that would be returned by .stack on JS Error objects. // string that would be returned by .stack on JS Error objects.
// Only works on JS-language stack frames. // Only works on JS-language stack frames.