Home | History | Annotate | Download | only in v8
      1 /*
      2  * Copyright (C) 2009 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 #include "config.h"
     32 #include "V8GCController.h"
     33 
     34 #include "ActiveDOMObject.h"
     35 #include "Attr.h"
     36 #include "DOMDataStore.h"
     37 #include "DOMImplementation.h"
     38 #include "HTMLImageElement.h"
     39 #include "HTMLNames.h"
     40 #include "MessagePort.h"
     41 #include "PlatformBridge.h"
     42 #include "RetainedDOMInfo.h"
     43 #include "RetainedObjectInfo.h"
     44 #include "V8Binding.h"
     45 #include "V8CSSRule.h"
     46 #include "V8CSSRuleList.h"
     47 #include "V8CSSStyleDeclaration.h"
     48 #include "V8DOMImplementation.h"
     49 #include "V8MessagePort.h"
     50 #include "V8StyleSheet.h"
     51 #include "V8StyleSheetList.h"
     52 #include "WrapperTypeInfo.h"
     53 
     54 #include <algorithm>
     55 #include <utility>
     56 #include <v8-debug.h>
     57 #include <wtf/HashMap.h>
     58 #include <wtf/StdLibExtras.h>
     59 #include <wtf/UnusedParam.h>
     60 
     61 namespace WebCore {
     62 
     63 #ifndef NDEBUG
     64 // Keeps track of global handles created (not JS wrappers
     65 // of DOM objects). Often these global handles are source
     66 // of leaks.
     67 //
     68 // If you want to let a C++ object hold a persistent handle
     69 // to a JS object, you should register the handle here to
     70 // keep track of leaks.
     71 //
     72 // When creating a persistent handle, call:
     73 //
     74 // #ifndef NDEBUG
     75 //    V8GCController::registerGlobalHandle(type, host, handle);
     76 // #endif
     77 //
     78 // When releasing the handle, call:
     79 //
     80 // #ifndef NDEBUG
     81 //    V8GCController::unregisterGlobalHandle(type, host, handle);
     82 // #endif
     83 //
     84 typedef HashMap<v8::Value*, GlobalHandleInfo*> GlobalHandleMap;
     85 
     86 static GlobalHandleMap& globalHandleMap()
     87 {
     88     DEFINE_STATIC_LOCAL(GlobalHandleMap, staticGlobalHandleMap, ());
     89     return staticGlobalHandleMap;
     90 }
     91 
     92 // The function is the place to set the break point to inspect
     93 // live global handles. Leaks are often come from leaked global handles.
     94 static void enumerateGlobalHandles()
     95 {
     96     for (GlobalHandleMap::iterator it = globalHandleMap().begin(), end = globalHandleMap().end(); it != end; ++it) {
     97         GlobalHandleInfo* info = it->second;
     98         UNUSED_PARAM(info);
     99         v8::Value* handle = it->first;
    100         UNUSED_PARAM(handle);
    101     }
    102 }
    103 
    104 void V8GCController::registerGlobalHandle(GlobalHandleType type, void* host, v8::Persistent<v8::Value> handle)
    105 {
    106     ASSERT(!globalHandleMap().contains(*handle));
    107     globalHandleMap().set(*handle, new GlobalHandleInfo(host, type));
    108 }
    109 
    110 void V8GCController::unregisterGlobalHandle(void* host, v8::Persistent<v8::Value> handle)
    111 {
    112     ASSERT(globalHandleMap().contains(*handle));
    113     GlobalHandleInfo* info = globalHandleMap().take(*handle);
    114     ASSERT(info->m_host == host);
    115     delete info;
    116 }
    117 #endif // ifndef NDEBUG
    118 
    119 typedef HashMap<Node*, v8::Object*> DOMNodeMap;
    120 typedef HashMap<void*, v8::Object*> DOMObjectMap;
    121 
    122 #ifndef NDEBUG
    123 
    124 class DOMObjectVisitor : public DOMWrapperMap<void>::Visitor {
    125 public:
    126     void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper)
    127     {
    128         WrapperTypeInfo* type = V8DOMWrapper::domWrapperType(wrapper);
    129         UNUSED_PARAM(type);
    130         UNUSED_PARAM(object);
    131     }
    132 };
    133 
    134 class EnsureWeakDOMNodeVisitor : public DOMWrapperMap<Node>::Visitor {
    135 public:
    136     void visitDOMWrapper(DOMDataStore* store, Node* object, v8::Persistent<v8::Object> wrapper)
    137     {
    138         UNUSED_PARAM(object);
    139         ASSERT(wrapper.IsWeak());
    140     }
    141 };
    142 
    143 #endif // NDEBUG
    144 
    145 class GCPrologueVisitor : public DOMWrapperMap<void>::Visitor {
    146 public:
    147     void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper)
    148     {
    149         WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper);
    150 
    151         // Additional handling of message port ensuring that entangled ports also
    152         // have their wrappers entangled. This should ideally be handled when the
    153         // ports are actually entangled in MessagePort::entangle, but to avoid
    154         // forking MessagePort.* this is postponed to GC time. Having this postponed
    155         // has the drawback that the wrappers are "entangled/unentangled" for each
    156         // GC even though their entaglement most likely is still the same.
    157         if (V8MessagePort::info.equals(typeInfo)) {
    158             // Mark each port as in-use if it's entangled. For simplicity's sake, we assume all ports are remotely entangled,
    159             // since the Chromium port implementation can't tell the difference.
    160             MessagePort* port1 = static_cast<MessagePort*>(object);
    161             if (port1->isEntangled() || port1->hasPendingActivity())
    162                 wrapper.ClearWeak();
    163         } else {
    164             ActiveDOMObject* activeDOMObject = typeInfo->toActiveDOMObject(wrapper);
    165             if (activeDOMObject && activeDOMObject->hasPendingActivity())
    166                 wrapper.ClearWeak();
    167         }
    168     }
    169 };
    170 
    171 // Implements v8::RetainedObjectInfo.
    172 class UnspecifiedGroup : public RetainedObjectInfo {
    173 public:
    174     explicit UnspecifiedGroup(void* object)
    175         : m_object(object)
    176     {
    177         ASSERT(m_object);
    178     }
    179 
    180     virtual void Dispose() { delete this; }
    181 
    182     virtual bool IsEquivalent(v8::RetainedObjectInfo* other)
    183     {
    184         ASSERT(other);
    185         return other == this || static_cast<WebCore::RetainedObjectInfo*>(other)->GetEquivalenceClass() == this->GetEquivalenceClass();
    186     }
    187 
    188     virtual intptr_t GetHash()
    189     {
    190         return reinterpret_cast<intptr_t>(m_object);
    191     }
    192 
    193     virtual const char* GetLabel()
    194     {
    195         return "Object group";
    196     }
    197 
    198     virtual intptr_t GetEquivalenceClass()
    199     {
    200         return reinterpret_cast<intptr_t>(m_object);
    201     }
    202 
    203 private:
    204     void* m_object;
    205 };
    206 
    207 class GroupId {
    208 public:
    209     GroupId() : m_type(NullType), m_groupId(0) {}
    210     GroupId(Node* node) : m_type(NodeType), m_node(node) {}
    211     GroupId(void* other) : m_type(OtherType), m_other(other) {}
    212     bool operator!() const { return m_type == NullType; }
    213     uintptr_t groupId() const { return m_groupId; }
    214     RetainedObjectInfo* createRetainedObjectInfo() const
    215     {
    216         switch (m_type) {
    217         case NullType:
    218             return 0;
    219         case NodeType:
    220             return new RetainedDOMInfo(m_node);
    221         case OtherType:
    222             return new UnspecifiedGroup(m_other);
    223         default:
    224             return 0;
    225         }
    226     }
    227 
    228 private:
    229     enum Type {
    230         NullType,
    231         NodeType,
    232         OtherType
    233     };
    234     Type m_type;
    235     union {
    236         uintptr_t m_groupId;
    237         Node* m_node;
    238         void* m_other;
    239     };
    240 };
    241 
    242 class GrouperItem {
    243 public:
    244     GrouperItem(GroupId groupId, v8::Persistent<v8::Object> wrapper) : m_groupId(groupId), m_wrapper(wrapper) {}
    245     uintptr_t groupId() const { return m_groupId.groupId(); }
    246     RetainedObjectInfo* createRetainedObjectInfo() const { return m_groupId.createRetainedObjectInfo(); }
    247     v8::Persistent<v8::Object> wrapper() const { return m_wrapper; }
    248 
    249 private:
    250     GroupId m_groupId;
    251     v8::Persistent<v8::Object> m_wrapper;
    252 };
    253 
    254 bool operator<(const GrouperItem& a, const GrouperItem& b)
    255 {
    256     return a.groupId() < b.groupId();
    257 }
    258 
    259 typedef Vector<GrouperItem> GrouperList;
    260 
    261 // If the node is in document, put it in the ownerDocument's object group.
    262 //
    263 // If an image element was created by JavaScript "new Image",
    264 // it is not in a document. However, if the load event has not
    265 // been fired (still onloading), it is treated as in the document.
    266 //
    267 // Otherwise, the node is put in an object group identified by the root
    268 // element of the tree to which it belongs.
    269 static GroupId calculateGroupId(Node* node)
    270 {
    271     if (node->inDocument() || (node->hasTagName(HTMLNames::imgTag) && !static_cast<HTMLImageElement*>(node)->haveFiredLoadEvent()))
    272         return GroupId(node->document());
    273 
    274     Node* root = node;
    275     if (node->isAttributeNode()) {
    276         root = static_cast<Attr*>(node)->ownerElement();
    277         // If the attribute has no element, no need to put it in the group,
    278         // because it'll always be a group of 1.
    279         if (!root)
    280             return GroupId();
    281     } else {
    282         while (Node* parent = root->parentNode())
    283             root = parent;
    284     }
    285 
    286     return GroupId(root);
    287 }
    288 
    289 static GroupId calculateGroupId(StyleBase* styleBase)
    290 {
    291     ASSERT(styleBase);
    292     StyleBase* current = styleBase;
    293     StyleSheet* styleSheet = 0;
    294     while (true) {
    295         // Special case: CSSStyleDeclarations might be either inline and in this case
    296         // we need to group them with their node or regular ones.
    297         if (current->isMutableStyleDeclaration()) {
    298             CSSMutableStyleDeclaration* cssMutableStyleDeclaration = static_cast<CSSMutableStyleDeclaration*>(current);
    299             if (cssMutableStyleDeclaration->isInlineStyleDeclaration()) {
    300                 ASSERT(cssMutableStyleDeclaration->parent()->isStyleSheet());
    301                 return calculateGroupId(cssMutableStyleDeclaration->node());
    302             }
    303             // Either we have no parent, or this parent is a CSSRule.
    304             ASSERT(cssMutableStyleDeclaration->parent() == cssMutableStyleDeclaration->parentRule());
    305         }
    306 
    307         if (current->isStyleSheet())
    308             styleSheet = static_cast<StyleSheet*>(current);
    309 
    310         StyleBase* parent = current->parent();
    311         if (!parent)
    312             break;
    313         current = parent;
    314     }
    315 
    316     if (styleSheet) {
    317         if (Node* ownerNode = styleSheet->ownerNode())
    318             return calculateGroupId(ownerNode);
    319         return GroupId(styleSheet);
    320     }
    321 
    322     return GroupId(current);
    323 }
    324 
    325 class GrouperVisitor : public DOMWrapperMap<Node>::Visitor, public DOMWrapperMap<void>::Visitor {
    326 public:
    327     void visitDOMWrapper(DOMDataStore* store, Node* node, v8::Persistent<v8::Object> wrapper)
    328     {
    329         GroupId groupId = calculateGroupId(node);
    330         if (!groupId)
    331             return;
    332         m_grouper.append(GrouperItem(groupId, wrapper));
    333     }
    334 
    335     void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper)
    336     {
    337         WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper);
    338 
    339         if (typeInfo->isSubclass(&V8StyleSheetList::info)) {
    340             StyleSheetList* styleSheetList = static_cast<StyleSheetList*>(object);
    341             GroupId groupId(styleSheetList);
    342             if (Document* document = styleSheetList->document())
    343                 groupId = GroupId(document);
    344             m_grouper.append(GrouperItem(groupId, wrapper));
    345 
    346         } else if (typeInfo->isSubclass(&V8DOMImplementation::info)) {
    347             DOMImplementation* domImplementation = static_cast<DOMImplementation*>(object);
    348             GroupId groupId(domImplementation);
    349             if (Document* document = domImplementation->ownerDocument())
    350                 groupId = GroupId(document);
    351             m_grouper.append(GrouperItem(groupId, wrapper));
    352 
    353         } else if (typeInfo->isSubclass(&V8StyleSheet::info) || typeInfo->isSubclass(&V8CSSRule::info)) {
    354             m_grouper.append(GrouperItem(calculateGroupId(static_cast<StyleBase*>(object)), wrapper));
    355 
    356         } else if (typeInfo->isSubclass(&V8CSSStyleDeclaration::info)) {
    357             CSSStyleDeclaration* cssStyleDeclaration = static_cast<CSSStyleDeclaration*>(object);
    358 
    359             GroupId groupId = calculateGroupId(cssStyleDeclaration);
    360             m_grouper.append(GrouperItem(groupId, wrapper));
    361 
    362             // Keep alive "dirty" primitive values (i.e. the ones that
    363             // have user-added properties) by creating implicit
    364             // references between the style declaration and the values
    365             // in it.
    366             if (cssStyleDeclaration->isMutableStyleDeclaration()) {
    367                 CSSMutableStyleDeclaration* cssMutableStyleDeclaration = static_cast<CSSMutableStyleDeclaration*>(cssStyleDeclaration);
    368                 Vector<v8::Persistent<v8::Value> > values;
    369                 values.reserveCapacity(cssMutableStyleDeclaration->length());
    370                 CSSMutableStyleDeclaration::const_iterator end = cssMutableStyleDeclaration->end();
    371                 for (CSSMutableStyleDeclaration::const_iterator it = cssMutableStyleDeclaration->begin(); it != end; ++it) {
    372                     v8::Persistent<v8::Object> value = store->domObjectMap().get(it->value());
    373                     if (!value.IsEmpty() && value->IsDirty())
    374                         values.append(value);
    375                 }
    376                 if (!values.isEmpty())
    377                     v8::V8::AddImplicitReferences(wrapper, values.data(), values.size());
    378             }
    379 
    380         } else if (typeInfo->isSubclass(&V8CSSRuleList::info)) {
    381             CSSRuleList* cssRuleList = static_cast<CSSRuleList*>(object);
    382             GroupId groupId(cssRuleList);
    383             StyleList* styleList = cssRuleList->styleList();
    384             if (styleList)
    385                 groupId = calculateGroupId(styleList);
    386             m_grouper.append(GrouperItem(groupId, wrapper));
    387         }
    388     }
    389 
    390     void applyGrouping()
    391     {
    392         // Group by sorting by the group id.
    393         std::sort(m_grouper.begin(), m_grouper.end());
    394 
    395         for (size_t i = 0; i < m_grouper.size(); ) {
    396             // Seek to the next key (or the end of the list).
    397             size_t nextKeyIndex = m_grouper.size();
    398             for (size_t j = i; j < m_grouper.size(); ++j) {
    399                 if (m_grouper[i].groupId() != m_grouper[j].groupId()) {
    400                     nextKeyIndex = j;
    401                     break;
    402                 }
    403             }
    404 
    405             ASSERT(nextKeyIndex > i);
    406 
    407             // We only care about a group if it has more than one object. If it only
    408             // has one object, it has nothing else that needs to be kept alive.
    409             if (nextKeyIndex - i <= 1) {
    410                 i = nextKeyIndex;
    411                 continue;
    412             }
    413 
    414             size_t rootIndex = i;
    415 
    416             Vector<v8::Persistent<v8::Value> > group;
    417             group.reserveCapacity(nextKeyIndex - i);
    418             for (; i < nextKeyIndex; ++i) {
    419                 v8::Persistent<v8::Value> wrapper = m_grouper[i].wrapper();
    420                 if (!wrapper.IsEmpty())
    421                     group.append(wrapper);
    422             }
    423 
    424             if (group.size() > 1)
    425                 v8::V8::AddObjectGroup(&group[0], group.size(), m_grouper[rootIndex].createRetainedObjectInfo());
    426 
    427             ASSERT(i == nextKeyIndex);
    428         }
    429     }
    430 
    431 private:
    432     GrouperList m_grouper;
    433 };
    434 
    435 // Create object groups for DOM tree nodes.
    436 void V8GCController::gcPrologue()
    437 {
    438     v8::HandleScope scope;
    439 
    440 #ifndef NDEBUG
    441     DOMObjectVisitor domObjectVisitor;
    442     visitDOMObjectsInCurrentThread(&domObjectVisitor);
    443 #endif
    444 
    445     // Run through all objects with possible pending activity making their
    446     // wrappers non weak if there is pending activity.
    447     GCPrologueVisitor prologueVisitor;
    448     visitActiveDOMObjectsInCurrentThread(&prologueVisitor);
    449 
    450     // Create object groups.
    451     GrouperVisitor grouperVisitor;
    452     visitDOMNodesInCurrentThread(&grouperVisitor);
    453     visitDOMObjectsInCurrentThread(&grouperVisitor);
    454     grouperVisitor.applyGrouping();
    455 
    456     // Clean single element cache for string conversions.
    457     lastStringImpl = 0;
    458     lastV8String.Clear();
    459 }
    460 
    461 class GCEpilogueVisitor : public DOMWrapperMap<void>::Visitor {
    462 public:
    463     void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper)
    464     {
    465         WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper);
    466         if (V8MessagePort::info.equals(typeInfo)) {
    467             MessagePort* port1 = static_cast<MessagePort*>(object);
    468             // We marked this port as reachable in GCPrologueVisitor.  Undo this now since the
    469             // port could be not reachable in the future if it gets disentangled (and also
    470             // GCPrologueVisitor expects to see all handles marked as weak).
    471             if ((!wrapper.IsWeak() && !wrapper.IsNearDeath()) || port1->hasPendingActivity())
    472                 wrapper.MakeWeak(port1, &DOMDataStore::weakActiveDOMObjectCallback);
    473         } else {
    474             ActiveDOMObject* activeDOMObject = typeInfo->toActiveDOMObject(wrapper);
    475             if (activeDOMObject && activeDOMObject->hasPendingActivity()) {
    476                 ASSERT(!wrapper.IsWeak());
    477                 // NOTE: To re-enable weak status of the active object we use
    478                 // |object| from the map and not |activeDOMObject|. The latter
    479                 // may be a different pointer (in case ActiveDOMObject is not
    480                 // the main base class of the object's class) and pointer
    481                 // identity is required by DOM map functions.
    482                 wrapper.MakeWeak(object, &DOMDataStore::weakActiveDOMObjectCallback);
    483             }
    484         }
    485     }
    486 };
    487 
    488 int V8GCController::workingSetEstimateMB = 0;
    489 
    490 namespace {
    491 
    492 int getMemoryUsageInMB()
    493 {
    494 #if PLATFORM(CHROMIUM) || PLATFORM(ANDROID)
    495     return PlatformBridge::memoryUsageMB();
    496 #else
    497     return 0;
    498 #endif
    499 }
    500 
    501 int getActualMemoryUsageInMB()
    502 {
    503 #if PLATFORM(CHROMIUM) || PLATFORM(ANDROID)
    504     return PlatformBridge::actualMemoryUsageMB();
    505 #else
    506     return 0;
    507 #endif
    508 }
    509 
    510 }  // anonymous namespace
    511 
    512 void V8GCController::gcEpilogue()
    513 {
    514     v8::HandleScope scope;
    515 
    516     // Run through all objects with pending activity making their wrappers weak
    517     // again.
    518     GCEpilogueVisitor epilogueVisitor;
    519     visitActiveDOMObjectsInCurrentThread(&epilogueVisitor);
    520 
    521     workingSetEstimateMB = getActualMemoryUsageInMB();
    522 
    523 #ifndef NDEBUG
    524     // Check all survivals are weak.
    525     DOMObjectVisitor domObjectVisitor;
    526     visitDOMObjectsInCurrentThread(&domObjectVisitor);
    527 
    528     EnsureWeakDOMNodeVisitor weakDOMNodeVisitor;
    529     visitDOMNodesInCurrentThread(&weakDOMNodeVisitor);
    530 
    531     enumerateGlobalHandles();
    532 #endif
    533 }
    534 
    535 void V8GCController::checkMemoryUsage()
    536 {
    537 #if PLATFORM(CHROMIUM) || PLATFORM(QT) && !OS(SYMBIAN)
    538     // These values are appropriate for Chromium only.
    539     const int lowUsageMB = 256;  // If memory usage is below this threshold, do not bother forcing GC.
    540     const int highUsageMB = 1024;  // If memory usage is above this threshold, force GC more aggresively.
    541     const int highUsageDeltaMB = 128;  // Delta of memory usage growth (vs. last workingSetEstimateMB) to force GC when memory usage is high.
    542 #elif PLATFORM(ANDROID)
    543     // Query the PlatformBridge for memory thresholds as these vary device to device.
    544     static const int lowUsageMB = PlatformBridge::lowMemoryUsageMB();
    545     static const int highUsageMB = PlatformBridge::highMemoryUsageMB();
    546     static const int highUsageDeltaMB = PlatformBridge::highUsageDeltaMB();
    547 #else
    548     return;
    549 #endif
    550 
    551     int memoryUsageMB = getMemoryUsageInMB();
    552     if ((memoryUsageMB > lowUsageMB && memoryUsageMB > 2 * workingSetEstimateMB) || (memoryUsageMB > highUsageMB && memoryUsageMB > workingSetEstimateMB + highUsageDeltaMB))
    553         v8::V8::LowMemoryNotification();
    554 }
    555 
    556 
    557 }  // namespace WebCore
    558