/* * Copyright (C) 2009 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "V8GCController.h" #include "ActiveDOMObject.h" #include "Attr.h" #include "DOMDataStore.h" #include "DOMImplementation.h" #include "HTMLImageElement.h" #include "HTMLNames.h" #include "MessagePort.h" #include "PlatformBridge.h" #include "RetainedDOMInfo.h" #include "RetainedObjectInfo.h" #include "V8Binding.h" #include "V8CSSRule.h" #include "V8CSSRuleList.h" #include "V8CSSStyleDeclaration.h" #include "V8DOMImplementation.h" #include "V8MessagePort.h" #include "V8StyleSheet.h" #include "V8StyleSheetList.h" #include "WrapperTypeInfo.h" #include <algorithm> #include <utility> #include <v8-debug.h> #include <wtf/HashMap.h> #include <wtf/StdLibExtras.h> #include <wtf/UnusedParam.h> namespace WebCore { #ifndef NDEBUG // Keeps track of global handles created (not JS wrappers // of DOM objects). Often these global handles are source // of leaks. // // If you want to let a C++ object hold a persistent handle // to a JS object, you should register the handle here to // keep track of leaks. // // When creating a persistent handle, call: // // #ifndef NDEBUG // V8GCController::registerGlobalHandle(type, host, handle); // #endif // // When releasing the handle, call: // // #ifndef NDEBUG // V8GCController::unregisterGlobalHandle(type, host, handle); // #endif // typedef HashMap<v8::Value*, GlobalHandleInfo*> GlobalHandleMap; static GlobalHandleMap& globalHandleMap() { DEFINE_STATIC_LOCAL(GlobalHandleMap, staticGlobalHandleMap, ()); return staticGlobalHandleMap; } // The function is the place to set the break point to inspect // live global handles. Leaks are often come from leaked global handles. static void enumerateGlobalHandles() { for (GlobalHandleMap::iterator it = globalHandleMap().begin(), end = globalHandleMap().end(); it != end; ++it) { GlobalHandleInfo* info = it->second; UNUSED_PARAM(info); v8::Value* handle = it->first; UNUSED_PARAM(handle); } } void V8GCController::registerGlobalHandle(GlobalHandleType type, void* host, v8::Persistent<v8::Value> handle) { ASSERT(!globalHandleMap().contains(*handle)); globalHandleMap().set(*handle, new GlobalHandleInfo(host, type)); } void V8GCController::unregisterGlobalHandle(void* host, v8::Persistent<v8::Value> handle) { ASSERT(globalHandleMap().contains(*handle)); GlobalHandleInfo* info = globalHandleMap().take(*handle); ASSERT(info->m_host == host); delete info; } #endif // ifndef NDEBUG typedef HashMap<Node*, v8::Object*> DOMNodeMap; typedef HashMap<void*, v8::Object*> DOMObjectMap; #ifndef NDEBUG class DOMObjectVisitor : public DOMWrapperMap<void>::Visitor { public: void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper) { WrapperTypeInfo* type = V8DOMWrapper::domWrapperType(wrapper); UNUSED_PARAM(type); UNUSED_PARAM(object); } }; class EnsureWeakDOMNodeVisitor : public DOMWrapperMap<Node>::Visitor { public: void visitDOMWrapper(DOMDataStore* store, Node* object, v8::Persistent<v8::Object> wrapper) { UNUSED_PARAM(object); ASSERT(wrapper.IsWeak()); } }; #endif // NDEBUG class GCPrologueVisitor : public DOMWrapperMap<void>::Visitor { public: void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper) { WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper); // Additional handling of message port ensuring that entangled ports also // have their wrappers entangled. This should ideally be handled when the // ports are actually entangled in MessagePort::entangle, but to avoid // forking MessagePort.* this is postponed to GC time. Having this postponed // has the drawback that the wrappers are "entangled/unentangled" for each // GC even though their entaglement most likely is still the same. if (V8MessagePort::info.equals(typeInfo)) { // Mark each port as in-use if it's entangled. For simplicity's sake, we assume all ports are remotely entangled, // since the Chromium port implementation can't tell the difference. MessagePort* port1 = static_cast<MessagePort*>(object); if (port1->isEntangled() || port1->hasPendingActivity()) wrapper.ClearWeak(); } else { ActiveDOMObject* activeDOMObject = typeInfo->toActiveDOMObject(wrapper); if (activeDOMObject && activeDOMObject->hasPendingActivity()) wrapper.ClearWeak(); } } }; // Implements v8::RetainedObjectInfo. class UnspecifiedGroup : public RetainedObjectInfo { public: explicit UnspecifiedGroup(void* object) : m_object(object) { ASSERT(m_object); } virtual void Dispose() { delete this; } virtual bool IsEquivalent(v8::RetainedObjectInfo* other) { ASSERT(other); return other == this || static_cast<WebCore::RetainedObjectInfo*>(other)->GetEquivalenceClass() == this->GetEquivalenceClass(); } virtual intptr_t GetHash() { return reinterpret_cast<intptr_t>(m_object); } virtual const char* GetLabel() { return "Object group"; } virtual intptr_t GetEquivalenceClass() { return reinterpret_cast<intptr_t>(m_object); } private: void* m_object; }; class GroupId { public: GroupId() : m_type(NullType), m_groupId(0) {} GroupId(Node* node) : m_type(NodeType), m_node(node) {} GroupId(void* other) : m_type(OtherType), m_other(other) {} bool operator!() const { return m_type == NullType; } uintptr_t groupId() const { return m_groupId; } RetainedObjectInfo* createRetainedObjectInfo() const { switch (m_type) { case NullType: return 0; case NodeType: return new RetainedDOMInfo(m_node); case OtherType: return new UnspecifiedGroup(m_other); default: return 0; } } private: enum Type { NullType, NodeType, OtherType }; Type m_type; union { uintptr_t m_groupId; Node* m_node; void* m_other; }; }; class GrouperItem { public: GrouperItem(GroupId groupId, v8::Persistent<v8::Object> wrapper) : m_groupId(groupId), m_wrapper(wrapper) {} uintptr_t groupId() const { return m_groupId.groupId(); } RetainedObjectInfo* createRetainedObjectInfo() const { return m_groupId.createRetainedObjectInfo(); } v8::Persistent<v8::Object> wrapper() const { return m_wrapper; } private: GroupId m_groupId; v8::Persistent<v8::Object> m_wrapper; }; bool operator<(const GrouperItem& a, const GrouperItem& b) { return a.groupId() < b.groupId(); } typedef Vector<GrouperItem> GrouperList; // If the node is in document, put it in the ownerDocument's object group. // // If an image element was created by JavaScript "new Image", // it is not in a document. However, if the load event has not // been fired (still onloading), it is treated as in the document. // // Otherwise, the node is put in an object group identified by the root // element of the tree to which it belongs. static GroupId calculateGroupId(Node* node) { if (node->inDocument() || (node->hasTagName(HTMLNames::imgTag) && !static_cast<HTMLImageElement*>(node)->haveFiredLoadEvent())) return GroupId(node->document()); Node* root = node; if (node->isAttributeNode()) { root = static_cast<Attr*>(node)->ownerElement(); // If the attribute has no element, no need to put it in the group, // because it'll always be a group of 1. if (!root) return GroupId(); } else { while (Node* parent = root->parentNode()) root = parent; } return GroupId(root); } static GroupId calculateGroupId(StyleBase* styleBase) { ASSERT(styleBase); StyleBase* current = styleBase; StyleSheet* styleSheet = 0; while (true) { // Special case: CSSStyleDeclarations might be either inline and in this case // we need to group them with their node or regular ones. if (current->isMutableStyleDeclaration()) { CSSMutableStyleDeclaration* cssMutableStyleDeclaration = static_cast<CSSMutableStyleDeclaration*>(current); if (cssMutableStyleDeclaration->isInlineStyleDeclaration()) { ASSERT(cssMutableStyleDeclaration->parent()->isStyleSheet()); return calculateGroupId(cssMutableStyleDeclaration->node()); } // Either we have no parent, or this parent is a CSSRule. ASSERT(cssMutableStyleDeclaration->parent() == cssMutableStyleDeclaration->parentRule()); } if (current->isStyleSheet()) styleSheet = static_cast<StyleSheet*>(current); StyleBase* parent = current->parent(); if (!parent) break; current = parent; } if (styleSheet) { if (Node* ownerNode = styleSheet->ownerNode()) return calculateGroupId(ownerNode); return GroupId(styleSheet); } return GroupId(current); } class GrouperVisitor : public DOMWrapperMap<Node>::Visitor, public DOMWrapperMap<void>::Visitor { public: void visitDOMWrapper(DOMDataStore* store, Node* node, v8::Persistent<v8::Object> wrapper) { GroupId groupId = calculateGroupId(node); if (!groupId) return; m_grouper.append(GrouperItem(groupId, wrapper)); } void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper) { WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper); if (typeInfo->isSubclass(&V8StyleSheetList::info)) { StyleSheetList* styleSheetList = static_cast<StyleSheetList*>(object); GroupId groupId(styleSheetList); if (Document* document = styleSheetList->document()) groupId = GroupId(document); m_grouper.append(GrouperItem(groupId, wrapper)); } else if (typeInfo->isSubclass(&V8DOMImplementation::info)) { DOMImplementation* domImplementation = static_cast<DOMImplementation*>(object); GroupId groupId(domImplementation); if (Document* document = domImplementation->ownerDocument()) groupId = GroupId(document); m_grouper.append(GrouperItem(groupId, wrapper)); } else if (typeInfo->isSubclass(&V8StyleSheet::info) || typeInfo->isSubclass(&V8CSSRule::info)) { m_grouper.append(GrouperItem(calculateGroupId(static_cast<StyleBase*>(object)), wrapper)); } else if (typeInfo->isSubclass(&V8CSSStyleDeclaration::info)) { CSSStyleDeclaration* cssStyleDeclaration = static_cast<CSSStyleDeclaration*>(object); GroupId groupId = calculateGroupId(cssStyleDeclaration); m_grouper.append(GrouperItem(groupId, wrapper)); // Keep alive "dirty" primitive values (i.e. the ones that // have user-added properties) by creating implicit // references between the style declaration and the values // in it. if (cssStyleDeclaration->isMutableStyleDeclaration()) { CSSMutableStyleDeclaration* cssMutableStyleDeclaration = static_cast<CSSMutableStyleDeclaration*>(cssStyleDeclaration); Vector<v8::Persistent<v8::Value> > values; values.reserveCapacity(cssMutableStyleDeclaration->length()); CSSMutableStyleDeclaration::const_iterator end = cssMutableStyleDeclaration->end(); for (CSSMutableStyleDeclaration::const_iterator it = cssMutableStyleDeclaration->begin(); it != end; ++it) { v8::Persistent<v8::Object> value = store->domObjectMap().get(it->value()); if (!value.IsEmpty() && value->IsDirty()) values.append(value); } if (!values.isEmpty()) v8::V8::AddImplicitReferences(wrapper, values.data(), values.size()); } } } void applyGrouping() { // Group by sorting by the group id. std::sort(m_grouper.begin(), m_grouper.end()); for (size_t i = 0; i < m_grouper.size(); ) { // Seek to the next key (or the end of the list). size_t nextKeyIndex = m_grouper.size(); for (size_t j = i; j < m_grouper.size(); ++j) { if (m_grouper[i].groupId() != m_grouper[j].groupId()) { nextKeyIndex = j; break; } } ASSERT(nextKeyIndex > i); // We only care about a group if it has more than one object. If it only // has one object, it has nothing else that needs to be kept alive. if (nextKeyIndex - i <= 1) { i = nextKeyIndex; continue; } size_t rootIndex = i; Vector<v8::Persistent<v8::Value> > group; group.reserveCapacity(nextKeyIndex - i); for (; i < nextKeyIndex; ++i) { v8::Persistent<v8::Value> wrapper = m_grouper[i].wrapper(); if (!wrapper.IsEmpty()) group.append(wrapper); } if (group.size() > 1) v8::V8::AddObjectGroup(&group[0], group.size(), m_grouper[rootIndex].createRetainedObjectInfo()); ASSERT(i == nextKeyIndex); } } private: GrouperList m_grouper; }; // Create object groups for DOM tree nodes. void V8GCController::gcPrologue() { v8::HandleScope scope; #ifndef NDEBUG DOMObjectVisitor domObjectVisitor; visitDOMObjectsInCurrentThread(&domObjectVisitor); #endif // Run through all objects with possible pending activity making their // wrappers non weak if there is pending activity. GCPrologueVisitor prologueVisitor; visitActiveDOMObjectsInCurrentThread(&prologueVisitor); // Create object groups. GrouperVisitor grouperVisitor; visitDOMNodesInCurrentThread(&grouperVisitor); visitDOMObjectsInCurrentThread(&grouperVisitor); grouperVisitor.applyGrouping(); // Clean single element cache for string conversions. lastStringImpl = 0; lastV8String.Clear(); } class GCEpilogueVisitor : public DOMWrapperMap<void>::Visitor { public: void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper) { WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper); if (V8MessagePort::info.equals(typeInfo)) { MessagePort* port1 = static_cast<MessagePort*>(object); // We marked this port as reachable in GCPrologueVisitor. Undo this now since the // port could be not reachable in the future if it gets disentangled (and also // GCPrologueVisitor expects to see all handles marked as weak). if ((!wrapper.IsWeak() && !wrapper.IsNearDeath()) || port1->hasPendingActivity()) wrapper.MakeWeak(port1, &DOMDataStore::weakActiveDOMObjectCallback); } else { ActiveDOMObject* activeDOMObject = typeInfo->toActiveDOMObject(wrapper); if (activeDOMObject && activeDOMObject->hasPendingActivity()) { ASSERT(!wrapper.IsWeak()); // NOTE: To re-enable weak status of the active object we use // |object| from the map and not |activeDOMObject|. The latter // may be a different pointer (in case ActiveDOMObject is not // the main base class of the object's class) and pointer // identity is required by DOM map functions. wrapper.MakeWeak(object, &DOMDataStore::weakActiveDOMObjectCallback); } } } }; int V8GCController::workingSetEstimateMB = 0; namespace { int getMemoryUsageInMB() { #if PLATFORM(CHROMIUM) || PLATFORM(ANDROID) return PlatformBridge::memoryUsageMB(); #else return 0; #endif } int getActualMemoryUsageInMB() { #if PLATFORM(CHROMIUM) || PLATFORM(ANDROID) return PlatformBridge::actualMemoryUsageMB(); #else return 0; #endif } } // anonymous namespace void V8GCController::gcEpilogue() { v8::HandleScope scope; // Run through all objects with pending activity making their wrappers weak // again. GCEpilogueVisitor epilogueVisitor; visitActiveDOMObjectsInCurrentThread(&epilogueVisitor); workingSetEstimateMB = getActualMemoryUsageInMB(); #ifndef NDEBUG // Check all survivals are weak. DOMObjectVisitor domObjectVisitor; visitDOMObjectsInCurrentThread(&domObjectVisitor); EnsureWeakDOMNodeVisitor weakDOMNodeVisitor; visitDOMNodesInCurrentThread(&weakDOMNodeVisitor); enumerateGlobalHandles(); #endif } void V8GCController::checkMemoryUsage() { #if PLATFORM(CHROMIUM) || PLATFORM(QT) && !OS(SYMBIAN) // These values are appropriate for Chromium only. const int lowUsageMB = 256; // If memory usage is below this threshold, do not bother forcing GC. const int highUsageMB = 1024; // If memory usage is above this threshold, force GC more aggresively. const int highUsageDeltaMB = 128; // Delta of memory usage growth (vs. last workingSetEstimateMB) to force GC when memory usage is high. #elif PLATFORM(ANDROID) // Query the PlatformBridge for memory thresholds as these vary device to device. static const int lowUsageMB = PlatformBridge::lowMemoryUsageMB(); static const int highUsageMB = PlatformBridge::highMemoryUsageMB(); static const int highUsageDeltaMB = PlatformBridge::highUsageDeltaMB(); #else return; #endif int memoryUsageMB = getMemoryUsageInMB(); if ((memoryUsageMB > lowUsageMB && memoryUsageMB > 2 * workingSetEstimateMB) || (memoryUsageMB > highUsageMB && memoryUsageMB > workingSetEstimateMB + highUsageDeltaMB)) v8::V8::LowMemoryNotification(); } } // namespace WebCore