Ignore:
Timestamp:
Aug 28, 2014, 5:48:59 PM (11 years ago)
Author:
[email protected]
Message:

DebuggerCallFrame::scope() should return a DebuggerScope.
<https://webkit.org/b/134420>

Reviewed by Geoffrey Garen.

Source/JavaScriptCore:

Rolling back in r170680 with the fix for <https://webkit.org/b/135656>.

Previously, DebuggerCallFrame::scope() returns a JSActivation (and relevant
peers) which the WebInspector will use to introspect CallFrame variables.
Instead, we should be returning a DebuggerScope as an abstraction layer that
provides the introspection functionality that the WebInspector needs. This
is the first step towards not forcing every frame to have a JSActivation
object just because the debugger is enabled.

  1. Instantiate the debuggerScopeStructure as a member of the JSGlobalObject instead of the VM. This allows JSObject::globalObject() to be able to return the global object for the DebuggerScope.
  1. On the DebuggerScope's life-cycle management:

The DebuggerCallFrame is designed to be "valid" only during a debugging session
(while the debugger is broken) through the use of a DebuggerCallFrameScope in
Debugger::pauseIfNeeded(). Once the debugger resumes from the break, the
DebuggerCallFrameScope destructs, and the DebuggerCallFrame will be invalidated.
We can't guarantee (from this code alone) that the Inspector code isn't still
holding a ref to the DebuggerCallFrame (though they shouldn't), but by contract,
the frame will be invalidated, and any attempt to query it will return null values.
This is pre-existing behavior.

Now, we're adding the DebuggerScope into the picture. While a single debugger
pause session is in progress, the Inspector may request the scope from the
DebuggerCallFrame. While the DebuggerCallFrame is still valid, we want
DebuggerCallFrame::scope() to always return the same DebuggerScope object.
This is why we hold on to the DebuggerScope with a strong ref.

If we use a weak ref instead, the following cooky behavior can manifest:

  1. The Inspector calls Debugger::scope() to get the top scope.
  2. The Inspector iterates down the scope chain and is now only holding a reference to a parent scope. It is no longer referencing the top scope.
  3. A GC occurs, and the DebuggerCallFrame's weak m_scope ref to the top scope gets cleared.
  4. The Inspector calls DebuggerCallFrame::scope() to get the top scope again but gets a different DebuggerScope instance.
  5. The Inspector iterates down the scope chain but never sees the parent scope instance that retained a ref to in step 2 above. This is because when iterating this new DebuggerScope instance (which has no knowledge of the previous parent DebuggerScope instance), a new DebuggerScope instance will get created for the same parent scope.

Since the DebuggerScope is a JSObject, its liveness is determined by its reachability.
However, its "validity" is determined by the life-cycle of its owner DebuggerCallFrame.
When the owner DebuggerCallFrame gets invalidated, its debugger scope chain (if
instantiated) will also get invalidated. This is why we need the
DebuggerScope::invalidateChain() method. The Inspector should not be using the
DebuggerScope instance after its owner DebuggerCallFrame is invalidated. If it does,
those methods will do nothing or returned a failed status.

Fix for <https://webkit.org/b/135656>:

  1. DebuggerScope::getOwnPropertySlot() and DebuggerScope::put() need to set m_thisValue in the returned slot to the wrapped scope object. Previously, it was pointing to the DebuggerScope though the rest of the fields in the returned slot will be set to data pertaining the wrapped scope object.
  1. DebuggerScope::getOwnPropertySlot() will invoke getPropertySlot() on its wrapped scope. This is because JSObject::getPropertySlot() cannot be overridden, and when called on a DebuggerScope, will not know to look in the ptototype chain of the DebuggerScope's wrapped scope. Hence, we'll treat all properties in the wrapped scope as own properties in the DebuggerScope. This is fine because the WebInspector does not presently care about where in the prototype chain the scope property comes from.

Note that the DebuggerScope and the JSActivation objects that it wraps do
not have prototypes. They are always jsNull(). This works perfectly with
the above change to use getPropertySlot() instead of getOwnPropertySlot().
To make this an explicit invariant, I also changed DebuggerScope::createStructure()
and JSActivation::createStructure() to not take a prototype argument, and
to always use jsNull() for their prototype value.

  • debugger/Debugger.h:
  • debugger/DebuggerCallFrame.cpp:

(JSC::DebuggerCallFrame::scope):
(JSC::DebuggerCallFrame::evaluate):
(JSC::DebuggerCallFrame::invalidate):

  • debugger/DebuggerCallFrame.h:
  • debugger/DebuggerScope.cpp:

(JSC::DebuggerScope::DebuggerScope):
(JSC::DebuggerScope::finishCreation):
(JSC::DebuggerScope::visitChildren):
(JSC::DebuggerScope::className):
(JSC::DebuggerScope::getOwnPropertySlot):
(JSC::DebuggerScope::put):
(JSC::DebuggerScope::deleteProperty):
(JSC::DebuggerScope::getOwnPropertyNames):
(JSC::DebuggerScope::defineOwnProperty):
(JSC::DebuggerScope::next):
(JSC::DebuggerScope::invalidateChain):
(JSC::DebuggerScope::isWithScope):
(JSC::DebuggerScope::isGlobalScope):
(JSC::DebuggerScope::isFunctionOrEvalScope):

  • debugger/DebuggerScope.h:

(JSC::DebuggerScope::create):
(JSC::DebuggerScope::createStructure):
(JSC::DebuggerScope::iterator::iterator):
(JSC::DebuggerScope::iterator::get):
(JSC::DebuggerScope::iterator::operator++):
(JSC::DebuggerScope::iterator::operator==):
(JSC::DebuggerScope::iterator::operator!=):
(JSC::DebuggerScope::isValid):
(JSC::DebuggerScope::jsScope):
(JSC::DebuggerScope::begin):
(JSC::DebuggerScope::end):

  • inspector/JSJavaScriptCallFrame.cpp:

(Inspector::JSJavaScriptCallFrame::scopeType):
(Inspector::JSJavaScriptCallFrame::scopeChain):

  • inspector/JavaScriptCallFrame.h:

(Inspector::JavaScriptCallFrame::scopeChain):

  • inspector/ScriptDebugServer.cpp:
  • runtime/JSActivation.h:

(JSC::JSActivation::createStructure):

  • runtime/JSGlobalObject.cpp:

(JSC::JSGlobalObject::reset):
(JSC::JSGlobalObject::visitChildren):

  • runtime/JSGlobalObject.h:

(JSC::JSGlobalObject::debuggerScopeStructure):

  • runtime/JSObject.cpp:
  • runtime/JSObject.h:

(JSC::JSObject::isWithScope):

  • runtime/JSScope.h:
  • runtime/PropertySlot.h:

(JSC::PropertySlot::setThisValue):

  • runtime/PutPropertySlot.h:

(JSC::PutPropertySlot::setThisValue):

  • runtime/VM.cpp:

(JSC::VM::VM):

  • runtime/VM.h:

Source/WebCore:

No new tests.

Rolling back in r170680 with the fix for <https://webkit.org/b/135656>.

  • bindings/js/ScriptController.cpp:

(WebCore::ScriptController::attachDebugger):

  • We should acquire the JSLock before modifying a JS global object.
File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/debugger/DebuggerScope.cpp

    r172372 r173100  
    2929#include "JSActivation.h"
    3030#include "JSCInlines.h"
     31#include "JSWithScope.h"
    3132
    3233namespace JSC {
     
    3637const ClassInfo DebuggerScope::s_info = { "DebuggerScope", &Base::s_info, 0, CREATE_METHOD_TABLE(DebuggerScope) };
    3738
    38 DebuggerScope::DebuggerScope(VM& vm)
    39     : JSNonFinalObject(vm, vm.debuggerScopeStructure.get())
     39DebuggerScope::DebuggerScope(VM& vm, JSScope* scope)
     40    : JSNonFinalObject(vm, scope->globalObject()->debuggerScopeStructure())
    4041{
     42    ASSERT(scope);
     43    m_scope.set(vm, this, scope);
    4144}
    4245
    43 void DebuggerScope::finishCreation(VM& vm, JSObject* activation)
     46void DebuggerScope::finishCreation(VM& vm)
    4447{
    4548    Base::finishCreation(vm);
    46     ASSERT(activation);
    47     ASSERT(activation->isActivationObject());
    48     m_activation.set(vm, this, jsCast<JSActivation*>(activation));
    4949}
    5050
     
    5454    ASSERT_GC_OBJECT_INHERITS(thisObject, info());
    5555    JSObject::visitChildren(thisObject, visitor);
    56     visitor.append(&thisObject->m_activation);
     56    visitor.append(&thisObject->m_scope);
     57    visitor.append(&thisObject->m_next);
    5758}
    5859
    5960String DebuggerScope::className(const JSObject* object)
    6061{
    61     const DebuggerScope* thisObject = jsCast<const DebuggerScope*>(object);
    62     return thisObject->m_activation->methodTable()->className(thisObject->m_activation.get());
     62    const DebuggerScope* scope = jsCast<const DebuggerScope*>(object);
     63    ASSERT(scope->isValid());
     64    if (!scope->isValid())
     65        return String();
     66    JSObject* thisObject = JSScope::objectAtScope(scope->jsScope());
     67    return thisObject->methodTable()->className(thisObject);
    6368}
    6469
    6570bool DebuggerScope::getOwnPropertySlot(JSObject* object, ExecState* exec, PropertyName propertyName, PropertySlot& slot)
    6671{
    67     DebuggerScope* thisObject = jsCast<DebuggerScope*>(object);
    68     return thisObject->m_activation->methodTable()->getOwnPropertySlot(thisObject->m_activation.get(), exec, propertyName, slot);
     72    DebuggerScope* scope = jsCast<DebuggerScope*>(object);
     73    ASSERT(scope->isValid());
     74    if (!scope->isValid())
     75        return false;
     76    JSObject* thisObject = JSScope::objectAtScope(scope->jsScope());
     77    slot.setThisValue(JSValue(thisObject));
     78
     79    // By default, JSObject::getPropertySlot() will look in the DebuggerScope's prototype
     80    // chain and not the wrapped scope, and JSObject::getPropertySlot() cannot be overridden
     81    // to behave differently for the DebuggerScope.
     82    //
     83    // Instead, we'll treat all properties in the wrapped scope and its prototype chain as
     84    // the own properties of the DebuggerScope. This is fine because the WebInspector
     85    // does not presently need to distinguish between what's owned at each level in the
     86    // prototype chain. Hence, we'll invoke getPropertySlot() on the wrapped scope here
     87    // instead of getOwnPropertySlot().
     88    return thisObject->getPropertySlot(exec, propertyName, slot);
    6989}
    7090
    7191void DebuggerScope::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
    7292{
    73     DebuggerScope* thisObject = jsCast<DebuggerScope*>(cell);
    74     thisObject->m_activation->methodTable()->put(thisObject->m_activation.get(), exec, propertyName, value, slot);
     93    DebuggerScope* scope = jsCast<DebuggerScope*>(cell);
     94    ASSERT(scope->isValid());
     95    if (!scope->isValid())
     96        return;
     97    JSObject* thisObject = JSScope::objectAtScope(scope->jsScope());
     98    slot.setThisValue(JSValue(thisObject));
     99    thisObject->methodTable()->put(thisObject, exec, propertyName, value, slot);
    75100}
    76101
    77102bool DebuggerScope::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName)
    78103{
    79     DebuggerScope* thisObject = jsCast<DebuggerScope*>(cell);
    80     return thisObject->m_activation->methodTable()->deleteProperty(thisObject->m_activation.get(), exec, propertyName);
     104    DebuggerScope* scope = jsCast<DebuggerScope*>(cell);
     105    ASSERT(scope->isValid());
     106    if (!scope->isValid())
     107        return false;
     108    JSObject* thisObject = JSScope::objectAtScope(scope->jsScope());
     109    return thisObject->methodTable()->deleteProperty(thisObject, exec, propertyName);
    81110}
    82111
    83112void DebuggerScope::getOwnPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode)
    84113{
    85     DebuggerScope* thisObject = jsCast<DebuggerScope*>(object);
    86     thisObject->m_activation->methodTable()->getPropertyNames(thisObject->m_activation.get(), exec, propertyNames, mode);
     114    DebuggerScope* scope = jsCast<DebuggerScope*>(object);
     115    ASSERT(scope->isValid());
     116    if (!scope->isValid())
     117        return;
     118    JSObject* thisObject = JSScope::objectAtScope(scope->jsScope());
     119    thisObject->methodTable()->getPropertyNames(thisObject, exec, propertyNames, mode);
    87120}
    88121
    89122bool DebuggerScope::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName propertyName, const PropertyDescriptor& descriptor, bool shouldThrow)
    90123{
    91     DebuggerScope* thisObject = jsCast<DebuggerScope*>(object);
    92     return thisObject->m_activation->methodTable()->defineOwnProperty(thisObject->m_activation.get(), exec, propertyName, descriptor, shouldThrow);
     124    DebuggerScope* scope = jsCast<DebuggerScope*>(object);
     125    ASSERT(scope->isValid());
     126    if (!scope->isValid())
     127        return false;
     128    JSObject* thisObject = JSScope::objectAtScope(scope->jsScope());
     129    return thisObject->methodTable()->defineOwnProperty(thisObject, exec, propertyName, descriptor, shouldThrow);
     130}
     131
     132DebuggerScope* DebuggerScope::next()
     133{
     134    ASSERT(isValid());
     135    if (!m_next && m_scope->next()) {
     136        VM& vm = *m_scope->vm();
     137        DebuggerScope* nextScope = create(vm, m_scope->next());
     138        m_next.set(vm, this, nextScope);
     139    }
     140    return m_next.get();
     141}
     142
     143void DebuggerScope::invalidateChain()
     144{
     145    DebuggerScope* scope = this;
     146    while (scope) {
     147        ASSERT(scope->isValid());
     148        DebuggerScope* nextScope = scope->m_next.get();
     149        scope->m_next.clear();
     150        scope->m_scope.clear();
     151        scope = nextScope;
     152    }
     153}
     154
     155bool DebuggerScope::isWithScope() const
     156{
     157    return m_scope->isWithScope();
     158}
     159
     160bool DebuggerScope::isGlobalScope() const
     161{
     162    return m_scope->isGlobalObject();
     163}
     164
     165bool DebuggerScope::isFunctionOrEvalScope() const
     166{
     167    // In the current debugger implementation, every function or eval will create an
     168    // activation object. Hence, an activation object implies a function or eval scope.
     169    return m_scope->isActivationObject();
    93170}
    94171
Note: See TracChangeset for help on using the changeset viewer.