• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
globalHandleMap()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.
enumerateGlobalHandles()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 
registerGlobalHandle(GlobalHandleType type,void * host,v8::Persistent<v8::Value> handle)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 
unregisterGlobalHandle(void * host,v8::Persistent<v8::Value> handle)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:
visitDOMWrapper(DOMDataStore * store,void * object,v8::Persistent<v8::Object> wrapper)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:
visitDOMWrapper(DOMDataStore * store,Node * object,v8::Persistent<v8::Object> wrapper)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:
visitDOMWrapper(DOMDataStore * store,void * object,v8::Persistent<v8::Object> wrapper)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:
UnspecifiedGroup(void * object)174     explicit UnspecifiedGroup(void* object)
175         : m_object(object)
176     {
177         ASSERT(m_object);
178     }
179 
Dispose()180     virtual void Dispose() { delete this; }
181 
IsEquivalent(v8::RetainedObjectInfo * other)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 
GetHash()188     virtual intptr_t GetHash()
189     {
190         return reinterpret_cast<intptr_t>(m_object);
191     }
192 
GetLabel()193     virtual const char* GetLabel()
194     {
195         return "Object group";
196     }
197 
GetEquivalenceClass()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:
GroupId()209     GroupId() : m_type(NullType), m_groupId(0) {}
GroupId(Node * node)210     GroupId(Node* node) : m_type(NodeType), m_node(node) {}
GroupId(void * other)211     GroupId(void* other) : m_type(OtherType), m_other(other) {}
operator !() const212     bool operator!() const { return m_type == NullType; }
groupId() const213     uintptr_t groupId() const { return m_groupId; }
createRetainedObjectInfo() const214     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:
GrouperItem(GroupId groupId,v8::Persistent<v8::Object> wrapper)244     GrouperItem(GroupId groupId, v8::Persistent<v8::Object> wrapper) : m_groupId(groupId), m_wrapper(wrapper) {}
groupId() const245     uintptr_t groupId() const { return m_groupId.groupId(); }
createRetainedObjectInfo() const246     RetainedObjectInfo* createRetainedObjectInfo() const { return m_groupId.createRetainedObjectInfo(); }
wrapper() const247     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 
operator <(const GrouperItem & a,const GrouperItem & b)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.
calculateGroupId(Node * node)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 
calculateGroupId(StyleBase * styleBase)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:
visitDOMWrapper(DOMDataStore * store,Node * node,v8::Persistent<v8::Object> wrapper)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 
visitDOMWrapper(DOMDataStore * store,void * object,v8::Persistent<v8::Object> wrapper)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 
applyGrouping()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.
gcPrologue()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:
visitDOMWrapper(DOMDataStore * store,void * object,v8::Persistent<v8::Object> wrapper)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 
getMemoryUsageInMB()492 int getMemoryUsageInMB()
493 {
494 #if PLATFORM(CHROMIUM) || PLATFORM(ANDROID)
495     return PlatformBridge::memoryUsageMB();
496 #else
497     return 0;
498 #endif
499 }
500 
getActualMemoryUsageInMB()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 
gcEpilogue()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 
checkMemoryUsage()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