Ignore:
Timestamp:
Jan 26, 2007, 8:50:17 PM (18 years ago)
Author:
ggaren
Message:

JavaScriptCore:

Reviewed by Maciej Stachowiak.


Fixed <rdar://problem/4608404> WebScriptObject's _rootObject lack
of ownership policy causes crashes (e.g., in Dashcode)


The old model for RootObject ownership was either to (1) leak them or (2) assign
them to a single owner -- the WebCore::Frame -- which would destroy them
when it believed that all of its plug-ins had unloaded.


This model was broken because of (1) and also because plug-ins are not the only
RootObject clients. All Bindings clients are RootObjects clients, including
applications, which outlive any particular WebCore::Frame.


The new model for RootObject ownership is to reference-count them, with a
throw-back to the old model: The WebCore::Frame tracks the RootObjects
it creates, and invalidates them when it believes that all of its plug-ins
have unloaded.


We maintain this throw-back to avoid plug-in leaks, particularly from Java.
Java is completely broken when it comes to releasing JavaScript objects.
Comments in our code allege that Java does not always call finalize when
collecting objects. Moreoever, my own testing reveals that, when Java does
notify JavaScript of a finalize, the data it provides is totally bogus.


This setup is far from ideal, but I don't think we can do better without
completely rewriting the bindings code, and possibly part of the Java
plug-in / VM.


Layout tests pass. No additional leaks reported. WebCore/manual-tests/*liveconnect*
and a few LiveConnect demos on the web also run without a hitch.


const RootObject* => RootObject*, since we need to ref/deref


  • bindings/NP_jsobject.cpp: (jsDeallocate): deref our RootObjects. Also unprotect or JSObject, instead of just relying on the RootObject to do it for us when it's invalidated. (_isSafeScript): Check RootObject validity. (_NPN_CreateScriptObject): ditto (_NPN_Invoke): ditto (_NPN_Evaluate): ditto (_NPN_GetProperty): ditto (_NPN_SetProperty): ditto (_NPN_RemoveProperty): ditto (_NPN_HasProperty): ditto (_NPN_HasMethod): ditto (_NPN_SetException): ditto
  • bindings/runtime_root.cpp: Revived bit-rotted LIAR LIAR LIAR comment.


LOOK: Added support for invalidating RootObjects without deleting them,
which is the main goal of this patch.

Moved protect counting into the RootObject class, to emphasize that
the RootObject protects the JSObject, and unprotects it upon being invalidated.

addNativeReference => RootObject::gcProtect
removeNativeReference => RootObject::gcUnprotect
ProtectCountSet::contains => RootObject::gcIsProtected


I know we'll all be sad to see the word "native" go.


  • bindings/runtime_root.h: Added ref-counting support to RootObject, with all the standard accoutrements.
  • bindings/c/c_utility.cpp: (KJS::Bindings::convertValueToNPVariant): If we can't find a valid RootObject, return void instead of just leaking.
  • bindings/jni/jni_instance.cpp: (JavaInstance::JavaInstance): Don't take a RootObject in our constructor; be like other Instances and require the caller to call setRootObject. This reduces the number of ownership code paths. (JavaInstance::invokeMethod): Check RootObject for validity.
  • bindings/jni/jni_instance.h: Removed private no-arg constructor. Having an arg constructor accomplishes the same thing.
  • bindings/jni/jni_jsobject.cpp: (JavaJSObject::invoke): No need to call findProtectCountSet, because finalize() checks for RootObject validity. (JavaJSObject::JavaJSObject): check RootObject for validity (JavaJSObject::call): ditto (JavaJSObject::eval): ditto (JavaJSObject::getMember): ditto (JavaJSObject::setMember): ditto (JavaJSObject::removeMember): ditto (JavaJSObject::getSlot): ditto (JavaJSObject::setSlot): ditto (JavaJSObject::toString): ditto (JavaJSObject::finalize): ditto (JavaJSObject::createNative): No need to tell the RootObject to protect the global object, since the RootObject already owns the interpreter.
  • bindings/jni/jni_runtime.cpp: (JavaArray::JavaArray): Removed copy construcutor becaue it was unused. Dead code is dangerous code.
  • bindings/objc/objc_runtime.mm: Added WebUndefined protocol. Previous use of WebScriptObject was bogus, because WebUndefined is not a subclass of WebScriptObject. (convertValueToObjcObject): If we can't find a valid RootObject, return nil instead of just leaking.
  • bindings/objc/objc_utility.mm: (KJS::Bindings::convertValueToObjcValue): If we can't find a valid RootObject, return nil instead of just leaking.

LayoutTests:

Reviewed by Maciej Stachowiak.


Added test for <rdar://problem/4608404> WebScriptObject's _rootObject lack
of ownership policy causes crashes (e.g., in Dashcode)


No test for Java or NPP versions of this bug because there's no reliable way to
make Java and NPP objects outlive their RootObjects (although Java objects
sometimes do).

  • plugins/root-object-premature-delete-crash-expected.txt: Added.
  • plugins/root-object-premature-delete-crash.html: Added.

WebCore:

Reviewed by Maciej Stachowiak.


Fixed <rdar://problem/4608404> WebScriptObject's _executionContext lack
of ownership policy causes crashes (e.g., in Dashcode)

Added RootObject ref-counting goodness.

  • page/mac/FrameMac.h:
  • page/mac/FrameMac.mm: (WebCore::FrameMac::cleanupPluginObjects): Invalidate our RootObjects instead of detroying them. Track _bindingRootObject separately from the rest of our RootObjects, since it has its own variable.
  • page/mac/WebCoreFrameBridge.mm: (createRootObject): Use the Frame's new, more encapsulated function to create a RootObject.
  • bindings/objc/WebScriptObject.mm: Nixed rootObject setters, since they were unused and they complicated reference-counting.

WebKitTools:

Reviewed by Maciej Stachowiak.


Added support for test for <rdar://problem/4608404> WebScriptObject's
_rootObject lack of ownership policy causes crashes (e.g., in Dashcode)


  • DumpRenderTree/DumpRenderTree.m: (+[LayoutTestController isSelectorExcludedFromWebScript:]): (+[LayoutTestController webScriptNameForSelector:]): (-[LayoutTestController storeWebScriptObject:]): (-[LayoutTestController accessStoredWebScriptObject]): (-[LayoutTestController dealloc]):
File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/JavaScriptCore/bindings/runtime_root.cpp

    r18811 r19183  
    2828#include "runtime_root.h"
    2929#include <wtf/HashCountedSet.h>
     30#include <wtf/HashSet.h>
    3031
    3132namespace KJS { namespace Bindings {
    3233
    33 // Java does NOT always call finalize (and thus KJS_JSObject_JSFinalize) when
    34 // it collects an objects.  This presents some difficulties.  We must ensure
    35 // the a JavaJSObject's corresponding JavaScript object doesn't get collected.  We
    36 // do this by incrementing the JavaScript's reference count the first time we
    37 // create a JavaJSObject for it, and decrementing the JavaScript reference count when
    38 // the last JavaJSObject that refers to it is finalized, or when the applet is
    39 // shutdown.
    40 //
    41 // To do this we keep a map that maps each applet instance
    42 // to the JavaScript objects it is referencing.  For each JavaScript instance
    43 // we also maintain a secondary reference count.  When that reference count reaches
    44 // 1 OR the applet is shutdown we deref the JavaScript instance.  Applet instances
    45 // are represented by a jlong.
    46 
    47 typedef HashMap<const RootObject*, ProtectCountSet*> RootObjectMap;
    48 
    49 static RootObjectMap* rootObjectMap()
    50 {
    51     static RootObjectMap staticRootObjectMap;
    52     return &staticRootObjectMap;
    53 }
    54 
    55 static ProtectCountSet* getProtectCountSet(const RootObject* rootObject)
    56 {
    57     ProtectCountSet* protectCountSet = rootObjectMap()->get(rootObject);
    58 
    59     if (!protectCountSet) {
    60         protectCountSet = new ProtectCountSet;
    61         rootObjectMap()->add(rootObject, protectCountSet);
    62     }
    63     return protectCountSet;
    64 }
    65 
    66 static void destroyProtectCountSet(const RootObject* rootObject, ProtectCountSet* protectCountSet)
    67 {
    68     rootObjectMap()->remove(rootObject);
    69     delete protectCountSet;
     34// This code attempts to solve two problems: (1) plug-ins leaking references to
     35// JS and the DOM; (2) plug-ins holding stale references to JS and the DOM. Previous
     36// comments in this file claimed that problem #1 was an issue in Java, in particular,
     37// because Java, allegedly, didn't always call finalize when collecting an object.
     38
     39typedef HashSet<RootObject*> RootObjectSet;
     40
     41static RootObjectSet* rootObjectSet()
     42{
     43    static RootObjectSet staticRootObjectSet;
     44    return &staticRootObjectSet;
    7045}
    7146
     
    7348// fix them by adding a JSObject to RootObject dictionary.
    7449
    75 ProtectCountSet* findProtectCountSet(JSObject* jsObject)
    76 {
    77     const RootObject* rootObject = findRootObject(jsObject);
    78     return rootObject ? getProtectCountSet(rootObject) : 0;
    79 }
    80 
    81 const RootObject* findRootObject(JSObject* jsObject)
    82 {
    83     RootObjectMap::const_iterator end = rootObjectMap()->end();
    84     for (RootObjectMap::const_iterator it = rootObjectMap()->begin(); it != end; ++it) {
    85         ProtectCountSet* set = it->second;
    86         if (set->contains(jsObject))
    87             return it->first;
    88     }
    89    
     50RootObject* findRootObject(JSObject* jsObject)
     51{
     52    RootObjectSet::const_iterator end = rootObjectSet()->end();
     53    for (RootObjectSet::const_iterator it = rootObjectSet()->begin(); it != end; ++it) {
     54        if ((*it)->gcIsProtected(jsObject))
     55            return *it;
     56    }
    9057    return 0;
    9158}
    9259
    93 const RootObject* findRootObject(Interpreter* interpreter)
    94 {
    95     RootObjectMap::const_iterator end = rootObjectMap()->end();
    96     for (RootObjectMap::const_iterator it = rootObjectMap()->begin(); it != end; ++it) {
    97         const RootObject* aRootObject = it->first;
    98        
    99         if (aRootObject->interpreter() == interpreter)
    100             return aRootObject;
    101     }
    102    
     60RootObject* findRootObject(Interpreter* interpreter)
     61{
     62    RootObjectSet::const_iterator end = rootObjectSet()->end();
     63    for (RootObjectSet::const_iterator it = rootObjectSet()->begin(); it != end; ++it) {
     64        if ((*it)->interpreter() == interpreter)
     65            return *it;
     66    }
    10367    return 0;
    104 }
    105 
    106 void addNativeReference(const RootObject* rootObject, JSObject* jsObject)
    107 {
    108     if (!rootObject)
    109         return;
    110    
    111     ProtectCountSet* protectCountSet = getProtectCountSet(rootObject);
    112     if (!protectCountSet->contains(jsObject)) {
    113         JSLock lock;
    114         gcProtect(jsObject);
    115     }
    116     protectCountSet->add(jsObject);
    117 }
    118 
    119 void removeNativeReference(JSObject* jsObject)
    120 {
    121     if (!jsObject)
    122         return;
    123 
    124     // We might have manually detroyed the root object and its protect set already
    125     ProtectCountSet* protectCountSet = findProtectCountSet(jsObject);
    126     if (!protectCountSet)
    127         return;
    128 
    129     if (protectCountSet->count(jsObject) == 1) {
    130         JSLock lock;
    131         gcUnprotect(jsObject);
    132     }
    133     protectCountSet->remove(jsObject);
    13468}
    13569
     
    256190    CFRunLoopAddSource(RootObject::_runLoop, RootObject::_performJavaScriptSource, kCFRunLoopDefaultMode);
    257191}
     192
    258193#endif
    259194
    260 // Destroys the RootObject and unprotects all JSObjects associated with it.
    261 void RootObject::destroy()
    262 {
    263     ProtectCountSet* protectCountSet = getProtectCountSet(this);
    264    
    265     if (protectCountSet) {
    266         ProtectCountSet::iterator end = protectCountSet->end();
    267         for (ProtectCountSet::iterator it = protectCountSet->begin(); it != end; ++it) {
    268             JSLock lock;
    269             gcUnprotect(it->first);           
    270         }
    271 
    272         destroyProtectCountSet(this, protectCountSet);
    273     }
    274 
    275     delete this;
     195PassRefPtr<RootObject> RootObject::create(const void* nativeHandle, PassRefPtr<Interpreter> interpreter)
     196{
     197    return new RootObject(nativeHandle, interpreter);
     198}
     199
     200RootObject::RootObject(const void* nativeHandle, PassRefPtr<Interpreter> interpreter)
     201    : m_refCount(0)
     202    , m_isValid(true)
     203    , m_nativeHandle(nativeHandle)
     204    , m_interpreter(interpreter)
     205{
     206    ASSERT(m_interpreter);
     207    rootObjectSet()->add(this);
     208}
     209
     210RootObject::~RootObject()
     211{
     212    if (m_isValid)
     213        invalidate();
     214}
     215
     216void RootObject::invalidate()
     217{
     218    if (!m_isValid)
     219        return;
     220
     221    m_isValid = false;
     222
     223    m_nativeHandle = 0;
     224    m_interpreter = 0;
     225
     226    ProtectCountSet::iterator end = m_protectCountSet.end();
     227    for (ProtectCountSet::iterator it = m_protectCountSet.begin(); it != end; ++it) {
     228        JSLock lock;
     229        KJS::gcUnprotect(it->first);
     230    }
     231    m_protectCountSet.clear();
     232
     233    rootObjectSet()->remove(this);
     234}
     235
     236void RootObject::gcProtect(JSObject* jsObject)
     237{
     238    ASSERT(m_isValid);
     239   
     240    if (!m_protectCountSet.contains(jsObject)) {
     241        JSLock lock;
     242        KJS::gcProtect(jsObject);
     243    }
     244    m_protectCountSet.add(jsObject);
     245}
     246
     247void RootObject::gcUnprotect(JSObject* jsObject)
     248{
     249    ASSERT(m_isValid);
     250   
     251    if (!jsObject)
     252        return;
     253
     254    if (m_protectCountSet.count(jsObject) == 1) {
     255        JSLock lock;
     256        KJS::gcUnprotect(jsObject);
     257    }
     258    m_protectCountSet.remove(jsObject);
     259}
     260
     261bool RootObject::gcIsProtected(JSObject* jsObject)
     262{
     263    ASSERT(m_isValid);
     264    return m_protectCountSet.contains(jsObject);
     265}
     266
     267const void* RootObject::nativeHandle() const
     268{
     269    ASSERT(m_isValid);
     270    return m_nativeHandle;
     271}
     272
     273Interpreter* RootObject::interpreter() const
     274{
     275    ASSERT(m_isValid);
     276    return m_interpreter.get();
    276277}
    277278
Note: See TracChangeset for help on using the changeset viewer.