mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-26 03:35:33 +00:00
Bug 1564164: Move DebuggerEnvironment into its own file, js/src/dbg/Environment.cpp. r=jorendorff
This patch should be pure code motion and declaration visibility changes. There should be no change in behavior. Differential Revision: https://phabricator.services.mozilla.com/D37363 --HG-- rename : js/src/dbg/Debugger-inl.h => js/src/dbg/Environment-inl.h rename : js/src/dbg/Debugger.cpp => js/src/dbg/Environment.cpp rename : js/src/dbg/Debugger.h => js/src/dbg/Environment.h extra : moz-landing-system : lando
This commit is contained in:
parent
1e1784264b
commit
b8467981fc
@ -112,9 +112,4 @@
|
||||
}
|
||||
}
|
||||
|
||||
inline js::Debugger* js::DebuggerEnvironment::owner() const {
|
||||
JSObject* dbgobj = &getReservedSlot(OWNER_SLOT).toObject();
|
||||
return Debugger::fromJSObject(dbgobj);
|
||||
}
|
||||
|
||||
#endif /* dbg_Debugger_inl_h */
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
#include "builtin/Promise.h"
|
||||
#include "dbg/DebuggerMemory.h"
|
||||
#include "dbg/Environment.h"
|
||||
#include "dbg/Frame.h"
|
||||
#include "dbg/NoExecute.h"
|
||||
#include "dbg/Object.h"
|
||||
@ -83,28 +84,9 @@ using mozilla::TimeStamp;
|
||||
|
||||
/*** Forward declarations, ClassOps and Classes *****************************/
|
||||
|
||||
static void DebuggerEnv_trace(JSTracer* trc, JSObject* obj);
|
||||
static void DebuggerScript_trace(JSTracer* trc, JSObject* obj);
|
||||
static void DebuggerSource_trace(JSTracer* trc, JSObject* obj);
|
||||
|
||||
const ClassOps DebuggerEnvironment::classOps_ = {nullptr, /* addProperty */
|
||||
nullptr, /* delProperty */
|
||||
nullptr, /* enumerate */
|
||||
nullptr, /* newEnumerate */
|
||||
nullptr, /* resolve */
|
||||
nullptr, /* mayResolve */
|
||||
nullptr, /* finalize */
|
||||
nullptr, /* call */
|
||||
nullptr, /* hasInstance */
|
||||
nullptr, /* construct */
|
||||
DebuggerEnv_trace};
|
||||
|
||||
const Class DebuggerEnvironment::class_ = {
|
||||
"Environment",
|
||||
JSCLASS_HAS_PRIVATE |
|
||||
JSCLASS_HAS_RESERVED_SLOTS(DebuggerEnvironment::RESERVED_SLOTS),
|
||||
&classOps_};
|
||||
|
||||
enum { JSSLOT_DEBUGSCRIPT_OWNER, JSSLOT_DEBUGSCRIPT_COUNT };
|
||||
|
||||
static const ClassOps DebuggerScript_classOps = {nullptr, /* addProperty */
|
||||
@ -3314,7 +3296,7 @@ void Debugger::removeAllocationsTrackingForAllDebuggees() {
|
||||
void Debugger::traceCrossCompartmentEdges(JSTracer* trc) {
|
||||
generatorFrames.traceCrossCompartmentEdges<DebuggerFrame::trace>(trc);
|
||||
objects.traceCrossCompartmentEdges<DebuggerObject::trace>(trc);
|
||||
environments.traceCrossCompartmentEdges<DebuggerEnv_trace>(trc);
|
||||
environments.traceCrossCompartmentEdges<DebuggerEnvironment::trace>(trc);
|
||||
scripts.traceCrossCompartmentEdges<DebuggerScript_trace>(trc);
|
||||
lazyScripts.traceCrossCompartmentEdges<DebuggerScript_trace>(trc);
|
||||
sources.traceCrossCompartmentEdges<DebuggerSource_trace>(trc);
|
||||
@ -8843,629 +8825,6 @@ static const JSPropertySpec DebuggerSource_properties[] = {
|
||||
|
||||
static const JSFunctionSpec DebuggerSource_methods[] = {JS_FS_END};
|
||||
|
||||
/*** Debugger.Environment ***************************************************/
|
||||
|
||||
void DebuggerEnv_trace(JSTracer* trc, JSObject* obj) {
|
||||
// There is a barrier on private pointers, so the Unbarriered marking
|
||||
// is okay.
|
||||
if (Env* referent = (JSObject*)obj->as<NativeObject>().getPrivate()) {
|
||||
TraceManuallyBarrieredCrossCompartmentEdge(trc, obj, &referent,
|
||||
"Debugger.Environment referent");
|
||||
obj->as<NativeObject>().setPrivateUnbarriered(referent);
|
||||
}
|
||||
}
|
||||
|
||||
static DebuggerEnvironment* DebuggerEnvironment_checkThis(
|
||||
JSContext* cx, const CallArgs& args, const char* fnname,
|
||||
bool requireDebuggee) {
|
||||
JSObject* thisobj = RequireObject(cx, args.thisv());
|
||||
if (!thisobj) {
|
||||
return nullptr;
|
||||
}
|
||||
if (thisobj->getClass() != &DebuggerEnvironment::class_) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_INCOMPATIBLE_PROTO, "Debugger.Environment",
|
||||
fnname, thisobj->getClass()->name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Forbid Debugger.Environment.prototype, which is of class
|
||||
// DebuggerEnvironment::class_ but isn't a real working Debugger.Environment.
|
||||
// The prototype object is distinguished by having no referent.
|
||||
DebuggerEnvironment* nthisobj = &thisobj->as<DebuggerEnvironment>();
|
||||
if (!nthisobj->getPrivate()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_INCOMPATIBLE_PROTO, "Debugger.Environment",
|
||||
fnname, "prototype object");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Forbid access to Debugger.Environment objects that are not debuggee
|
||||
// environments.
|
||||
if (requireDebuggee) {
|
||||
Rooted<Env*> env(cx, static_cast<Env*>(nthisobj->getPrivate()));
|
||||
if (!Debugger::fromChildJSObject(nthisobj)->observesGlobal(
|
||||
&env->nonCCWGlobal())) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_DEBUG_NOT_DEBUGGEE,
|
||||
"Debugger.Environment", "environment");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return nthisobj;
|
||||
}
|
||||
|
||||
#define THIS_DEBUGGER_ENVIRONMENT(cx, argc, vp, fnname, args, environment) \
|
||||
CallArgs args = CallArgsFromVp(argc, vp); \
|
||||
Rooted<DebuggerEnvironment*> environment( \
|
||||
cx, DebuggerEnvironment_checkThis(cx, args, fnname, false)); \
|
||||
if (!environment) return false;
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::construct(JSContext* cx, unsigned argc, Value* vp) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
|
||||
"Debugger.Environment");
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool IsDeclarative(Env* env) {
|
||||
return env->is<DebugEnvironmentProxy>() &&
|
||||
env->as<DebugEnvironmentProxy>().isForDeclarative();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static bool IsDebugEnvironmentWrapper(Env* env) {
|
||||
return env->is<DebugEnvironmentProxy>() &&
|
||||
env->as<DebugEnvironmentProxy>().environment().is<T>();
|
||||
}
|
||||
|
||||
bool DebuggerEnvironment::typeGetter(JSContext* cx, unsigned argc, Value* vp) {
|
||||
THIS_DEBUGGER_ENVIRONMENT(cx, argc, vp, "get type", args, environment);
|
||||
|
||||
if (!environment->requireDebuggee(cx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DebuggerEnvironmentType type = environment->type();
|
||||
|
||||
const char* s;
|
||||
switch (type) {
|
||||
case DebuggerEnvironmentType::Declarative:
|
||||
s = "declarative";
|
||||
break;
|
||||
case DebuggerEnvironmentType::With:
|
||||
s = "with";
|
||||
break;
|
||||
case DebuggerEnvironmentType::Object:
|
||||
s = "object";
|
||||
break;
|
||||
}
|
||||
|
||||
JSAtom* str = Atomize(cx, s, strlen(s), PinAtom);
|
||||
if (!str) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setString(str);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DebuggerEnvironment::scopeKindGetter(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
THIS_DEBUGGER_ENVIRONMENT(cx, argc, vp, "get scopeKind", args, environment);
|
||||
|
||||
if (!environment->requireDebuggee(cx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Maybe<ScopeKind> kind = environment->scopeKind();
|
||||
if (kind.isSome()) {
|
||||
const char* s = ScopeKindString(*kind);
|
||||
JSAtom* str = Atomize(cx, s, strlen(s), PinAtom);
|
||||
if (!str) {
|
||||
return false;
|
||||
}
|
||||
args.rval().setString(str);
|
||||
} else {
|
||||
args.rval().setNull();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::parentGetter(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
THIS_DEBUGGER_ENVIRONMENT(cx, argc, vp, "get type", args, environment);
|
||||
|
||||
if (!environment->requireDebuggee(cx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedDebuggerEnvironment result(cx);
|
||||
if (!environment->getParent(cx, &result)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObjectOrNull(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::objectGetter(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
THIS_DEBUGGER_ENVIRONMENT(cx, argc, vp, "get type", args, environment);
|
||||
|
||||
if (!environment->requireDebuggee(cx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (environment->type() == DebuggerEnvironmentType::Declarative) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_DEBUG_NO_ENV_OBJECT);
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedDebuggerObject result(cx);
|
||||
if (!environment->getObject(cx, &result)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObject(*result);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::calleeGetter(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
THIS_DEBUGGER_ENVIRONMENT(cx, argc, vp, "get callee", args, environment);
|
||||
|
||||
if (!environment->requireDebuggee(cx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedDebuggerObject result(cx);
|
||||
if (!environment->getCallee(cx, &result)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObjectOrNull(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::inspectableGetter(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
THIS_DEBUGGER_ENVIRONMENT(cx, argc, vp, "get inspectable", args, environment);
|
||||
|
||||
args.rval().setBoolean(environment->isDebuggee());
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::optimizedOutGetter(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
THIS_DEBUGGER_ENVIRONMENT(cx, argc, vp, "get optimizedOut", args,
|
||||
environment);
|
||||
|
||||
args.rval().setBoolean(environment->isOptimized());
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::namesMethod(JSContext* cx, unsigned argc, Value* vp) {
|
||||
THIS_DEBUGGER_ENVIRONMENT(cx, argc, vp, "names", args, environment);
|
||||
|
||||
if (!environment->requireDebuggee(cx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<IdVector> ids(cx, IdVector(cx));
|
||||
if (!DebuggerEnvironment::getNames(cx, environment, &ids)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedObject obj(cx, IdVectorToArray(cx, ids));
|
||||
if (!obj) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObject(*obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::findMethod(JSContext* cx, unsigned argc, Value* vp) {
|
||||
THIS_DEBUGGER_ENVIRONMENT(cx, argc, vp, "find", args, environment);
|
||||
if (!args.requireAtLeast(cx, "Debugger.Environment.find", 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!environment->requireDebuggee(cx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedId id(cx);
|
||||
if (!ValueToIdentifier(cx, args[0], &id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedDebuggerEnvironment result(cx);
|
||||
if (!DebuggerEnvironment::find(cx, environment, id, &result)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObjectOrNull(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::getVariableMethod(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
THIS_DEBUGGER_ENVIRONMENT(cx, argc, vp, "getVariable", args, environment);
|
||||
if (!args.requireAtLeast(cx, "Debugger.Environment.getVariable", 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!environment->requireDebuggee(cx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedId id(cx);
|
||||
if (!ValueToIdentifier(cx, args[0], &id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return DebuggerEnvironment::getVariable(cx, environment, id, args.rval());
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::setVariableMethod(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
THIS_DEBUGGER_ENVIRONMENT(cx, argc, vp, "setVariable", args, environment);
|
||||
if (!args.requireAtLeast(cx, "Debugger.Environment.setVariable", 2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!environment->requireDebuggee(cx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedId id(cx);
|
||||
if (!ValueToIdentifier(cx, args[0], &id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!DebuggerEnvironment::setVariable(cx, environment, id, args[1])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DebuggerEnvironment::requireDebuggee(JSContext* cx) const {
|
||||
if (!isDebuggee()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_DEBUG_NOT_DEBUGGEE, "Debugger.Environment",
|
||||
"environment");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const JSPropertySpec DebuggerEnvironment::properties_[] = {
|
||||
JS_PSG("type", DebuggerEnvironment::typeGetter, 0),
|
||||
JS_PSG("scopeKind", DebuggerEnvironment::scopeKindGetter, 0),
|
||||
JS_PSG("parent", DebuggerEnvironment::parentGetter, 0),
|
||||
JS_PSG("object", DebuggerEnvironment::objectGetter, 0),
|
||||
JS_PSG("callee", DebuggerEnvironment::calleeGetter, 0),
|
||||
JS_PSG("inspectable", DebuggerEnvironment::inspectableGetter, 0),
|
||||
JS_PSG("optimizedOut", DebuggerEnvironment::optimizedOutGetter, 0),
|
||||
JS_PS_END};
|
||||
|
||||
const JSFunctionSpec DebuggerEnvironment::methods_[] = {
|
||||
JS_FN("names", DebuggerEnvironment::namesMethod, 0, 0),
|
||||
JS_FN("find", DebuggerEnvironment::findMethod, 1, 0),
|
||||
JS_FN("getVariable", DebuggerEnvironment::getVariableMethod, 1, 0),
|
||||
JS_FN("setVariable", DebuggerEnvironment::setVariableMethod, 2, 0),
|
||||
JS_FS_END};
|
||||
|
||||
/* static */
|
||||
NativeObject* DebuggerEnvironment::initClass(JSContext* cx,
|
||||
HandleObject dbgCtor,
|
||||
Handle<GlobalObject*> global) {
|
||||
return InitClass(cx, dbgCtor, nullptr, &DebuggerEnvironment::class_,
|
||||
construct, 0, properties_, methods_, nullptr, nullptr);
|
||||
}
|
||||
|
||||
/* static */
|
||||
DebuggerEnvironment* DebuggerEnvironment::create(JSContext* cx,
|
||||
HandleObject proto,
|
||||
HandleObject referent,
|
||||
HandleNativeObject debugger) {
|
||||
NewObjectKind newKind =
|
||||
IsInsideNursery(referent) ? GenericObject : TenuredObject;
|
||||
DebuggerEnvironment* obj =
|
||||
NewObjectWithGivenProto<DebuggerEnvironment>(cx, proto, newKind);
|
||||
if (!obj) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
obj->setPrivateGCThing(referent);
|
||||
obj->setReservedSlot(OWNER_SLOT, ObjectValue(*debugger));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/* static */
|
||||
DebuggerEnvironmentType DebuggerEnvironment::type() const {
|
||||
// Don't bother switching compartments just to check env's type.
|
||||
if (IsDeclarative(referent())) {
|
||||
return DebuggerEnvironmentType::Declarative;
|
||||
}
|
||||
if (IsDebugEnvironmentWrapper<WithEnvironmentObject>(referent())) {
|
||||
return DebuggerEnvironmentType::With;
|
||||
}
|
||||
return DebuggerEnvironmentType::Object;
|
||||
}
|
||||
|
||||
mozilla::Maybe<ScopeKind> DebuggerEnvironment::scopeKind() const {
|
||||
if (!referent()->is<DebugEnvironmentProxy>()) {
|
||||
return Nothing();
|
||||
}
|
||||
EnvironmentObject& env =
|
||||
referent()->as<DebugEnvironmentProxy>().environment();
|
||||
Scope* scope = GetEnvironmentScope(env);
|
||||
return scope ? Some(scope->kind()) : Nothing();
|
||||
}
|
||||
|
||||
bool DebuggerEnvironment::getParent(
|
||||
JSContext* cx, MutableHandleDebuggerEnvironment result) const {
|
||||
// Don't bother switching compartments just to get env's parent.
|
||||
Rooted<Env*> parent(cx, referent()->enclosingEnvironment());
|
||||
if (!parent) {
|
||||
result.set(nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
return owner()->wrapEnvironment(cx, parent, result);
|
||||
}
|
||||
|
||||
bool DebuggerEnvironment::getObject(JSContext* cx,
|
||||
MutableHandleDebuggerObject result) const {
|
||||
MOZ_ASSERT(type() != DebuggerEnvironmentType::Declarative);
|
||||
|
||||
// Don't bother switching compartments just to get env's object.
|
||||
RootedObject object(cx);
|
||||
if (IsDebugEnvironmentWrapper<WithEnvironmentObject>(referent())) {
|
||||
object.set(&referent()
|
||||
->as<DebugEnvironmentProxy>()
|
||||
.environment()
|
||||
.as<WithEnvironmentObject>()
|
||||
.object());
|
||||
} else if (IsDebugEnvironmentWrapper<NonSyntacticVariablesObject>(
|
||||
referent())) {
|
||||
object.set(&referent()
|
||||
->as<DebugEnvironmentProxy>()
|
||||
.environment()
|
||||
.as<NonSyntacticVariablesObject>());
|
||||
} else {
|
||||
object.set(referent());
|
||||
MOZ_ASSERT(!object->is<DebugEnvironmentProxy>());
|
||||
}
|
||||
|
||||
return owner()->wrapDebuggeeObject(cx, object, result);
|
||||
}
|
||||
|
||||
bool DebuggerEnvironment::getCallee(JSContext* cx,
|
||||
MutableHandleDebuggerObject result) const {
|
||||
if (!referent()->is<DebugEnvironmentProxy>()) {
|
||||
result.set(nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
JSObject& scope = referent()->as<DebugEnvironmentProxy>().environment();
|
||||
if (!scope.is<CallObject>()) {
|
||||
result.set(nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
RootedObject callee(cx, &scope.as<CallObject>().callee());
|
||||
if (IsInternalFunctionObject(*callee)) {
|
||||
result.set(nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
return owner()->wrapDebuggeeObject(cx, callee, result);
|
||||
}
|
||||
|
||||
bool DebuggerEnvironment::isDebuggee() const {
|
||||
MOZ_ASSERT(referent());
|
||||
MOZ_ASSERT(!referent()->is<EnvironmentObject>());
|
||||
|
||||
return owner()->observesGlobal(&referent()->nonCCWGlobal());
|
||||
}
|
||||
|
||||
bool DebuggerEnvironment::isOptimized() const {
|
||||
return referent()->is<DebugEnvironmentProxy>() &&
|
||||
referent()->as<DebugEnvironmentProxy>().isOptimizedOut();
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::getNames(JSContext* cx,
|
||||
HandleDebuggerEnvironment environment,
|
||||
MutableHandle<IdVector> result) {
|
||||
MOZ_ASSERT(environment->isDebuggee());
|
||||
|
||||
Rooted<Env*> referent(cx, environment->referent());
|
||||
|
||||
RootedIdVector ids(cx);
|
||||
{
|
||||
Maybe<AutoRealm> ar;
|
||||
ar.emplace(cx, referent);
|
||||
|
||||
ErrorCopier ec(ar);
|
||||
if (!GetPropertyKeys(cx, referent, JSITER_HIDDEN, &ids)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < ids.length(); ++i) {
|
||||
jsid id = ids[i];
|
||||
if (JSID_IS_ATOM(id) && IsIdentifier(JSID_TO_ATOM(id))) {
|
||||
cx->markId(id);
|
||||
if (!result.append(id)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::find(JSContext* cx,
|
||||
HandleDebuggerEnvironment environment,
|
||||
HandleId id,
|
||||
MutableHandleDebuggerEnvironment result) {
|
||||
MOZ_ASSERT(environment->isDebuggee());
|
||||
|
||||
Rooted<Env*> env(cx, environment->referent());
|
||||
Debugger* dbg = environment->owner();
|
||||
|
||||
{
|
||||
Maybe<AutoRealm> ar;
|
||||
ar.emplace(cx, env);
|
||||
|
||||
cx->markId(id);
|
||||
|
||||
// This can trigger resolve hooks.
|
||||
ErrorCopier ec(ar);
|
||||
for (; env; env = env->enclosingEnvironment()) {
|
||||
bool found;
|
||||
if (!HasProperty(cx, env, id, &found)) {
|
||||
return false;
|
||||
}
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!env) {
|
||||
result.set(nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
return dbg->wrapEnvironment(cx, env, result);
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::getVariable(JSContext* cx,
|
||||
HandleDebuggerEnvironment environment,
|
||||
HandleId id, MutableHandleValue result) {
|
||||
MOZ_ASSERT(environment->isDebuggee());
|
||||
|
||||
Rooted<Env*> referent(cx, environment->referent());
|
||||
Debugger* dbg = environment->owner();
|
||||
|
||||
{
|
||||
Maybe<AutoRealm> ar;
|
||||
ar.emplace(cx, referent);
|
||||
|
||||
cx->markId(id);
|
||||
|
||||
// This can trigger getters.
|
||||
ErrorCopier ec(ar);
|
||||
|
||||
bool found;
|
||||
if (!HasProperty(cx, referent, id, &found)) {
|
||||
return false;
|
||||
}
|
||||
if (!found) {
|
||||
result.setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
// For DebugEnvironmentProxys, we get sentinel values for optimized out
|
||||
// slots and arguments instead of throwing (the default behavior).
|
||||
//
|
||||
// See wrapDebuggeeValue for how the sentinel values are wrapped.
|
||||
if (referent->is<DebugEnvironmentProxy>()) {
|
||||
Rooted<DebugEnvironmentProxy*> env(
|
||||
cx, &referent->as<DebugEnvironmentProxy>());
|
||||
if (!DebugEnvironmentProxy::getMaybeSentinelValue(cx, env, id, result)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!GetProperty(cx, referent, referent, id, result)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When we've faked up scope chain objects for optimized-out scopes,
|
||||
// declarative environments may contain internal JSFunction objects, which
|
||||
// we shouldn't expose to the user.
|
||||
if (result.isObject()) {
|
||||
RootedObject obj(cx, &result.toObject());
|
||||
if (obj->is<JSFunction>() &&
|
||||
IsInternalFunctionObject(obj->as<JSFunction>()))
|
||||
result.setMagic(JS_OPTIMIZED_OUT);
|
||||
}
|
||||
|
||||
return dbg->wrapDebuggeeValue(cx, result);
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::setVariable(JSContext* cx,
|
||||
HandleDebuggerEnvironment environment,
|
||||
HandleId id, HandleValue value_) {
|
||||
MOZ_ASSERT(environment->isDebuggee());
|
||||
|
||||
Rooted<Env*> referent(cx, environment->referent());
|
||||
Debugger* dbg = environment->owner();
|
||||
|
||||
RootedValue value(cx, value_);
|
||||
if (!dbg->unwrapDebuggeeValue(cx, &value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
Maybe<AutoRealm> ar;
|
||||
ar.emplace(cx, referent);
|
||||
if (!cx->compartment()->wrap(cx, &value)) {
|
||||
return false;
|
||||
}
|
||||
cx->markId(id);
|
||||
|
||||
// This can trigger setters.
|
||||
ErrorCopier ec(ar);
|
||||
|
||||
// Make sure the environment actually has the specified binding.
|
||||
bool found;
|
||||
if (!HasProperty(cx, referent, id, &found)) {
|
||||
return false;
|
||||
}
|
||||
if (!found) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_DEBUG_VARIABLE_NOT_FOUND);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Just set the property.
|
||||
if (!SetProperty(cx, referent, id, value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*** JS::dbg::Builder *******************************************************/
|
||||
|
||||
Builder::Builder(JSContext* cx, js::Debugger* debugger)
|
||||
|
@ -1353,87 +1353,6 @@ class Debugger : private mozilla::LinkedListElement<Debugger> {
|
||||
Debugger& operator=(const Debugger&) = delete;
|
||||
};
|
||||
|
||||
enum class DebuggerEnvironmentType { Declarative, With, Object };
|
||||
|
||||
class DebuggerEnvironment : public NativeObject {
|
||||
public:
|
||||
enum { OWNER_SLOT };
|
||||
|
||||
static const unsigned RESERVED_SLOTS = 1;
|
||||
|
||||
static const Class class_;
|
||||
|
||||
static NativeObject* initClass(JSContext* cx, HandleObject dbgCtor,
|
||||
Handle<GlobalObject*> global);
|
||||
static DebuggerEnvironment* create(JSContext* cx, HandleObject proto,
|
||||
HandleObject referent,
|
||||
HandleNativeObject debugger);
|
||||
|
||||
DebuggerEnvironmentType type() const;
|
||||
mozilla::Maybe<ScopeKind> scopeKind() const;
|
||||
MOZ_MUST_USE bool getParent(JSContext* cx,
|
||||
MutableHandleDebuggerEnvironment result) const;
|
||||
MOZ_MUST_USE bool getObject(JSContext* cx,
|
||||
MutableHandleDebuggerObject result) const;
|
||||
MOZ_MUST_USE bool getCallee(JSContext* cx,
|
||||
MutableHandleDebuggerObject result) const;
|
||||
bool isDebuggee() const;
|
||||
bool isOptimized() const;
|
||||
|
||||
static MOZ_MUST_USE bool getNames(JSContext* cx,
|
||||
HandleDebuggerEnvironment environment,
|
||||
MutableHandle<IdVector> result);
|
||||
static MOZ_MUST_USE bool find(JSContext* cx,
|
||||
HandleDebuggerEnvironment environment,
|
||||
HandleId id,
|
||||
MutableHandleDebuggerEnvironment result);
|
||||
static MOZ_MUST_USE bool getVariable(JSContext* cx,
|
||||
HandleDebuggerEnvironment environment,
|
||||
HandleId id, MutableHandleValue result);
|
||||
static MOZ_MUST_USE bool setVariable(JSContext* cx,
|
||||
HandleDebuggerEnvironment environment,
|
||||
HandleId id, HandleValue value);
|
||||
|
||||
private:
|
||||
static const ClassOps classOps_;
|
||||
|
||||
static const JSPropertySpec properties_[];
|
||||
static const JSFunctionSpec methods_[];
|
||||
|
||||
Env* referent() const {
|
||||
Env* env = static_cast<Env*>(getPrivate());
|
||||
MOZ_ASSERT(env);
|
||||
return env;
|
||||
}
|
||||
|
||||
Debugger* owner() const;
|
||||
|
||||
bool requireDebuggee(JSContext* cx) const;
|
||||
|
||||
static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
static MOZ_MUST_USE bool typeGetter(JSContext* cx, unsigned argc, Value* vp);
|
||||
static MOZ_MUST_USE bool scopeKindGetter(JSContext* cx, unsigned argc,
|
||||
Value* vp);
|
||||
static MOZ_MUST_USE bool parentGetter(JSContext* cx, unsigned argc,
|
||||
Value* vp);
|
||||
static MOZ_MUST_USE bool objectGetter(JSContext* cx, unsigned argc,
|
||||
Value* vp);
|
||||
static MOZ_MUST_USE bool calleeGetter(JSContext* cx, unsigned argc,
|
||||
Value* vp);
|
||||
static MOZ_MUST_USE bool inspectableGetter(JSContext* cx, unsigned argc,
|
||||
Value* vp);
|
||||
static MOZ_MUST_USE bool optimizedOutGetter(JSContext* cx, unsigned argc,
|
||||
Value* vp);
|
||||
|
||||
static MOZ_MUST_USE bool namesMethod(JSContext* cx, unsigned argc, Value* vp);
|
||||
static MOZ_MUST_USE bool findMethod(JSContext* cx, unsigned argc, Value* vp);
|
||||
static MOZ_MUST_USE bool getVariableMethod(JSContext* cx, unsigned argc,
|
||||
Value* vp);
|
||||
static MOZ_MUST_USE bool setVariableMethod(JSContext* cx, unsigned argc,
|
||||
Value* vp);
|
||||
};
|
||||
|
||||
/*
|
||||
* A Handler represents a reference to a handler function. These handler
|
||||
* functions are called by the Debugger API to notify the user of certain
|
||||
|
19
js/src/dbg/Environment-inl.h
Normal file
19
js/src/dbg/Environment-inl.h
Normal file
@ -0,0 +1,19 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef dbg_Environment_inl_h
|
||||
#define dbg_Environment_inl_h
|
||||
|
||||
#include "dbg/Environment.h"
|
||||
|
||||
#include "dbg/Debugger-inl.h"
|
||||
|
||||
inline js::Debugger* js::DebuggerEnvironment::owner() const {
|
||||
JSObject* dbgobj = &getReservedSlot(OWNER_SLOT).toObject();
|
||||
return Debugger::fromJSObject(dbgobj);
|
||||
}
|
||||
|
||||
#endif /* dbg_Environment_inl_h */
|
662
js/src/dbg/Environment.cpp
Normal file
662
js/src/dbg/Environment.cpp
Normal file
@ -0,0 +1,662 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "dbg/Environment-inl.h"
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
|
||||
#include "dbg/Debugger.h"
|
||||
#include "dbg/Object.h"
|
||||
#include "frontend/BytecodeCompiler.h"
|
||||
#include "vm/Realm.h"
|
||||
|
||||
#include "vm/Compartment-inl.h"
|
||||
|
||||
using namespace js;
|
||||
|
||||
using js::frontend::IsIdentifier;
|
||||
using mozilla::Maybe;
|
||||
using mozilla::Nothing;
|
||||
using mozilla::Some;
|
||||
|
||||
const ClassOps DebuggerEnvironment::classOps_ = {nullptr, /* addProperty */
|
||||
nullptr, /* delProperty */
|
||||
nullptr, /* enumerate */
|
||||
nullptr, /* newEnumerate */
|
||||
nullptr, /* resolve */
|
||||
nullptr, /* mayResolve */
|
||||
nullptr, /* finalize */
|
||||
nullptr, /* call */
|
||||
nullptr, /* hasInstance */
|
||||
nullptr, /* construct */
|
||||
trace};
|
||||
|
||||
const Class DebuggerEnvironment::class_ = {
|
||||
"Environment",
|
||||
JSCLASS_HAS_PRIVATE |
|
||||
JSCLASS_HAS_RESERVED_SLOTS(DebuggerEnvironment::RESERVED_SLOTS),
|
||||
&classOps_};
|
||||
|
||||
void DebuggerEnvironment::trace(JSTracer* trc, JSObject* obj) {
|
||||
// There is a barrier on private pointers, so the Unbarriered marking
|
||||
// is okay.
|
||||
if (Env* referent = (JSObject*)obj->as<NativeObject>().getPrivate()) {
|
||||
TraceManuallyBarrieredCrossCompartmentEdge(trc, obj, &referent,
|
||||
"Debugger.Environment referent");
|
||||
obj->as<NativeObject>().setPrivateUnbarriered(referent);
|
||||
}
|
||||
}
|
||||
|
||||
static DebuggerEnvironment* DebuggerEnvironment_checkThis(
|
||||
JSContext* cx, const CallArgs& args, const char* fnname,
|
||||
bool requireDebuggee) {
|
||||
JSObject* thisobj = RequireObject(cx, args.thisv());
|
||||
if (!thisobj) {
|
||||
return nullptr;
|
||||
}
|
||||
if (thisobj->getClass() != &DebuggerEnvironment::class_) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_INCOMPATIBLE_PROTO, "Debugger.Environment",
|
||||
fnname, thisobj->getClass()->name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Forbid Debugger.Environment.prototype, which is of class
|
||||
// DebuggerEnvironment::class_ but isn't a real working Debugger.Environment.
|
||||
// The prototype object is distinguished by having no referent.
|
||||
DebuggerEnvironment* nthisobj = &thisobj->as<DebuggerEnvironment>();
|
||||
if (!nthisobj->getPrivate()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_INCOMPATIBLE_PROTO, "Debugger.Environment",
|
||||
fnname, "prototype object");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Forbid access to Debugger.Environment objects that are not debuggee
|
||||
// environments.
|
||||
if (requireDebuggee) {
|
||||
Rooted<Env*> env(cx, static_cast<Env*>(nthisobj->getPrivate()));
|
||||
if (!Debugger::fromChildJSObject(nthisobj)->observesGlobal(
|
||||
&env->nonCCWGlobal())) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_DEBUG_NOT_DEBUGGEE,
|
||||
"Debugger.Environment", "environment");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return nthisobj;
|
||||
}
|
||||
|
||||
#define THIS_DEBUGGER_ENVIRONMENT(cx, argc, vp, fnname, args, environment) \
|
||||
CallArgs args = CallArgsFromVp(argc, vp); \
|
||||
Rooted<DebuggerEnvironment*> environment( \
|
||||
cx, DebuggerEnvironment_checkThis(cx, args, fnname, false)); \
|
||||
if (!environment) return false;
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::construct(JSContext* cx, unsigned argc, Value* vp) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
|
||||
"Debugger.Environment");
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool IsDeclarative(Env* env) {
|
||||
return env->is<DebugEnvironmentProxy>() &&
|
||||
env->as<DebugEnvironmentProxy>().isForDeclarative();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static bool IsDebugEnvironmentWrapper(Env* env) {
|
||||
return env->is<DebugEnvironmentProxy>() &&
|
||||
env->as<DebugEnvironmentProxy>().environment().is<T>();
|
||||
}
|
||||
|
||||
bool DebuggerEnvironment::typeGetter(JSContext* cx, unsigned argc, Value* vp) {
|
||||
THIS_DEBUGGER_ENVIRONMENT(cx, argc, vp, "get type", args, environment);
|
||||
|
||||
if (!environment->requireDebuggee(cx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DebuggerEnvironmentType type = environment->type();
|
||||
|
||||
const char* s;
|
||||
switch (type) {
|
||||
case DebuggerEnvironmentType::Declarative:
|
||||
s = "declarative";
|
||||
break;
|
||||
case DebuggerEnvironmentType::With:
|
||||
s = "with";
|
||||
break;
|
||||
case DebuggerEnvironmentType::Object:
|
||||
s = "object";
|
||||
break;
|
||||
}
|
||||
|
||||
JSAtom* str = Atomize(cx, s, strlen(s), PinAtom);
|
||||
if (!str) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setString(str);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DebuggerEnvironment::scopeKindGetter(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
THIS_DEBUGGER_ENVIRONMENT(cx, argc, vp, "get scopeKind", args, environment);
|
||||
|
||||
if (!environment->requireDebuggee(cx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Maybe<ScopeKind> kind = environment->scopeKind();
|
||||
if (kind.isSome()) {
|
||||
const char* s = ScopeKindString(*kind);
|
||||
JSAtom* str = Atomize(cx, s, strlen(s), PinAtom);
|
||||
if (!str) {
|
||||
return false;
|
||||
}
|
||||
args.rval().setString(str);
|
||||
} else {
|
||||
args.rval().setNull();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::parentGetter(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
THIS_DEBUGGER_ENVIRONMENT(cx, argc, vp, "get type", args, environment);
|
||||
|
||||
if (!environment->requireDebuggee(cx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedDebuggerEnvironment result(cx);
|
||||
if (!environment->getParent(cx, &result)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObjectOrNull(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::objectGetter(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
THIS_DEBUGGER_ENVIRONMENT(cx, argc, vp, "get type", args, environment);
|
||||
|
||||
if (!environment->requireDebuggee(cx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (environment->type() == DebuggerEnvironmentType::Declarative) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_DEBUG_NO_ENV_OBJECT);
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedDebuggerObject result(cx);
|
||||
if (!environment->getObject(cx, &result)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObject(*result);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::calleeGetter(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
THIS_DEBUGGER_ENVIRONMENT(cx, argc, vp, "get callee", args, environment);
|
||||
|
||||
if (!environment->requireDebuggee(cx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedDebuggerObject result(cx);
|
||||
if (!environment->getCallee(cx, &result)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObjectOrNull(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::inspectableGetter(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
THIS_DEBUGGER_ENVIRONMENT(cx, argc, vp, "get inspectable", args, environment);
|
||||
|
||||
args.rval().setBoolean(environment->isDebuggee());
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::optimizedOutGetter(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
THIS_DEBUGGER_ENVIRONMENT(cx, argc, vp, "get optimizedOut", args,
|
||||
environment);
|
||||
|
||||
args.rval().setBoolean(environment->isOptimized());
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::namesMethod(JSContext* cx, unsigned argc, Value* vp) {
|
||||
THIS_DEBUGGER_ENVIRONMENT(cx, argc, vp, "names", args, environment);
|
||||
|
||||
if (!environment->requireDebuggee(cx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<IdVector> ids(cx, IdVector(cx));
|
||||
if (!DebuggerEnvironment::getNames(cx, environment, &ids)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedObject obj(cx, IdVectorToArray(cx, ids));
|
||||
if (!obj) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObject(*obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::findMethod(JSContext* cx, unsigned argc, Value* vp) {
|
||||
THIS_DEBUGGER_ENVIRONMENT(cx, argc, vp, "find", args, environment);
|
||||
if (!args.requireAtLeast(cx, "Debugger.Environment.find", 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!environment->requireDebuggee(cx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedId id(cx);
|
||||
if (!ValueToIdentifier(cx, args[0], &id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedDebuggerEnvironment result(cx);
|
||||
if (!DebuggerEnvironment::find(cx, environment, id, &result)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObjectOrNull(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::getVariableMethod(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
THIS_DEBUGGER_ENVIRONMENT(cx, argc, vp, "getVariable", args, environment);
|
||||
if (!args.requireAtLeast(cx, "Debugger.Environment.getVariable", 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!environment->requireDebuggee(cx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedId id(cx);
|
||||
if (!ValueToIdentifier(cx, args[0], &id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return DebuggerEnvironment::getVariable(cx, environment, id, args.rval());
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::setVariableMethod(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
THIS_DEBUGGER_ENVIRONMENT(cx, argc, vp, "setVariable", args, environment);
|
||||
if (!args.requireAtLeast(cx, "Debugger.Environment.setVariable", 2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!environment->requireDebuggee(cx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedId id(cx);
|
||||
if (!ValueToIdentifier(cx, args[0], &id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!DebuggerEnvironment::setVariable(cx, environment, id, args[1])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DebuggerEnvironment::requireDebuggee(JSContext* cx) const {
|
||||
if (!isDebuggee()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_DEBUG_NOT_DEBUGGEE, "Debugger.Environment",
|
||||
"environment");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const JSPropertySpec DebuggerEnvironment::properties_[] = {
|
||||
JS_PSG("type", DebuggerEnvironment::typeGetter, 0),
|
||||
JS_PSG("scopeKind", DebuggerEnvironment::scopeKindGetter, 0),
|
||||
JS_PSG("parent", DebuggerEnvironment::parentGetter, 0),
|
||||
JS_PSG("object", DebuggerEnvironment::objectGetter, 0),
|
||||
JS_PSG("callee", DebuggerEnvironment::calleeGetter, 0),
|
||||
JS_PSG("inspectable", DebuggerEnvironment::inspectableGetter, 0),
|
||||
JS_PSG("optimizedOut", DebuggerEnvironment::optimizedOutGetter, 0),
|
||||
JS_PS_END};
|
||||
|
||||
const JSFunctionSpec DebuggerEnvironment::methods_[] = {
|
||||
JS_FN("names", DebuggerEnvironment::namesMethod, 0, 0),
|
||||
JS_FN("find", DebuggerEnvironment::findMethod, 1, 0),
|
||||
JS_FN("getVariable", DebuggerEnvironment::getVariableMethod, 1, 0),
|
||||
JS_FN("setVariable", DebuggerEnvironment::setVariableMethod, 2, 0),
|
||||
JS_FS_END};
|
||||
|
||||
/* static */
|
||||
NativeObject* DebuggerEnvironment::initClass(JSContext* cx,
|
||||
HandleObject dbgCtor,
|
||||
Handle<GlobalObject*> global) {
|
||||
return InitClass(cx, dbgCtor, nullptr, &DebuggerEnvironment::class_,
|
||||
construct, 0, properties_, methods_, nullptr, nullptr);
|
||||
}
|
||||
|
||||
/* static */
|
||||
DebuggerEnvironment* DebuggerEnvironment::create(JSContext* cx,
|
||||
HandleObject proto,
|
||||
HandleObject referent,
|
||||
HandleNativeObject debugger) {
|
||||
NewObjectKind newKind =
|
||||
IsInsideNursery(referent) ? GenericObject : TenuredObject;
|
||||
DebuggerEnvironment* obj =
|
||||
NewObjectWithGivenProto<DebuggerEnvironment>(cx, proto, newKind);
|
||||
if (!obj) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
obj->setPrivateGCThing(referent);
|
||||
obj->setReservedSlot(OWNER_SLOT, ObjectValue(*debugger));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/* static */
|
||||
DebuggerEnvironmentType DebuggerEnvironment::type() const {
|
||||
// Don't bother switching compartments just to check env's type.
|
||||
if (IsDeclarative(referent())) {
|
||||
return DebuggerEnvironmentType::Declarative;
|
||||
}
|
||||
if (IsDebugEnvironmentWrapper<WithEnvironmentObject>(referent())) {
|
||||
return DebuggerEnvironmentType::With;
|
||||
}
|
||||
return DebuggerEnvironmentType::Object;
|
||||
}
|
||||
|
||||
mozilla::Maybe<ScopeKind> DebuggerEnvironment::scopeKind() const {
|
||||
if (!referent()->is<DebugEnvironmentProxy>()) {
|
||||
return Nothing();
|
||||
}
|
||||
EnvironmentObject& env =
|
||||
referent()->as<DebugEnvironmentProxy>().environment();
|
||||
Scope* scope = GetEnvironmentScope(env);
|
||||
return scope ? Some(scope->kind()) : Nothing();
|
||||
}
|
||||
|
||||
bool DebuggerEnvironment::getParent(
|
||||
JSContext* cx, MutableHandleDebuggerEnvironment result) const {
|
||||
// Don't bother switching compartments just to get env's parent.
|
||||
Rooted<Env*> parent(cx, referent()->enclosingEnvironment());
|
||||
if (!parent) {
|
||||
result.set(nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
return owner()->wrapEnvironment(cx, parent, result);
|
||||
}
|
||||
|
||||
bool DebuggerEnvironment::getObject(JSContext* cx,
|
||||
MutableHandleDebuggerObject result) const {
|
||||
MOZ_ASSERT(type() != DebuggerEnvironmentType::Declarative);
|
||||
|
||||
// Don't bother switching compartments just to get env's object.
|
||||
RootedObject object(cx);
|
||||
if (IsDebugEnvironmentWrapper<WithEnvironmentObject>(referent())) {
|
||||
object.set(&referent()
|
||||
->as<DebugEnvironmentProxy>()
|
||||
.environment()
|
||||
.as<WithEnvironmentObject>()
|
||||
.object());
|
||||
} else if (IsDebugEnvironmentWrapper<NonSyntacticVariablesObject>(
|
||||
referent())) {
|
||||
object.set(&referent()
|
||||
->as<DebugEnvironmentProxy>()
|
||||
.environment()
|
||||
.as<NonSyntacticVariablesObject>());
|
||||
} else {
|
||||
object.set(referent());
|
||||
MOZ_ASSERT(!object->is<DebugEnvironmentProxy>());
|
||||
}
|
||||
|
||||
return owner()->wrapDebuggeeObject(cx, object, result);
|
||||
}
|
||||
|
||||
bool DebuggerEnvironment::getCallee(JSContext* cx,
|
||||
MutableHandleDebuggerObject result) const {
|
||||
if (!referent()->is<DebugEnvironmentProxy>()) {
|
||||
result.set(nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
JSObject& scope = referent()->as<DebugEnvironmentProxy>().environment();
|
||||
if (!scope.is<CallObject>()) {
|
||||
result.set(nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
RootedObject callee(cx, &scope.as<CallObject>().callee());
|
||||
if (IsInternalFunctionObject(*callee)) {
|
||||
result.set(nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
return owner()->wrapDebuggeeObject(cx, callee, result);
|
||||
}
|
||||
|
||||
bool DebuggerEnvironment::isDebuggee() const {
|
||||
MOZ_ASSERT(referent());
|
||||
MOZ_ASSERT(!referent()->is<EnvironmentObject>());
|
||||
|
||||
return owner()->observesGlobal(&referent()->nonCCWGlobal());
|
||||
}
|
||||
|
||||
bool DebuggerEnvironment::isOptimized() const {
|
||||
return referent()->is<DebugEnvironmentProxy>() &&
|
||||
referent()->as<DebugEnvironmentProxy>().isOptimizedOut();
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::getNames(JSContext* cx,
|
||||
HandleDebuggerEnvironment environment,
|
||||
MutableHandle<IdVector> result) {
|
||||
MOZ_ASSERT(environment->isDebuggee());
|
||||
|
||||
Rooted<Env*> referent(cx, environment->referent());
|
||||
|
||||
RootedIdVector ids(cx);
|
||||
{
|
||||
Maybe<AutoRealm> ar;
|
||||
ar.emplace(cx, referent);
|
||||
|
||||
ErrorCopier ec(ar);
|
||||
if (!GetPropertyKeys(cx, referent, JSITER_HIDDEN, &ids)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < ids.length(); ++i) {
|
||||
jsid id = ids[i];
|
||||
if (JSID_IS_ATOM(id) && IsIdentifier(JSID_TO_ATOM(id))) {
|
||||
cx->markId(id);
|
||||
if (!result.append(id)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::find(JSContext* cx,
|
||||
HandleDebuggerEnvironment environment,
|
||||
HandleId id,
|
||||
MutableHandleDebuggerEnvironment result) {
|
||||
MOZ_ASSERT(environment->isDebuggee());
|
||||
|
||||
Rooted<Env*> env(cx, environment->referent());
|
||||
Debugger* dbg = environment->owner();
|
||||
|
||||
{
|
||||
Maybe<AutoRealm> ar;
|
||||
ar.emplace(cx, env);
|
||||
|
||||
cx->markId(id);
|
||||
|
||||
// This can trigger resolve hooks.
|
||||
ErrorCopier ec(ar);
|
||||
for (; env; env = env->enclosingEnvironment()) {
|
||||
bool found;
|
||||
if (!HasProperty(cx, env, id, &found)) {
|
||||
return false;
|
||||
}
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!env) {
|
||||
result.set(nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
return dbg->wrapEnvironment(cx, env, result);
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::getVariable(JSContext* cx,
|
||||
HandleDebuggerEnvironment environment,
|
||||
HandleId id, MutableHandleValue result) {
|
||||
MOZ_ASSERT(environment->isDebuggee());
|
||||
|
||||
Rooted<Env*> referent(cx, environment->referent());
|
||||
Debugger* dbg = environment->owner();
|
||||
|
||||
{
|
||||
Maybe<AutoRealm> ar;
|
||||
ar.emplace(cx, referent);
|
||||
|
||||
cx->markId(id);
|
||||
|
||||
// This can trigger getters.
|
||||
ErrorCopier ec(ar);
|
||||
|
||||
bool found;
|
||||
if (!HasProperty(cx, referent, id, &found)) {
|
||||
return false;
|
||||
}
|
||||
if (!found) {
|
||||
result.setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
// For DebugEnvironmentProxys, we get sentinel values for optimized out
|
||||
// slots and arguments instead of throwing (the default behavior).
|
||||
//
|
||||
// See wrapDebuggeeValue for how the sentinel values are wrapped.
|
||||
if (referent->is<DebugEnvironmentProxy>()) {
|
||||
Rooted<DebugEnvironmentProxy*> env(
|
||||
cx, &referent->as<DebugEnvironmentProxy>());
|
||||
if (!DebugEnvironmentProxy::getMaybeSentinelValue(cx, env, id, result)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!GetProperty(cx, referent, referent, id, result)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When we've faked up scope chain objects for optimized-out scopes,
|
||||
// declarative environments may contain internal JSFunction objects, which
|
||||
// we shouldn't expose to the user.
|
||||
if (result.isObject()) {
|
||||
RootedObject obj(cx, &result.toObject());
|
||||
if (obj->is<JSFunction>() &&
|
||||
IsInternalFunctionObject(obj->as<JSFunction>()))
|
||||
result.setMagic(JS_OPTIMIZED_OUT);
|
||||
}
|
||||
|
||||
return dbg->wrapDebuggeeValue(cx, result);
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerEnvironment::setVariable(JSContext* cx,
|
||||
HandleDebuggerEnvironment environment,
|
||||
HandleId id, HandleValue value_) {
|
||||
MOZ_ASSERT(environment->isDebuggee());
|
||||
|
||||
Rooted<Env*> referent(cx, environment->referent());
|
||||
Debugger* dbg = environment->owner();
|
||||
|
||||
RootedValue value(cx, value_);
|
||||
if (!dbg->unwrapDebuggeeValue(cx, &value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
Maybe<AutoRealm> ar;
|
||||
ar.emplace(cx, referent);
|
||||
if (!cx->compartment()->wrap(cx, &value)) {
|
||||
return false;
|
||||
}
|
||||
cx->markId(id);
|
||||
|
||||
// This can trigger setters.
|
||||
ErrorCopier ec(ar);
|
||||
|
||||
// Make sure the environment actually has the specified binding.
|
||||
bool found;
|
||||
if (!HasProperty(cx, referent, id, &found)) {
|
||||
return false;
|
||||
}
|
||||
if (!found) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_DEBUG_VARIABLE_NOT_FOUND);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Just set the property.
|
||||
if (!SetProperty(cx, referent, id, value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
106
js/src/dbg/Environment.h
Normal file
106
js/src/dbg/Environment.h
Normal file
@ -0,0 +1,106 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef dbg_Environment_h
|
||||
#define dbg_Environment_h
|
||||
|
||||
#include "dbg/Debugger.h"
|
||||
#include "gc/Rooting.h"
|
||||
#include "js/Class.h"
|
||||
#include "js/PropertySpec.h"
|
||||
#include "js/RootingAPI.h"
|
||||
#include "js/TypeDecls.h"
|
||||
#include "vm/GlobalObject.h"
|
||||
#include "vm/NativeObject.h"
|
||||
|
||||
namespace js {
|
||||
|
||||
enum class DebuggerEnvironmentType { Declarative, With, Object };
|
||||
|
||||
class DebuggerEnvironment : public NativeObject {
|
||||
public:
|
||||
enum { OWNER_SLOT };
|
||||
|
||||
static const unsigned RESERVED_SLOTS = 1;
|
||||
|
||||
static const Class class_;
|
||||
|
||||
static NativeObject* initClass(JSContext* cx, HandleObject dbgCtor,
|
||||
Handle<GlobalObject*> global);
|
||||
static DebuggerEnvironment* create(JSContext* cx, HandleObject proto,
|
||||
HandleObject referent,
|
||||
HandleNativeObject debugger);
|
||||
|
||||
static void trace(JSTracer* trc, JSObject* obj);
|
||||
|
||||
DebuggerEnvironmentType type() const;
|
||||
mozilla::Maybe<ScopeKind> scopeKind() const;
|
||||
MOZ_MUST_USE bool getParent(JSContext* cx,
|
||||
MutableHandleDebuggerEnvironment result) const;
|
||||
MOZ_MUST_USE bool getObject(JSContext* cx,
|
||||
MutableHandleDebuggerObject result) const;
|
||||
MOZ_MUST_USE bool getCallee(JSContext* cx,
|
||||
MutableHandleDebuggerObject result) const;
|
||||
bool isDebuggee() const;
|
||||
bool isOptimized() const;
|
||||
|
||||
static MOZ_MUST_USE bool getNames(JSContext* cx,
|
||||
HandleDebuggerEnvironment environment,
|
||||
MutableHandle<IdVector> result);
|
||||
static MOZ_MUST_USE bool find(JSContext* cx,
|
||||
HandleDebuggerEnvironment environment,
|
||||
HandleId id,
|
||||
MutableHandleDebuggerEnvironment result);
|
||||
static MOZ_MUST_USE bool getVariable(JSContext* cx,
|
||||
HandleDebuggerEnvironment environment,
|
||||
HandleId id, MutableHandleValue result);
|
||||
static MOZ_MUST_USE bool setVariable(JSContext* cx,
|
||||
HandleDebuggerEnvironment environment,
|
||||
HandleId id, HandleValue value);
|
||||
|
||||
private:
|
||||
static const ClassOps classOps_;
|
||||
|
||||
static const JSPropertySpec properties_[];
|
||||
static const JSFunctionSpec methods_[];
|
||||
|
||||
Env* referent() const {
|
||||
Env* env = static_cast<Env*>(getPrivate());
|
||||
MOZ_ASSERT(env);
|
||||
return env;
|
||||
}
|
||||
|
||||
Debugger* owner() const;
|
||||
|
||||
bool requireDebuggee(JSContext* cx) const;
|
||||
|
||||
static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
static MOZ_MUST_USE bool typeGetter(JSContext* cx, unsigned argc, Value* vp);
|
||||
static MOZ_MUST_USE bool scopeKindGetter(JSContext* cx, unsigned argc,
|
||||
Value* vp);
|
||||
static MOZ_MUST_USE bool parentGetter(JSContext* cx, unsigned argc,
|
||||
Value* vp);
|
||||
static MOZ_MUST_USE bool objectGetter(JSContext* cx, unsigned argc,
|
||||
Value* vp);
|
||||
static MOZ_MUST_USE bool calleeGetter(JSContext* cx, unsigned argc,
|
||||
Value* vp);
|
||||
static MOZ_MUST_USE bool inspectableGetter(JSContext* cx, unsigned argc,
|
||||
Value* vp);
|
||||
static MOZ_MUST_USE bool optimizedOutGetter(JSContext* cx, unsigned argc,
|
||||
Value* vp);
|
||||
|
||||
static MOZ_MUST_USE bool namesMethod(JSContext* cx, unsigned argc, Value* vp);
|
||||
static MOZ_MUST_USE bool findMethod(JSContext* cx, unsigned argc, Value* vp);
|
||||
static MOZ_MUST_USE bool getVariableMethod(JSContext* cx, unsigned argc,
|
||||
Value* vp);
|
||||
static MOZ_MUST_USE bool setVariableMethod(JSContext* cx, unsigned argc,
|
||||
Value* vp);
|
||||
};
|
||||
|
||||
} /* namespace js */
|
||||
|
||||
#endif /* dbg_Environment_h */
|
@ -9,6 +9,7 @@
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/ScopeExit.h"
|
||||
|
||||
#include "dbg/Environment.h"
|
||||
#include "dbg/NoExecute.h"
|
||||
#include "dbg/Object.h"
|
||||
#include "frontend/BytecodeCompilation.h"
|
||||
|
@ -23,6 +23,7 @@ include('../js-cxxflags.mozbuild')
|
||||
SOURCES = [
|
||||
'Debugger.cpp',
|
||||
'DebuggerMemory.cpp',
|
||||
'Environment.cpp',
|
||||
'Frame.cpp',
|
||||
'NoExecute.cpp',
|
||||
'Object.cpp',
|
||||
|
Loading…
Reference in New Issue
Block a user