mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1117242 - SavedFrame objects should do principal checks for every accessor; r=jimb,jandem,bz
This commit is contained in:
parent
c9c82c55da
commit
770748abfb
@ -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);
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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())); }');
|
||||||
|
@ -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()); }');
|
||||||
|
23
js/src/jit-test/tests/saved-stacks/principals-03.js
Normal file
23
js/src/jit-test/tests/saved-stacks/principals-03.js
Normal 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);
|
15
js/src/jit-test/tests/saved-stacks/principals-04.js
Normal file
15
js/src/jit-test/tests/saved-stacks/principals-04.js
Normal 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(), "");
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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();
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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 ,
|
||||||
|
108
js/xpconnect/tests/unit/test_xray_SavedFrame.js
Normal file
108
js/xpconnect/tests/unit/test_xray_SavedFrame.js
Normal 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;
|
||||||
|
}
|
||||||
|
|
@ -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]
|
@ -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;
|
||||||
|
@ -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.
|
||||||
|
Loading…
Reference in New Issue
Block a user