Home | History | Annotate | Download | only in wtf
      1 /*
      2  * Copyright (C) 2013 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 "wtf/PartitionAlloc.h"
     33 
     34 #include "wtf/CryptographicallyRandomNumber.h"
     35 #include "wtf/OwnArrayPtr.h"
     36 #include "wtf/PageAllocator.h"
     37 #include <gtest/gtest.h>
     38 #include <stdlib.h>
     39 #include <string.h>
     40 
     41 #if OS(UNIX)
     42 #include <sys/mman.h>
     43 
     44 #ifndef MAP_ANONYMOUS
     45 #define MAP_ANONYMOUS MAP_ANON
     46 #endif
     47 #endif // OS(UNIX)
     48 
     49 #if !defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
     50 
     51 namespace {
     52 
     53 static PartitionRoot root;
     54 
     55 static const int kTestAllocSize = sizeof(void*);
     56 
     57 static void RandomNumberSource(unsigned char* buf, size_t len)
     58 {
     59     memset(buf, '\0', len);
     60 }
     61 
     62 static void TestSetup()
     63 {
     64     WTF::setRandomSource(RandomNumberSource);
     65     partitionAllocInit(&root);
     66 }
     67 
     68 static void TestShutdown()
     69 {
     70     // We expect no leaks in the general case. We have a test for leak
     71     // detection.
     72     EXPECT_TRUE(partitionAllocShutdown(&root));
     73 }
     74 
     75 static WTF::PartitionPageHeader* GetFullPage(size_t size)
     76 {
     77     size_t bucketIdx = size >> WTF::kBucketShift;
     78     WTF::PartitionBucket* bucket = &root.buckets[bucketIdx];
     79     size_t numSlots = (WTF::kPartitionPageSize - sizeof(WTF::PartitionPageHeader)) / size;
     80     void* first = 0;
     81     void* last = 0;
     82     size_t i;
     83     for (i = 0; i < numSlots; ++i) {
     84         void* ptr = partitionAlloc(&root, size);
     85         EXPECT_TRUE(ptr);
     86         if (!i)
     87             first = ptr;
     88         else if (i == numSlots - 1)
     89             last = ptr;
     90     }
     91     EXPECT_EQ(reinterpret_cast<size_t>(first) & WTF::kPartitionPageBaseMask, reinterpret_cast<size_t>(last) & WTF::kPartitionPageBaseMask);
     92     EXPECT_EQ(numSlots, static_cast<size_t>(bucket->currPage->numAllocatedSlots));
     93     EXPECT_EQ(0, bucket->currPage->freelistHead);
     94     EXPECT_TRUE(bucket->currPage);
     95     EXPECT_TRUE(bucket->currPage != &root.seedPage);
     96     return bucket->currPage;
     97 }
     98 
     99 static void FreeFullPage(WTF::PartitionPageHeader* page, size_t size)
    100 {
    101     size_t numSlots = (WTF::kPartitionPageSize - sizeof(WTF::PartitionPageHeader)) / size;
    102     EXPECT_EQ(numSlots, static_cast<size_t>(abs(page->numAllocatedSlots)));
    103     char* ptr = reinterpret_cast<char*>(page);
    104     ptr += sizeof(WTF::PartitionPageHeader);
    105     size_t i;
    106     for (i = 0; i < numSlots; ++i) {
    107         partitionFree(ptr);
    108         ptr += size;
    109     }
    110     EXPECT_EQ(0, page->numAllocatedSlots);
    111 }
    112 
    113 // Check that the most basic of allocate / free pairs work.
    114 TEST(WTF_PartitionAlloc, Basic)
    115 {
    116     TestSetup();
    117     size_t bucketIdx = kTestAllocSize >> WTF::kBucketShift;
    118     WTF::PartitionBucket* bucket = &root.buckets[bucketIdx];
    119 
    120     EXPECT_EQ(0, bucket->freePages);
    121     EXPECT_EQ(&bucket->root->seedPage, bucket->currPage);
    122     EXPECT_EQ(&bucket->root->seedPage, bucket->currPage->next);
    123     EXPECT_EQ(&bucket->root->seedPage, bucket->currPage->prev);
    124 
    125     void* ptr = partitionAlloc(&root, kTestAllocSize);
    126     EXPECT_TRUE(ptr);
    127     EXPECT_EQ(sizeof(WTF::PartitionPageHeader), reinterpret_cast<size_t>(ptr) & WTF::kPartitionPageOffsetMask);
    128     // Check that the offset appears to include a guard page.
    129     EXPECT_EQ(WTF::kPartitionPageSize + sizeof(WTF::PartitionPageHeader), reinterpret_cast<size_t>(ptr) & WTF::kSuperPageOffsetMask);
    130 
    131     partitionFree(ptr);
    132     // Expect that a just-freed page doesn't get tossed to the freelist.
    133     EXPECT_EQ(0, bucket->freePages);
    134 
    135     TestShutdown();
    136 }
    137 
    138 // Check that we can detect a memory leak.
    139 TEST(WTF_PartitionAlloc, SimpleLeak)
    140 {
    141     TestSetup();
    142     void* leakedPtr = partitionAlloc(&root, kTestAllocSize);
    143     EXPECT_FALSE(partitionAllocShutdown(&root));
    144 }
    145 
    146 // Test multiple allocations, and freelist handling.
    147 TEST(WTF_PartitionAlloc, MultiAlloc)
    148 {
    149     TestSetup();
    150 
    151     char* ptr1 = reinterpret_cast<char*>(partitionAlloc(&root, kTestAllocSize));
    152     char* ptr2 = reinterpret_cast<char*>(partitionAlloc(&root, kTestAllocSize));
    153     EXPECT_TRUE(ptr1);
    154     EXPECT_TRUE(ptr2);
    155     ptrdiff_t diff = ptr2 - ptr1;
    156     EXPECT_EQ(kTestAllocSize, diff);
    157 
    158     // Check that we re-use the just-freed slot.
    159     partitionFree(ptr2);
    160     ptr2 = reinterpret_cast<char*>(partitionAlloc(&root, kTestAllocSize));
    161     EXPECT_TRUE(ptr2);
    162     diff = ptr2 - ptr1;
    163     EXPECT_EQ(kTestAllocSize, diff);
    164     partitionFree(ptr1);
    165     ptr1 = reinterpret_cast<char*>(partitionAlloc(&root, kTestAllocSize));
    166     EXPECT_TRUE(ptr1);
    167     diff = ptr2 - ptr1;
    168     EXPECT_EQ(kTestAllocSize, diff);
    169 
    170     char* ptr3 = reinterpret_cast<char*>(partitionAlloc(&root, kTestAllocSize));
    171     EXPECT_TRUE(ptr3);
    172     diff = ptr3 - ptr1;
    173     EXPECT_EQ(kTestAllocSize * 2, diff);
    174 
    175     partitionFree(ptr1);
    176     partitionFree(ptr2);
    177     partitionFree(ptr3);
    178 
    179     TestShutdown();
    180 }
    181 
    182 // Test a bucket with multiple pages.
    183 TEST(WTF_PartitionAlloc, MultiPages)
    184 {
    185     TestSetup();
    186     size_t bucketIdx = kTestAllocSize >> WTF::kBucketShift;
    187     WTF::PartitionBucket* bucket = &root.buckets[bucketIdx];
    188 
    189     WTF::PartitionPageHeader* page = GetFullPage(kTestAllocSize);
    190     FreeFullPage(page, kTestAllocSize);
    191     EXPECT_EQ(0, bucket->freePages);
    192     EXPECT_EQ(page, bucket->currPage);
    193     EXPECT_EQ(page, page->next);
    194     EXPECT_EQ(page, page->prev);
    195 
    196     page = GetFullPage(kTestAllocSize);
    197     WTF::PartitionPageHeader* page2 = GetFullPage(kTestAllocSize);
    198 
    199     EXPECT_EQ(page2, bucket->currPage);
    200 
    201     // Fully free the non-current page, it should be freelisted.
    202     FreeFullPage(page, kTestAllocSize);
    203     EXPECT_EQ(0, page->numAllocatedSlots);
    204     EXPECT_TRUE(bucket->freePages);
    205     EXPECT_EQ(page, bucket->freePages->page);
    206     EXPECT_EQ(page2, bucket->currPage);
    207 
    208     // Allocate a new page, it should pull from the freelist.
    209     page = GetFullPage(kTestAllocSize);
    210     EXPECT_FALSE(bucket->freePages);
    211     EXPECT_EQ(page, bucket->currPage);
    212 
    213     FreeFullPage(page, kTestAllocSize);
    214     FreeFullPage(page2, kTestAllocSize);
    215     EXPECT_EQ(0, page->numAllocatedSlots);
    216     EXPECT_EQ(0, page2->numAllocatedSlots);
    217 
    218     TestShutdown();
    219 }
    220 
    221 // Test some finer aspects of internal page transitions.
    222 TEST(WTF_PartitionAlloc, PageTransitions)
    223 {
    224     TestSetup();
    225     size_t bucketIdx = kTestAllocSize >> WTF::kBucketShift;
    226     WTF::PartitionBucket* bucket = &root.buckets[bucketIdx];
    227 
    228     WTF::PartitionPageHeader* page1 = GetFullPage(kTestAllocSize);
    229     WTF::PartitionPageHeader* page2 = GetFullPage(kTestAllocSize);
    230     EXPECT_EQ(page2, bucket->currPage);
    231     EXPECT_EQ(page1, page2->next);
    232     EXPECT_EQ(page1, page2->prev);
    233     // Allocating another page at this point should cause us to scan over page1
    234     // (which is both full and NOT our current page), and evict it from the
    235     // freelist. Older code had a O(n^2) condition due to failure to do this.
    236     WTF::PartitionPageHeader* page3 = GetFullPage(kTestAllocSize);
    237     EXPECT_EQ(page3, bucket->currPage);
    238     EXPECT_EQ(page2, page3->next);
    239     EXPECT_EQ(page3, page2->next);
    240 
    241     // Work out a pointer into page2 and free it.
    242     char* ptr = reinterpret_cast<char*>(page2) + sizeof(WTF::PartitionPageHeader);
    243     partitionFree(ptr);
    244     // Trying to allocate at this time should cause us to cycle around to page2
    245     // and find the recently freed slot.
    246     char* newPtr = reinterpret_cast<char*>(partitionAlloc(&root, kTestAllocSize));
    247     EXPECT_EQ(ptr, newPtr);
    248     EXPECT_EQ(page2, bucket->currPage);
    249 
    250     // Work out a pointer into page1 and free it. This should pull the page
    251     // back into the ring list of available pages.
    252     ptr = reinterpret_cast<char*>(page1) + sizeof(WTF::PartitionPageHeader);
    253     partitionFree(ptr);
    254     // This allocation should be satisfied by page1.
    255     newPtr = reinterpret_cast<char*>(partitionAlloc(&root, kTestAllocSize));
    256     EXPECT_EQ(ptr, newPtr);
    257     EXPECT_EQ(page1, bucket->currPage);
    258 
    259     FreeFullPage(page3, kTestAllocSize);
    260     FreeFullPage(page2, kTestAllocSize);
    261     FreeFullPage(page1, kTestAllocSize);
    262 
    263     TestShutdown();
    264 }
    265 
    266 // Test some corner cases relating to page transitions in the internal
    267 // free page list metadata bucket.
    268 TEST(WTF_PartitionAlloc, FreePageListPageTransitions)
    269 {
    270     TestSetup();
    271     WTF::PartitionBucket* freePageBucket = &root.buckets[WTF::kFreePageBucket];
    272     size_t bucketIdx = kTestAllocSize >> WTF::kBucketShift;
    273     WTF::PartitionBucket* bucket = &root.buckets[bucketIdx];
    274 
    275     size_t numToFillFreeListPage = (WTF::kPartitionPageSize - sizeof(WTF::PartitionPageHeader)) / sizeof(WTF::PartitionFreepagelistEntry);
    276     OwnArrayPtr<WTF::PartitionPageHeader*> pages = adoptArrayPtr(new WTF::PartitionPageHeader*[numToFillFreeListPage + 1]);
    277     size_t i;
    278     // The +1 is because we need to account for the fact that the current page
    279     // never gets thrown on the freelist.
    280     for (i = 0; i < numToFillFreeListPage + 1; ++i) {
    281         pages[i] = GetFullPage(kTestAllocSize);
    282     }
    283     EXPECT_EQ(pages[numToFillFreeListPage], bucket->currPage);
    284     for (i = 0; i < numToFillFreeListPage + 1; ++i) {
    285         FreeFullPage(pages[i], kTestAllocSize);
    286     }
    287     EXPECT_EQ(pages[numToFillFreeListPage], bucket->currPage);
    288 
    289     // At this moment, we should have filled an entire partition page full of
    290     // WTF::PartitionFreepagelistEntry, in the special free list entry bucket.
    291     EXPECT_EQ(numToFillFreeListPage, freePageBucket->currPage->numAllocatedSlots);
    292     EXPECT_EQ(0, freePageBucket->currPage->freelistHead);
    293 
    294     // Allocate / free a full couple of pages of a different bucket size so
    295     // we get control of a different free page list.
    296     WTF::PartitionPageHeader* page1 = GetFullPage(kTestAllocSize * 2);
    297     WTF::PartitionPageHeader* page2 = GetFullPage(kTestAllocSize * 2);
    298     FreeFullPage(page1, kTestAllocSize * 2);
    299     FreeFullPage(page2, kTestAllocSize * 2);
    300 
    301     // Now, we have a second page for free page objects, with a single entry
    302     // in it -- from a free page in the "kTestAllocSize * 2" bucket.
    303     EXPECT_EQ(1, freePageBucket->currPage->numAllocatedSlots);
    304     EXPECT_EQ(0, freePageBucket->freePages);
    305 
    306     // If we re-allocate all kTestAllocSize allocations, we'll pull all the
    307     // free pages and end up freeing the first page for free page objects.
    308     // It's getting a bit tricky but a nice re-entrancy is going on:
    309     // alloc(kTestAllocSize) -> pulls page from free page list ->
    310     // free(PartitionFreepagelistEntry) -> last entry in page freed ->
    311     // alloc(PartitionFreepagelistEntry).
    312     for (i = 0; i < numToFillFreeListPage + 1; ++i) {
    313         pages[i] = GetFullPage(kTestAllocSize);
    314     }
    315     EXPECT_EQ(pages[numToFillFreeListPage], bucket->currPage);
    316     EXPECT_EQ(2, freePageBucket->currPage->numAllocatedSlots);
    317     EXPECT_TRUE(freePageBucket->freePages);
    318 
    319     // As part of the final free-up, we'll test another re-entrancy:
    320     // free(kTestAllocSize) -> last entry in page freed ->
    321     // alloc(PartitionFreepagelistEntry) -> pulls page from free page list ->
    322     // free(PartitionFreepagelistEntry)
    323     for (i = 0; i < numToFillFreeListPage + 1; ++i) {
    324         FreeFullPage(pages[i], kTestAllocSize);
    325     }
    326     EXPECT_EQ(pages[numToFillFreeListPage], bucket->currPage);
    327 
    328     TestShutdown();
    329 }
    330 
    331 // Test a large series of allocations that cross more than one underlying
    332 // 64KB super page allocation.
    333 TEST(WTF_PartitionAlloc, MultiPageAllocs)
    334 {
    335     TestSetup();
    336     // This is guaranteed to cross a super page boundary because the first
    337     // partition page "slot" will be taken up by a guard page.
    338     size_t numPagesNeeded = WTF::kSuperPageSize / WTF::kPartitionPageSize;
    339     EXPECT_GT(numPagesNeeded, 1u);
    340     OwnArrayPtr<WTF::PartitionPageHeader*> pages;
    341     pages = adoptArrayPtr(new WTF::PartitionPageHeader*[numPagesNeeded]);
    342     uintptr_t firstSuperPageBase = 0;
    343     size_t i;
    344     for (i = 0; i < numPagesNeeded; ++i) {
    345         pages[i] = GetFullPage(kTestAllocSize);
    346         if (!i)
    347             firstSuperPageBase = (reinterpret_cast<uintptr_t>(pages[i]) & WTF::kSuperPageBaseMask);
    348         if (i == numPagesNeeded - 1) {
    349             uintptr_t secondSuperPageBase = reinterpret_cast<uintptr_t>(pages[i]) & WTF::kSuperPageBaseMask;
    350             EXPECT_FALSE(secondSuperPageBase == firstSuperPageBase);
    351             // If the two super pages are contiguous, also check that we didn't
    352             // erroneously allocate a guard page for the second page.
    353             if (secondSuperPageBase == firstSuperPageBase + WTF::kSuperPageSize)
    354                 EXPECT_EQ(0u, secondSuperPageBase & WTF::kSuperPageOffsetMask);
    355         }
    356     }
    357     for (i = 0; i < numPagesNeeded; ++i) {
    358         FreeFullPage(pages[i], kTestAllocSize);
    359     }
    360 
    361     TestShutdown();
    362 }
    363 
    364 // Test the generic allocation functions that can handle arbitrary sizes and
    365 // reallocing etc.
    366 TEST(WTF_PartitionAlloc, GenericAlloc)
    367 {
    368     TestSetup();
    369 
    370     void* ptr = partitionAllocGeneric(&root, 1);
    371     EXPECT_TRUE(ptr);
    372     partitionFreeGeneric(ptr, 1);
    373     ptr = partitionAllocGeneric(&root, WTF::kMaxAllocation + 1);
    374     EXPECT_TRUE(ptr);
    375     partitionFreeGeneric(ptr, WTF::kMaxAllocation + 1);
    376 
    377     ptr = partitionAllocGeneric(&root, 1);
    378     EXPECT_TRUE(ptr);
    379     void* origPtr = ptr;
    380     char* charPtr = static_cast<char*>(ptr);
    381     *charPtr = 'A';
    382 
    383     // Change the size of the realloc, remaining inside the same bucket.
    384     void* newPtr = partitionReallocGeneric(&root, ptr, 1, 2);
    385     EXPECT_EQ(ptr, newPtr);
    386     newPtr = partitionReallocGeneric(&root, ptr, 2, 1);
    387     EXPECT_EQ(ptr, newPtr);
    388     newPtr = partitionReallocGeneric(&root, ptr, 1, WTF::kAllocationGranularity);
    389     EXPECT_EQ(ptr, newPtr);
    390 
    391     // Change the size of the realloc, switching buckets.
    392     newPtr = partitionReallocGeneric(&root, ptr, WTF::kAllocationGranularity, WTF::kAllocationGranularity + 1);
    393     EXPECT_NE(newPtr, ptr);
    394     // Check that the realloc copied correctly.
    395     char* newCharPtr = static_cast<char*>(newPtr);
    396     EXPECT_EQ(*newCharPtr, 'A');
    397     *newCharPtr = 'B';
    398     // The realloc moved. To check that the old allocation was freed, we can
    399     // do an alloc of the old allocation size and check that the old allocation
    400     // address is at the head of the freelist and reused.
    401     void* reusedPtr = partitionAllocGeneric(&root, 1);
    402     EXPECT_EQ(reusedPtr, origPtr);
    403     partitionFreeGeneric(reusedPtr, 1);
    404 
    405     // Downsize the realloc.
    406     ptr = newPtr;
    407     newPtr = partitionReallocGeneric(&root, ptr, WTF::kAllocationGranularity + 1, 1);
    408     EXPECT_EQ(newPtr, origPtr);
    409     newCharPtr = static_cast<char*>(newPtr);
    410     EXPECT_EQ(*newCharPtr, 'B');
    411     *newCharPtr = 'C';
    412 
    413     // Upsize the realloc to outside the partition.
    414     ptr = newPtr;
    415     newPtr = partitionReallocGeneric(&root, ptr, 1, WTF::kMaxAllocation + 1);
    416     EXPECT_NE(newPtr, ptr);
    417     newCharPtr = static_cast<char*>(newPtr);
    418     EXPECT_EQ(*newCharPtr, 'C');
    419     *newCharPtr = 'D';
    420 
    421     // Upsize and downsize the realloc, remaining outside the partition.
    422     ptr = newPtr;
    423     newPtr = partitionReallocGeneric(&root, ptr, WTF::kMaxAllocation + 1, WTF::kMaxAllocation * 10);
    424     newCharPtr = static_cast<char*>(newPtr);
    425     EXPECT_EQ(*newCharPtr, 'D');
    426     *newCharPtr = 'E';
    427     ptr = newPtr;
    428     newPtr = partitionReallocGeneric(&root, ptr, WTF::kMaxAllocation * 10, WTF::kMaxAllocation * 2);
    429     newCharPtr = static_cast<char*>(newPtr);
    430     EXPECT_EQ(*newCharPtr, 'E');
    431     *newCharPtr = 'F';
    432 
    433     // Downsize the realloc to inside the partition.
    434     ptr = newPtr;
    435     newPtr = partitionReallocGeneric(&root, ptr, WTF::kMaxAllocation * 2, 1);
    436     EXPECT_NE(newPtr, ptr);
    437     EXPECT_EQ(newPtr, origPtr);
    438     newCharPtr = static_cast<char*>(newPtr);
    439     EXPECT_EQ(*newCharPtr, 'F');
    440 
    441     partitionFreeGeneric(newPtr, 1);
    442     TestShutdown();
    443 }
    444 
    445 #if OS(UNIX)
    446 
    447 // Test correct handling if our mapping collides with another.
    448 TEST(WTF_PartitionAlloc, MappingCollision)
    449 {
    450     TestSetup();
    451 
    452     WTF::PartitionPageHeader* page1 = GetFullPage(kTestAllocSize);
    453     char* pageBase = reinterpret_cast<char*>(page1);
    454     // Map a single system page either side of the mapping for our allocations,
    455     // with the goal of tripping up alignment of the next mapping.
    456     void* map1 = mmap(pageBase - WTF::kSystemPageSize, WTF::kSystemPageSize, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
    457     EXPECT_TRUE(map1 && map1 != MAP_FAILED);
    458     void* map2 = mmap(pageBase + WTF::kSuperPageSize, WTF::kSystemPageSize, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
    459     EXPECT_TRUE(map2 && map2 != MAP_FAILED);
    460 
    461     WTF::PartitionPageHeader* page2 = GetFullPage(kTestAllocSize);
    462     EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(page2) & WTF::kPartitionPageOffsetMask);
    463     FreeFullPage(page2, kTestAllocSize);
    464 
    465     FreeFullPage(page1, kTestAllocSize);
    466     munmap(map1, WTF::kSystemPageSize);
    467     munmap(map2, WTF::kSystemPageSize);
    468 
    469     TestShutdown();
    470 }
    471 
    472 #endif // OS(UNIX)
    473 
    474 } // namespace
    475 
    476 #endif // !defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
    477