|  | // Copyright (c) 2006, 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. | 
|  |  | 
|  | // --- | 
|  | // Author: Sanjay Ghemawat | 
|  | //         Maxim Lifantsev (refactoring) | 
|  | // | 
|  |  | 
|  | #ifndef BASE_HEAP_PROFILE_TABLE_H_ | 
|  | #define BASE_HEAP_PROFILE_TABLE_H_ | 
|  |  | 
|  | #include "addressmap-inl.h" | 
|  | #include "base/basictypes.h" | 
|  | #include "base/logging.h"   // for RawFD | 
|  | #include "heap-profile-stats.h" | 
|  |  | 
|  | #if defined(TYPE_PROFILING) | 
|  | #include <gperftools/type_profiler_map.h> | 
|  | #endif  // defined(TYPE_PROFILING) | 
|  |  | 
|  | // Table to maintain a heap profile data inside, | 
|  | // i.e. the set of currently active heap memory allocations. | 
|  | // thread-unsafe and non-reentrant code: | 
|  | // each instance object must be used by one thread | 
|  | // at a time w/o self-recursion. | 
|  | // | 
|  | // TODO(maxim): add a unittest for this class. | 
|  | class HeapProfileTable { | 
|  | public: | 
|  |  | 
|  | // Extension to be used for heap pforile files. | 
|  | static const char kFileExt[]; | 
|  |  | 
|  | // Longest stack trace we record. | 
|  | static const int kMaxStackDepth = 32; | 
|  |  | 
|  | // data types ---------------------------- | 
|  |  | 
|  | // Profile stats. | 
|  | typedef HeapProfileStats Stats; | 
|  |  | 
|  | // Possible marks for MarkCurrentAllocations and MarkUnmarkedAllocations. New | 
|  | // allocations are marked with UNMARKED by default. | 
|  | enum AllocationMark { | 
|  | UNMARKED = 0, | 
|  | MARK_ONE, | 
|  | MARK_TWO, | 
|  | MARK_THREE | 
|  | }; | 
|  |  | 
|  | // Info we can return about an allocation. | 
|  | struct AllocInfo { | 
|  | size_t object_size;  // size of the allocation | 
|  | const void* const* call_stack;  // call stack that made the allocation call | 
|  | int stack_depth;  // depth of call_stack | 
|  | bool live; | 
|  | bool ignored; | 
|  | }; | 
|  |  | 
|  | // Info we return about an allocation context. | 
|  | // An allocation context is a unique caller stack trace | 
|  | // of an allocation operation. | 
|  | struct AllocContextInfo : public Stats { | 
|  | int stack_depth;                // Depth of stack trace | 
|  | const void* const* call_stack;  // Stack trace | 
|  | }; | 
|  |  | 
|  | // Memory (de)allocator interface we'll use. | 
|  | typedef void* (*Allocator)(size_t size); | 
|  | typedef void  (*DeAllocator)(void* ptr); | 
|  |  | 
|  | // interface --------------------------- | 
|  |  | 
|  | HeapProfileTable(Allocator alloc, DeAllocator dealloc, bool profile_mmap); | 
|  | ~HeapProfileTable(); | 
|  |  | 
|  | // Collect the stack trace for the function that asked to do the | 
|  | // allocation for passing to RecordAlloc() below. | 
|  | // | 
|  | // The stack trace is stored in 'stack'. The stack depth is returned. | 
|  | // | 
|  | // 'skip_count' gives the number of stack frames between this call | 
|  | // and the memory allocation function. | 
|  | static int GetCallerStackTrace(int skip_count, void* stack[kMaxStackDepth]); | 
|  |  | 
|  | // Record an allocation at 'ptr' of 'bytes' bytes.  'stack_depth' | 
|  | // and 'call_stack' identifying the function that requested the | 
|  | // allocation. They can be generated using GetCallerStackTrace() above. | 
|  | void RecordAlloc(const void* ptr, size_t bytes, | 
|  | int stack_depth, const void* const call_stack[]); | 
|  |  | 
|  | // Record the deallocation of memory at 'ptr'. | 
|  | void RecordFree(const void* ptr); | 
|  |  | 
|  | // Return true iff we have recorded an allocation at 'ptr'. | 
|  | // If yes, fill *object_size with the allocation byte size. | 
|  | bool FindAlloc(const void* ptr, size_t* object_size) const; | 
|  | // Same as FindAlloc, but fills all of *info. | 
|  | bool FindAllocDetails(const void* ptr, AllocInfo* info) const; | 
|  |  | 
|  | // Return true iff "ptr" points into a recorded allocation | 
|  | // If yes, fill *object_ptr with the actual allocation address | 
|  | // and *object_size with the allocation byte size. | 
|  | // max_size specifies largest currently possible allocation size. | 
|  | bool FindInsideAlloc(const void* ptr, size_t max_size, | 
|  | const void** object_ptr, size_t* object_size) const; | 
|  |  | 
|  | // If "ptr" points to a recorded allocation and it's not marked as live | 
|  | // mark it as live and return true. Else return false. | 
|  | // All allocations start as non-live. | 
|  | bool MarkAsLive(const void* ptr); | 
|  |  | 
|  | // If "ptr" points to a recorded allocation, mark it as "ignored". | 
|  | // Ignored objects are treated like other objects, except that they | 
|  | // are skipped in heap checking reports. | 
|  | void MarkAsIgnored(const void* ptr); | 
|  |  | 
|  | // Mark all currently known allocations with the given AllocationMark. | 
|  | void MarkCurrentAllocations(AllocationMark mark); | 
|  |  | 
|  | // Mark all unmarked (i.e. marked with AllocationMark::UNMARKED) with the | 
|  | // given mark. | 
|  | void MarkUnmarkedAllocations(AllocationMark mark); | 
|  |  | 
|  | // Return current total (de)allocation statistics.  It doesn't contain | 
|  | // mmap'ed regions. | 
|  | const Stats& total() const { return total_; } | 
|  |  | 
|  | // Allocation data iteration callback: gets passed object pointer and | 
|  | // fully-filled AllocInfo. | 
|  | typedef void (*AllocIterator)(const void* ptr, const AllocInfo& info); | 
|  |  | 
|  | // Iterate over the allocation profile data calling "callback" | 
|  | // for every allocation. | 
|  | void IterateAllocs(AllocIterator callback) const { | 
|  | address_map_->Iterate(MapArgsAllocIterator, callback); | 
|  | } | 
|  |  | 
|  | // Callback for iterating through addresses of all allocated objects. Accepts | 
|  | // pointer to user data and object pointer. | 
|  | typedef void (*AddressIterator)(void* data, const void* ptr); | 
|  |  | 
|  | // Iterate over the addresses of all allocated objects. | 
|  | void IterateAllocationAddresses(AddressIterator, void* data); | 
|  |  | 
|  | // Allocation context profile data iteration callback | 
|  | typedef void (*AllocContextIterator)(const AllocContextInfo& info); | 
|  |  | 
|  | // Iterate over the allocation context profile data calling "callback" | 
|  | // for every allocation context. Allocation contexts are ordered by the | 
|  | // size of allocated space. | 
|  | void IterateOrderedAllocContexts(AllocContextIterator callback) const; | 
|  |  | 
|  | // Fill profile data into buffer 'buf' of size 'size' | 
|  | // and return the actual size occupied by the dump in 'buf'. | 
|  | // The profile buckets are dumped in the decreasing order | 
|  | // of currently allocated bytes. | 
|  | // We do not provision for 0-terminating 'buf'. | 
|  | int FillOrderedProfile(char buf[], int size) const; | 
|  |  | 
|  | // Cleanup any old profile files matching prefix + ".*" + kFileExt. | 
|  | static void CleanupOldProfiles(const char* prefix); | 
|  |  | 
|  | // Return a snapshot of the current contents of *this. | 
|  | // Caller must call ReleaseSnapshot() on result when no longer needed. | 
|  | // The result is only valid while this exists and until | 
|  | // the snapshot is discarded by calling ReleaseSnapshot(). | 
|  | class Snapshot; | 
|  | Snapshot* TakeSnapshot(); | 
|  |  | 
|  | // Release a previously taken snapshot.  snapshot must not | 
|  | // be used after this call. | 
|  | void ReleaseSnapshot(Snapshot* snapshot); | 
|  |  | 
|  | // Return a snapshot of every non-live, non-ignored object in *this. | 
|  | // If "base" is non-NULL, skip any objects present in "base". | 
|  | // As a side-effect, clears the "live" bit on every live object in *this. | 
|  | // Caller must call ReleaseSnapshot() on result when no longer needed. | 
|  | Snapshot* NonLiveSnapshot(Snapshot* base); | 
|  |  | 
|  | // Dump a list of allocations marked as "live" along with their creation | 
|  | // stack traces and sizes to a file named |file_name|. Together with | 
|  | // MarkCurrentAllocatiosn and MarkUnmarkedAllocations this can be used | 
|  | // to find objects that are created in a certain time span: | 
|  | //   1. Invoke MarkCurrentAllocations(MARK_ONE) to mark the start of the | 
|  | //      timespan. | 
|  | //   2. Perform whatever action you suspect allocates memory that is not | 
|  | //      correctly freed. | 
|  | //   3. Invoke MarkUnmarkedAllocations(MARK_TWO). | 
|  | //   4. Perform whatever action is supposed to free the memory again. New | 
|  | //      allocations are not marked. So all allocations that are marked as | 
|  | //      "live" where created during step 2. | 
|  | //   5. Invoke DumpMarkedObjects(MARK_TWO) to get the list of allocations that | 
|  | //      were created during step 2, but survived step 4. | 
|  | // | 
|  | // Note that this functionality cannot be used if the HeapProfileTable is | 
|  | // used for leak checking (using HeapLeakChecker). | 
|  | void DumpMarkedObjects(AllocationMark mark, const char* file_name); | 
|  |  | 
|  | #if defined(TYPE_PROFILING) | 
|  | void DumpTypeStatistics(const char* file_name) const; | 
|  | #endif  // defined(TYPE_PROFILING) | 
|  |  | 
|  | private: | 
|  | friend class DeepHeapProfile; | 
|  |  | 
|  | // data types ---------------------------- | 
|  |  | 
|  | // Hash table bucket to hold (de)allocation stats | 
|  | // for a given allocation call stack trace. | 
|  | typedef HeapProfileBucket Bucket; | 
|  |  | 
|  | // Info stored in the address map | 
|  | struct AllocValue { | 
|  | // Access to the stack-trace bucket | 
|  | Bucket* bucket() const { | 
|  | return reinterpret_cast<Bucket*>(bucket_rep & ~uintptr_t(kMask)); | 
|  | } | 
|  | // This also does set_live(false). | 
|  | void set_bucket(Bucket* b) { bucket_rep = reinterpret_cast<uintptr_t>(b); } | 
|  | size_t  bytes;   // Number of bytes in this allocation | 
|  |  | 
|  | // Access to the allocation liveness flag (for leak checking) | 
|  | bool live() const { return bucket_rep & kLive; } | 
|  | void set_live(bool l) { | 
|  | bucket_rep = (bucket_rep & ~uintptr_t(kLive)) | (l ? kLive : 0); | 
|  | } | 
|  |  | 
|  | // Should this allocation be ignored if it looks like a leak? | 
|  | bool ignore() const { return bucket_rep & kIgnore; } | 
|  | void set_ignore(bool r) { | 
|  | bucket_rep = (bucket_rep & ~uintptr_t(kIgnore)) | (r ? kIgnore : 0); | 
|  | } | 
|  | AllocationMark mark() const { | 
|  | return static_cast<AllocationMark>(bucket_rep & uintptr_t(kMask)); | 
|  | } | 
|  | void set_mark(AllocationMark mark) { | 
|  | bucket_rep = (bucket_rep & ~uintptr_t(kMask)) | uintptr_t(mark); | 
|  | } | 
|  |  | 
|  | private: | 
|  | // We store a few bits in the bottom bits of bucket_rep. | 
|  | // (Alignment is at least four, so we have at least two bits.) | 
|  | static const int kLive = 1; | 
|  | static const int kIgnore = 2; | 
|  | static const int kMask = kLive | kIgnore; | 
|  |  | 
|  | uintptr_t bucket_rep; | 
|  | }; | 
|  |  | 
|  | // helper for FindInsideAlloc | 
|  | static size_t AllocValueSize(const AllocValue& v) { return v.bytes; } | 
|  |  | 
|  | typedef AddressMap<AllocValue> AllocationMap; | 
|  |  | 
|  | // Arguments that need to be passed DumpBucketIterator callback below. | 
|  | struct BufferArgs { | 
|  | BufferArgs(char* buf_arg, int buflen_arg, int bufsize_arg) | 
|  | : buf(buf_arg), | 
|  | buflen(buflen_arg), | 
|  | bufsize(bufsize_arg) { | 
|  | } | 
|  |  | 
|  | char* buf; | 
|  | int buflen; | 
|  | int bufsize; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(BufferArgs); | 
|  | }; | 
|  |  | 
|  | // Arguments that need to be passed DumpNonLiveIterator callback below. | 
|  | struct DumpArgs { | 
|  | DumpArgs(RawFD fd_arg, Stats* profile_stats_arg) | 
|  | : fd(fd_arg), | 
|  | profile_stats(profile_stats_arg) { | 
|  | } | 
|  |  | 
|  | RawFD fd;  // file to write to | 
|  | Stats* profile_stats;  // stats to update (may be NULL) | 
|  | }; | 
|  |  | 
|  | // Arguments that need to be passed DumpMarkedIterator callback below. | 
|  | struct DumpMarkedArgs { | 
|  | DumpMarkedArgs(RawFD fd_arg, AllocationMark mark_arg) | 
|  | : fd(fd_arg), | 
|  | mark(mark_arg) { | 
|  | } | 
|  |  | 
|  | RawFD fd;  // file to write to. | 
|  | AllocationMark mark;  // The mark of the allocations to process. | 
|  | }; | 
|  |  | 
|  | // Arguments that need to be passed MarkIterator callback below. | 
|  | struct MarkArgs { | 
|  | MarkArgs(AllocationMark mark_arg, bool mark_all_arg) | 
|  | : mark(mark_arg), | 
|  | mark_all(mark_all_arg) { | 
|  | } | 
|  |  | 
|  | AllocationMark mark;  // The mark to put on allocations. | 
|  | bool mark_all;  // True if all allocations should be marked. Otherwise just | 
|  | // mark unmarked allocations. | 
|  | }; | 
|  |  | 
|  | #if defined(TYPE_PROFILING) | 
|  | struct TypeCount { | 
|  | TypeCount(size_t bytes_arg, unsigned int objects_arg) | 
|  | : bytes(bytes_arg), | 
|  | objects(objects_arg) { | 
|  | } | 
|  |  | 
|  | size_t bytes; | 
|  | unsigned int objects; | 
|  | }; | 
|  | #endif  // defined(TYPE_PROFILING) | 
|  |  | 
|  | struct AllocationAddressIteratorArgs { | 
|  | AllocationAddressIteratorArgs(AddressIterator callback_arg, void* data_arg) | 
|  | : callback(callback_arg), | 
|  | data(data_arg) { | 
|  | } | 
|  |  | 
|  | AddressIterator callback; | 
|  | void* data; | 
|  | }; | 
|  |  | 
|  | // helpers ---------------------------- | 
|  |  | 
|  | // Unparse bucket b and print its portion of profile dump into buf. | 
|  | // We return the amount of space in buf that we use.  We start printing | 
|  | // at buf + buflen, and promise not to go beyond buf + bufsize. | 
|  | // We do not provision for 0-terminating 'buf'. | 
|  | // | 
|  | // If profile_stats is non-NULL, we update *profile_stats by | 
|  | // counting bucket b. | 
|  | // | 
|  | // "extra" is appended to the unparsed bucket.  Typically it is empty, | 
|  | // but may be set to something like " heapprofile" for the total | 
|  | // bucket to indicate the type of the profile. | 
|  | static int UnparseBucket(const Bucket& b, | 
|  | char* buf, int buflen, int bufsize, | 
|  | const char* extra, | 
|  | Stats* profile_stats); | 
|  |  | 
|  | // Get the bucket for the caller stack trace 'key' of depth 'depth' | 
|  | // creating the bucket if needed. | 
|  | Bucket* GetBucket(int depth, const void* const key[]); | 
|  |  | 
|  | // Helper for IterateAllocs to do callback signature conversion | 
|  | // from AllocationMap::Iterate to AllocIterator. | 
|  | static void MapArgsAllocIterator(const void* ptr, AllocValue* v, | 
|  | AllocIterator callback) { | 
|  | AllocInfo info; | 
|  | info.object_size = v->bytes; | 
|  | info.call_stack = v->bucket()->stack; | 
|  | info.stack_depth = v->bucket()->depth; | 
|  | info.live = v->live(); | 
|  | info.ignored = v->ignore(); | 
|  | callback(ptr, info); | 
|  | } | 
|  |  | 
|  | // Helper to dump a bucket. | 
|  | inline static void DumpBucketIterator(const Bucket* bucket, | 
|  | BufferArgs* args); | 
|  |  | 
|  | // Helper for IterateAllocationAddresses. | 
|  | inline static void AllocationAddressesIterator( | 
|  | const void* ptr, | 
|  | AllocValue* v, | 
|  | const AllocationAddressIteratorArgs& args); | 
|  |  | 
|  | // Helper for MarkCurrentAllocations and MarkUnmarkedAllocations. | 
|  | inline static void MarkIterator(const void* ptr, AllocValue* v, | 
|  | const MarkArgs& args); | 
|  |  | 
|  | // Helper for DumpNonLiveProfile to do object-granularity | 
|  | // heap profile dumping. It gets passed to AllocationMap::Iterate. | 
|  | inline static void DumpNonLiveIterator(const void* ptr, AllocValue* v, | 
|  | const DumpArgs& args); | 
|  |  | 
|  | // Helper for DumpMarkedObjects to dump all allocations with a given mark. It | 
|  | // gets passed to AllocationMap::Iterate. | 
|  | inline static void DumpMarkedIterator(const void* ptr, AllocValue* v, | 
|  | const DumpMarkedArgs& args); | 
|  |  | 
|  | #if defined(TYPE_PROFILING) | 
|  | inline static void TallyTypesItererator(const void* ptr, | 
|  | AllocValue* value, | 
|  | AddressMap<TypeCount>* type_size_map); | 
|  |  | 
|  | inline static void DumpTypesIterator(const void* ptr, | 
|  | TypeCount* size, | 
|  | const DumpArgs& args); | 
|  | #endif  // defined(TYPE_PROFILING) | 
|  |  | 
|  | // Helper for IterateOrderedAllocContexts and FillOrderedProfile. | 
|  | // Creates a sorted list of Buckets whose length is num_buckets_. | 
|  | // The caller is responsible for deallocating the returned list. | 
|  | Bucket** MakeSortedBucketList() const; | 
|  |  | 
|  | // Helper for TakeSnapshot.  Saves object to snapshot. | 
|  | static void AddToSnapshot(const void* ptr, AllocValue* v, Snapshot* s); | 
|  |  | 
|  | // Arguments passed to AddIfNonLive | 
|  | struct AddNonLiveArgs { | 
|  | Snapshot* dest; | 
|  | Snapshot* base; | 
|  | }; | 
|  |  | 
|  | // Helper for NonLiveSnapshot.  Adds the object to the destination | 
|  | // snapshot if it is non-live. | 
|  | static void AddIfNonLive(const void* ptr, AllocValue* v, | 
|  | AddNonLiveArgs* arg); | 
|  |  | 
|  | // Write contents of "*allocations" as a heap profile to | 
|  | // "file_name".  "total" must contain the total of all entries in | 
|  | // "*allocations". | 
|  | static bool WriteProfile(const char* file_name, | 
|  | const Bucket& total, | 
|  | AllocationMap* allocations); | 
|  |  | 
|  | // data ---------------------------- | 
|  |  | 
|  | // Memory (de)allocator that we use. | 
|  | Allocator alloc_; | 
|  | DeAllocator dealloc_; | 
|  |  | 
|  | // Overall profile stats; we use only the Stats part, | 
|  | // but make it a Bucket to pass to UnparseBucket. | 
|  | Bucket total_; | 
|  |  | 
|  | bool profile_mmap_; | 
|  |  | 
|  | // Bucket hash table for malloc. | 
|  | // We hand-craft one instead of using one of the pre-written | 
|  | // ones because we do not want to use malloc when operating on the table. | 
|  | // It is only few lines of code, so no big deal. | 
|  | Bucket** bucket_table_; | 
|  | int num_buckets_; | 
|  |  | 
|  | // Map of all currently allocated objects and mapped regions we know about. | 
|  | AllocationMap* address_map_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(HeapProfileTable); | 
|  | }; | 
|  |  | 
|  | class HeapProfileTable::Snapshot { | 
|  | public: | 
|  | const Stats& total() const { return total_; } | 
|  |  | 
|  | // Report anything in this snapshot as a leak. | 
|  | // May use new/delete for temporary storage. | 
|  | // If should_symbolize is true, will fork (which is not threadsafe) | 
|  | // to turn addresses into symbol names.  Set to false for maximum safety. | 
|  | // Also writes a heap profile to "filename" that contains | 
|  | // all of the objects in this snapshot. | 
|  | void ReportLeaks(const char* checker_name, const char* filename, | 
|  | bool should_symbolize); | 
|  |  | 
|  | // Report the addresses of all leaked objects. | 
|  | // May use new/delete for temporary storage. | 
|  | void ReportIndividualObjects(); | 
|  |  | 
|  | bool Empty() const { | 
|  | return (total_.allocs == 0) && (total_.alloc_size == 0); | 
|  | } | 
|  |  | 
|  | private: | 
|  | friend class HeapProfileTable; | 
|  |  | 
|  | // Total count/size are stored in a Bucket so we can reuse UnparseBucket | 
|  | Bucket total_; | 
|  |  | 
|  | // We share the Buckets managed by the parent table, but have our | 
|  | // own object->bucket map. | 
|  | AllocationMap map_; | 
|  |  | 
|  | Snapshot(Allocator alloc, DeAllocator dealloc) : map_(alloc, dealloc) { | 
|  | memset(&total_, 0, sizeof(total_)); | 
|  | } | 
|  |  | 
|  | // Callback used to populate a Snapshot object with entries found | 
|  | // in another allocation map. | 
|  | inline void Add(const void* ptr, const AllocValue& v) { | 
|  | map_.Insert(ptr, v); | 
|  | total_.allocs++; | 
|  | total_.alloc_size += v.bytes; | 
|  | } | 
|  |  | 
|  | // Helpers for sorting and generating leak reports | 
|  | struct Entry; | 
|  | struct ReportState; | 
|  | static void ReportCallback(const void* ptr, AllocValue* v, ReportState*); | 
|  | static void ReportObject(const void* ptr, AllocValue* v, char*); | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(Snapshot); | 
|  | }; | 
|  |  | 
|  | #endif  // BASE_HEAP_PROFILE_TABLE_H_ |