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         }
    381     }
    382 
    383     void applyGrouping()
    384     {
    385         // Group by sorting by the group id.
    386         std::sort(m_grouper.begin(), m_grouper.end());
    387 
    388         for (size_t i = 0; i < m_grouper.size(); ) {
    389             // Seek to the next key (or the end of the list).
    390             size_t nextKeyIndex = m_grouper.size();
    391             for (size_t j = i; j < m_grouper.size(); ++j) {
    392                 if (m_grouper[i].groupId() != m_grouper[j].groupId()) {
    393                     nextKeyIndex = j;
    394                     break;
    395                 }
    396             }
    397 
    398             ASSERT(nextKeyIndex > i);
    399 
    400             // We only care about a group if it has more than one object. If it only
    401             // has one object, it has nothing else that needs to be kept alive.
    402             if (nextKeyIndex - i <= 1) {
    403                 i = nextKeyIndex;
    404                 continue;
    405             }
    406 
    407             size_t rootIndex = i;
    408 
    409             Vector<v8::Persistent<v8::Value> > group;
    410             group.reserveCapacity(nextKeyIndex - i);
    411             for (; i < nextKeyIndex; ++i) {
    412                 v8::Persistent<v8::Value> wrapper = m_grouper[i].wrapper();
    413                 if (!wrapper.IsEmpty())
    414                     group.append(wrapper);
    415             }
    416 
    417             if (group.size() > 1)
    418                 v8::V8::AddObjectGroup(&group[0], group.size(), m_grouper[rootIndex].createRetainedObjectInfo());
    419 
    420             ASSERT(i == nextKeyIndex);
    421         }
    422     }
    423 
    424 private:
    425     GrouperList m_grouper;
    426 };
    427 
    428 // Create object groups for DOM tree nodes.
    429 void V8GCController::gcPrologue()
    430 {
    431     v8::HandleScope scope;
    432 
    433 #ifndef NDEBUG
    434     DOMObjectVisitor domObjectVisitor;
    435     visitDOMObjectsInCurrentThread(&domObjectVisitor);
    436 #endif
    437 
    438     // Run through all objects with possible pending activity making their
    439     // wrappers non weak if there is pending activity.
    440     GCPrologueVisitor prologueVisitor;
    441     visitActiveDOMObjectsInCurrentThread(&prologueVisitor);
    442 
    443     // Create object groups.
    444     GrouperVisitor grouperVisitor;
    445     visitDOMNodesInCurrentThread(&grouperVisitor);
    446     visitDOMObjectsInCurrentThread(&grouperVisitor);
    447     grouperVisitor.applyGrouping();
    448 
    449     // Clean single element cache for string conversions.
    450     lastStringImpl = 0;
    451     lastV8String.Clear();
    452 }
    453 
    454 class GCEpilogueVisitor : public DOMWrapperMap<void>::Visitor {
    455 public:
    456     void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper)
    457     {
    458         WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper);
    459         if (V8MessagePort::info.equals(typeInfo)) {
    460             MessagePort* port1 = static_cast<MessagePort*>(object);
    461             // We marked this port as reachable in GCPrologueVisitor.  Undo this now since the
    462             // port could be not reachable in the future if it gets disentangled (and also
    463             // GCPrologueVisitor expects to see all handles marked as weak).
    464             if ((!wrapper.IsWeak() && !wrapper.IsNearDeath()) || port1->hasPendingActivity())
    465                 wrapper.MakeWeak(port1, &DOMDataStore::weakActiveDOMObjectCallback);
    466         } else {
    467             ActiveDOMObject* activeDOMObject = typeInfo->toActiveDOMObject(wrapper);
    468             if (activeDOMObject && activeDOMObject->hasPendingActivity()) {
    469                 ASSERT(!wrapper.IsWeak());
    470                 // NOTE: To re-enable weak status of the active object we use
    471                 // |object| from the map and not |activeDOMObject|. The latter
    472                 // may be a different pointer (in case ActiveDOMObject is not
    473                 // the main base class of the object's class) and pointer
    474                 // identity is required by DOM map functions.
    475                 wrapper.MakeWeak(object, &DOMDataStore::weakActiveDOMObjectCallback);
    476             }
    477         }
    478     }
    479 };
    480 
    481 int V8GCController::workingSetEstimateMB = 0;
    482 
    483 namespace {
    484 
    485 int getMemoryUsageInMB()
    486 {
    487 #if PLATFORM(CHROMIUM) || PLATFORM(ANDROID)
    488     return PlatformBridge::memoryUsageMB();
    489 #else
    490     return 0;
    491 #endif
    492 }
    493 
    494 int getActualMemoryUsageInMB()
    495 {
    496 #if PLATFORM(CHROMIUM) || PLATFORM(ANDROID)
    497     return PlatformBridge::actualMemoryUsageMB();
    498 #else
    499     return 0;
    500 #endif
    501 }
    502 
    503 }  // anonymous namespace
    504 
    505 void V8GCController::gcEpilogue()
    506 {
    507     v8::HandleScope scope;
    508 
    509     // Run through all objects with pending activity making their wrappers weak
    510     // again.
    511     GCEpilogueVisitor epilogueVisitor;
    512     visitActiveDOMObjectsInCurrentThread(&epilogueVisitor);
    513 
    514     workingSetEstimateMB = getActualMemoryUsageInMB();
    515 
    516 #ifndef NDEBUG
    517     // Check all survivals are weak.
    518     DOMObjectVisitor domObjectVisitor;
    519     visitDOMObjectsInCurrentThread(&domObjectVisitor);
    520 
    521     EnsureWeakDOMNodeVisitor weakDOMNodeVisitor;
    522     visitDOMNodesInCurrentThread(&weakDOMNodeVisitor);
    523 
    524     enumerateGlobalHandles();
    525 #endif
    526 }
    527 
    528 void V8GCController::checkMemoryUsage()
    529 {
    530 #if PLATFORM(CHROMIUM) || PLATFORM(QT) && !OS(SYMBIAN)
    531     // These values are appropriate for Chromium only.
    532     const int lowUsageMB = 256;  // If memory usage is below this threshold, do not bother forcing GC.
    533     const int highUsageMB = 1024;  // If memory usage is above this threshold, force GC more aggresively.
    534     const int highUsageDeltaMB = 128;  // Delta of memory usage growth (vs. last workingSetEstimateMB) to force GC when memory usage is high.
    535 #elif PLATFORM(ANDROID)
    536     // Query the PlatformBridge for memory thresholds as these vary device to device.
    537     static const int lowUsageMB = PlatformBridge::lowMemoryUsageMB();
    538     static const int highUsageMB = PlatformBridge::highMemoryUsageMB();
    539     static const int highUsageDeltaMB = PlatformBridge::highUsageDeltaMB();
    540 #else
    541     return;
    542 #endif
    543 
    544     int memoryUsageMB = getMemoryUsageInMB();
    545     if ((memoryUsageMB > lowUsageMB && memoryUsageMB > 2 * workingSetEstimateMB) || (memoryUsageMB > highUsageMB && memoryUsageMB > workingSetEstimateMB + highUsageDeltaMB))
    546         v8::V8::LowMemoryNotification();
    547 }
    548 
    549 
    550 }  // namespace WebCore
    551