Bug 963879 - Part 2: Synthesize completely optimized out scopes. (r=jimb)

This commit is contained in:
Shu-yu Guo 2015-01-14 15:18:43 -08:00
parent 48c3cefd90
commit d3357df3ed
4 changed files with 136 additions and 24 deletions

View File

@ -386,6 +386,7 @@ MSG_DEF(JSMSG_NOT_TRACKING_ALLOCATIONS, 1, JSEXN_ERR, "Cannot call {0} without s
MSG_DEF(JSMSG_OBJECT_METADATA_CALLBACK_ALREADY_SET, 0, JSEXN_ERR, "Cannot track object allocation, because other tools are already doing so")
MSG_DEF(JSMSG_QUERY_INNERMOST_WITHOUT_LINE_URL, 0, JSEXN_TYPEERR, "findScripts query object with 'innermost' property must have 'line' and either 'displayURL', 'url', or 'source'")
MSG_DEF(JSMSG_QUERY_LINE_WITHOUT_URL, 0, JSEXN_TYPEERR, "findScripts query object has 'line' property, but no 'displayURL', 'url', or 'source' property")
MSG_DEF(JSMSG_DEBUG_CANT_SET_OPT_ENV, 1, JSEXN_REFERENCEERR, "can't set `{0}' in an optimized-out environment")
// Intl
MSG_DEF(JSMSG_DATE_NOT_FINITE, 0, JSEXN_RANGEERR, "date value is not finite in DateTimeFormat.format()")

View File

@ -279,6 +279,32 @@ CallObject::createForStrictEval(JSContext *cx, AbstractFramePtr frame)
return create(cx, script, scopeChain, callee);
}
CallObject *
CallObject::createHollowForDebug(JSContext *cx, HandleFunction callee)
{
MOZ_ASSERT(!callee->isHeavyweight());
// This scope's parent link is never used: the DebugScopeObject that
// refers to this scope carries its own parent link, which is what
// Debugger uses to construct the tree of Debugger.Environment objects. So
// just parent this scope directly to the global.
Rooted<GlobalObject *> global(cx, &callee->global());
Rooted<CallObject *> callobj(cx, createForFunction(cx, global, callee));
if (!callobj)
return nullptr;
RootedValue optimizedOut(cx, MagicValue(JS_OPTIMIZED_OUT));
RootedId id(cx);
RootedScript script(cx, callee->nonLazyScript());
for (BindingIter bi(script); !bi.done(); bi++) {
id = NameToId(bi->name());
if (!JSObject::setGeneric(cx, callobj, callobj, id, &optimizedOut, true))
return nullptr;
}
return callobj;
}
const Class CallObject::class_ = {
"Call",
JSCLASS_IS_ANONYMOUS | JSCLASS_HAS_RESERVED_SLOTS(CallObject::RESERVED_SLOTS)
@ -642,10 +668,9 @@ const Class StaticEvalObject::class_ = {
/*****************************************************************************/
ClonedBlockObject *
ClonedBlockObject::create(JSContext *cx, Handle<StaticBlockObject *> block, AbstractFramePtr frame)
/* static */ ClonedBlockObject *
ClonedBlockObject::create(JSContext *cx, Handle<StaticBlockObject *> block, HandleObject enclosing)
{
assertSameCompartment(cx, frame);
MOZ_ASSERT(block->getClass() == &BlockObject::class_);
RootedTypeObject type(cx, cx->getNewType(&BlockObject::class_, TaggedProto(block.get())));
@ -660,9 +685,9 @@ ClonedBlockObject::create(JSContext *cx, Handle<StaticBlockObject *> block, Abst
return nullptr;
/* Set the parent if necessary, as for call objects. */
if (&frame.scopeChain()->global() != obj->getParent()) {
if (&enclosing->global() != obj->getParent()) {
MOZ_ASSERT(obj->getParent() == nullptr);
Rooted<GlobalObject*> global(cx, &frame.scopeChain()->global());
Rooted<GlobalObject*> global(cx, &enclosing->global());
if (!JSObject::setParent(cx, obj, global))
return nullptr;
}
@ -670,13 +695,41 @@ ClonedBlockObject::create(JSContext *cx, Handle<StaticBlockObject *> block, Abst
MOZ_ASSERT(!obj->inDictionaryMode());
MOZ_ASSERT(obj->slotSpan() >= block->numVariables() + RESERVED_SLOTS);
obj->setReservedSlot(SCOPE_CHAIN_SLOT, ObjectValue(*frame.scopeChain()));
obj->setReservedSlot(SCOPE_CHAIN_SLOT, ObjectValue(*enclosing));
MOZ_ASSERT(obj->isDelegate());
return &obj->as<ClonedBlockObject>();
}
/* static */ ClonedBlockObject *
ClonedBlockObject::create(JSContext *cx, Handle<StaticBlockObject *> block, AbstractFramePtr frame)
{
assertSameCompartment(cx, frame);
RootedObject enclosing(cx, frame.scopeChain());
return create(cx, block, enclosing);
}
/* static */ ClonedBlockObject *
ClonedBlockObject::createHollowForDebug(JSContext *cx, Handle<StaticBlockObject *> block)
{
MOZ_ASSERT(!block->needsClone());
// This scope's parent link is never used: the DebugScopeObject that
// refers to this scope carries its own parent link, which is what
// Debugger uses to construct the tree of Debugger.Environment objects. So
// just parent this scope directly to the global.
Rooted<GlobalObject *> global(cx, &block->global());
Rooted<ClonedBlockObject *> obj(cx, create(cx, block, global));
if (!obj)
return nullptr;
for (unsigned i = 0; i < block->numVariables(); i++)
obj->setVar(i, MagicValue(JS_OPTIMIZED_OUT), DONT_CHECK_ALIASING);
return obj;
}
void
ClonedBlockObject::copyUnaliasedValues(AbstractFramePtr frame)
{
@ -1321,6 +1374,7 @@ class DebugScopeProxy : public BaseProxyHandler
MutableHandleValue vp, AccessResult *accessResult) const
{
MOZ_ASSERT(&debugScope->scope() == scope);
MOZ_ASSERT_IF(action == SET, !debugScope->isOptimizedOut());
*accessResult = ACCESS_GENERIC;
LiveScopeVal *maybeLiveScope = DebugScopes::hasLiveScope(*scope);
@ -1684,6 +1738,9 @@ class DebugScopeProxy : public BaseProxyHandler
Rooted<DebugScopeObject*> debugScope(cx, &proxy->as<DebugScopeObject>());
Rooted<ScopeObject*> scope(cx, &proxy->as<DebugScopeObject>().scope());
if (debugScope->isOptimizedOut())
return Throw(cx, id, JSMSG_DEBUG_CANT_SET_OPT_ENV);
AccessResult access;
if (!handleUnaliasedAccess(cx, debugScope, scope, id, SET, vp, &access))
return false;
@ -1864,6 +1921,26 @@ DebugScopeObject::getMaybeSentinelValue(JSContext *cx, HandleId id, MutableHandl
return DebugScopeProxy::singleton.getMaybeSentinelValue(cx, self, id, vp);
}
bool
DebugScopeObject::isOptimizedOut() const
{
ScopeObject &s = scope();
if (DebugScopes::hasLiveScope(s))
return false;
if (s.is<ClonedBlockObject>())
return !s.as<ClonedBlockObject>().staticBlock().needsClone();
if (s.is<CallObject>()) {
return !s.as<CallObject>().isForEval() &&
!s.as<CallObject>().callee().isHeavyweight() &&
!maybeSnapshot();
}
return false;
}
bool
js_IsDebugScopeSlow(ProxyObject *proxy)
{
@ -2095,6 +2172,8 @@ DebugScopes::addDebugScope(JSContext *cx, const ScopeIter &si, DebugScopeObject
MOZ_ASSERT(cx->compartment() == debugScope.compartment());
MOZ_ASSERT_IF(si.withinInitialFrame() && si.initialFrame().isFunctionFrame(),
!si.initialFrame().callee()->isGenerator());
// Generators should always reify their scopes.
MOZ_ASSERT_IF(si.type() == ScopeIter::Call, !si.fun().isGenerator());
if (!CanUseDebugScopeMaps(cx))
return true;
@ -2110,12 +2189,16 @@ DebugScopes::addDebugScope(JSContext *cx, const ScopeIter &si, DebugScopeObject
return false;
}
MOZ_ASSERT(!scopes->liveScopes.has(&debugScope.scope()));
if (!scopes->liveScopes.put(&debugScope.scope(), LiveScopeVal(si))) {
js_ReportOutOfMemory(cx);
return false;
// Only add to liveScopes if we synthesized the debug scope on a live
// frame.
if (si.withinInitialFrame()) {
MOZ_ASSERT(!scopes->liveScopes.has(&debugScope.scope()));
if (!scopes->liveScopes.put(&debugScope.scope(), LiveScopeVal(si))) {
js_ReportOutOfMemory(cx);
return false;
}
liveScopesPostWriteBarrier(cx->runtime(), &scopes->liveScopes, &debugScope.scope());
}
liveScopesPostWriteBarrier(cx->runtime(), &scopes->liveScopes, &debugScope.scope());
return true;
}
@ -2423,10 +2506,6 @@ GetDebugScopeForMissing(JSContext *cx, const ScopeIter &si)
{
MOZ_ASSERT(!si.hasScopeObject() && si.canHaveScopeObject());
// FIXMEshu completely optimized-out scopes
if (!si.withinInitialFrame())
MOZ_CRASH("NYI");
if (DebugScopeObject *debugScope = DebugScopes::hasDebugScope(cx, si))
return debugScope;
@ -2449,9 +2528,15 @@ GetDebugScopeForMissing(JSContext *cx, const ScopeIter &si)
DebugScopeObject *debugScope = nullptr;
switch (si.type()) {
case ScopeIter::Call: {
RootedFunction callee(cx, &si.fun());
// Generators should always reify their scopes.
MOZ_ASSERT(!si.initialFrame().callee()->isGenerator());
Rooted<CallObject*> callobj(cx, CallObject::createForFunction(cx, si.initialFrame()));
MOZ_ASSERT(!callee->isGenerator());
Rooted<CallObject*> callobj(cx);
if (si.withinInitialFrame())
callobj = CallObject::createForFunction(cx, si.initialFrame());
else
callobj = CallObject::createHollowForDebug(cx, callee);
if (!callobj)
return nullptr;
@ -2468,10 +2553,15 @@ GetDebugScopeForMissing(JSContext *cx, const ScopeIter &si)
}
case ScopeIter::Block: {
// Generators should always reify their scopes.
MOZ_ASSERT_IF(si.initialFrame().isFunctionFrame(),
MOZ_ASSERT_IF(si.withinInitialFrame() && si.initialFrame().isFunctionFrame(),
!si.initialFrame().callee()->isGenerator());
Rooted<StaticBlockObject *> staticBlock(cx, &si.staticBlock());
ClonedBlockObject *block = ClonedBlockObject::create(cx, staticBlock, si.initialFrame());
ClonedBlockObject *block;
if (si.withinInitialFrame())
block = ClonedBlockObject::create(cx, staticBlock, si.initialFrame());
else
block = ClonedBlockObject::createHollowForDebug(cx, staticBlock);
if (!block)
return nullptr;

View File

@ -281,6 +281,7 @@ class CallObject : public ScopeObject
static CallObject *createForFunction(JSContext *cx, AbstractFramePtr frame);
static CallObject *createForStrictEval(JSContext *cx, AbstractFramePtr frame);
static CallObject *createHollowForDebug(JSContext *cx, HandleFunction callee);
/* True if this is for a strict mode eval frame. */
bool isForEval() const {
@ -633,10 +634,16 @@ class StaticBlockObject : public BlockObject
class ClonedBlockObject : public BlockObject
{
static ClonedBlockObject *create(JSContext *cx, Handle<StaticBlockObject *> block,
HandleObject enclosing);
public:
static ClonedBlockObject *create(JSContext *cx, Handle<StaticBlockObject *> block,
AbstractFramePtr frame);
static ClonedBlockObject *createHollowForDebug(JSContext *cx,
Handle<StaticBlockObject *> block);
/* The static block from which this block was cloned. */
StaticBlockObject &staticBlock() const {
return getProto()->as<StaticBlockObject>();
@ -758,8 +765,18 @@ class ScopeIter
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
// The key in MissingScopeMap, used to map live frames to their synthesized
// scopes.
// The key in MissingScopeMap. For live frames, maps live frames to their
// synthesized scopes. For completely optimized-out scopes, maps the static
// scope objects to their synthesized scopes. The scopes we synthesize for
// static scope objects are read-only, and we never use their parent links, so
// they don't need to be distinct.
//
// That is, completely optimized out scopes have can't be distinguished by
// frame. Note that even if the frame corresponding to the static scope is
// live on the stack, it is unsound to synthesize a scope from that live
// frame. In other words, the provenance of the scope chain is from allocated
// closures (i.e., allocation sites) and is irrecoverable from simple stack
// inspection (i.e., call sites).
class MissingScopeKey
{
friend class LiveScopeVal;
@ -786,7 +803,7 @@ class MissingScopeKey
bool operator!=(const MissingScopeKey &other) const {
return frame_ != other.frame_ || staticScope_ != other.staticScope_;
}
static void rekey(MissingScopeKey &k, const MissingScopeKey& newKey) {
static void rekey(MissingScopeKey &k, const MissingScopeKey &newKey) {
k = newKey;
}
};
@ -879,6 +896,10 @@ class DebugScopeObject : public ProxyObject
// Get a property by 'id', but returns sentinel values instead of throwing
// on exceptional cases.
bool getMaybeSentinelValue(JSContext *cx, HandleId id, MutableHandleValue vp);
// Does this debug scope not have a dynamic counterpart or was never live
// (and thus does not have a synthesized ScopeObject or a snapshot)?
bool isOptimizedOut() const;
};
/* Maintains per-compartment debug scope bookkeeping information. */

View File

@ -35,7 +35,7 @@ namespace js {
* Nightly) and without (all others). FIXME: Bug 1066322 - Enable ES6 symbols
* in all builds.
*/
static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 226;
static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 228;
static_assert(XDR_BYTECODE_VERSION_SUBTRAHEND % 2 == 0, "see the comment above");
static const uint32_t XDR_BYTECODE_VERSION =
uint32_t(0xb973c0de - (XDR_BYTECODE_VERSION_SUBTRAHEND
@ -44,7 +44,7 @@ static const uint32_t XDR_BYTECODE_VERSION =
#endif
));
static_assert(JSErr_Limit == 365,
static_assert(JSErr_Limit == 366,
"GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or "
"removed MSG_DEFs from js.msg, you should increment "
"XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's "