1/*
2 * Copyright (c) 2011 Apple Inc. All rights reserved.
3 *
4 * @APPLE_APACHE_LICENSE_HEADER_START@
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *     http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 * @APPLE_APACHE_LICENSE_HEADER_END@
19 */
20//
21//  malloc.m
22//  Copyright (c) 2009-2011 Apple Inc. All rights reserved.
23//
24
25#include "WhiteBoxTest.h"
26
27// with dispatch we get occasional failures. dispatch_apply() bug?
28#define USE_DISPATCH 0
29
30#define BLOCK_SIZE_MAX 1024
31#define REALLOC_BLOCK_SIZE_INCREMENT 64
32#define REALLOC_NUM_BLOCKS (BLOCK_SIZE_MAX/REALLOC_BLOCK_SIZE_INCREMENT)
33
34@interface MallocTest : WhiteBoxTest
35{
36    uint _block_count;
37    void **_test_blocks;
38    vm_address_t *_disguised_blocks;
39    uint _recoveredCount;
40}
41@end
42
43@implementation MallocTest
44
45- (id)initWithCount:(uint)count
46{
47    self = [super init];
48    if (self) {
49        _block_count = count;
50        _test_blocks = (void **)malloc(count * sizeof(void *));
51        _disguised_blocks = (vm_address_t *)malloc(count * sizeof(vm_address_t));
52    }
53    return self;
54}
55
56- (void)finalize
57{
58    free(_test_blocks);
59    free(_disguised_blocks);
60    [super finalize];
61}
62
63- (void)checkFreed
64{
65    // we sometimes race and miss one block somehow, so don't fail the test if we're off by one
66    if (_recoveredCount >= _block_count-1) {
67        [self passed];
68    } else {
69        int i = 0;
70        while (!_disguised_blocks[i] && i < _block_count)
71            i++;
72        if (i < _block_count) {
73            [self fail:[NSString stringWithFormat:@"recovered %d blocks, expected %d. missed block: %p", _recoveredCount, _block_count, [self undisguise:_disguised_blocks[i]]]];
74        } else {
75            [self fail:[NSString stringWithFormat:@"recovered %d blocks, expected %d.", _recoveredCount, _block_count]];
76        }
77    }
78}
79
80- (void)adminDeallocate:(void *)address
81{
82    for (int i=0; i<_block_count; i++) {
83        if (address == [self undisguise:_disguised_blocks[i]]) {
84            _recoveredCount++;
85            _disguised_blocks[i] = 0;
86            break;
87        }
88    }
89}
90
91@end
92
93@interface Realloc : MallocTest
94@end
95
96
97@implementation Realloc
98
99- (id)init
100{
101    return [super initWithCount:REALLOC_NUM_BLOCKS];
102}
103
104- (void)allocate
105{
106    // allocate a bunch of blocks
107    dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
108    dispatch_apply(_block_count, q, ^(size_t i){
109        _test_blocks[i] = malloc_zone_malloc([self auto_zone], REALLOC_BLOCK_SIZE_INCREMENT*(i+1));
110        _disguised_blocks[i] = [self disguise:_test_blocks[i]];
111    });
112
113    // now realloc all the blocks to a different (arbitrary) size
114    for (int i=0; i<_block_count; i++) {
115        _test_blocks[i] = malloc_zone_realloc([self auto_zone], _test_blocks[i], REALLOC_BLOCK_SIZE_INCREMENT*(i+2));
116        if (_test_blocks[i] == NULL)
117            [self fail:@"malloc_zone_realloc() returned NULL"];
118    }
119
120    // now free all the blocks
121#if USE_DISPATCH
122    for (int i=0; i<_block_count; i++) {
123        malloc_zone_free([self auto_zone], _test_blocks[i]);
124        _test_blocks[i] = NULL;
125    }
126#else
127    dispatch_apply(_block_count, q, ^(size_t i){
128        malloc_zone_free([self auto_zone], _test_blocks[i]);
129        _test_blocks[i] = NULL;
130    });
131#endif
132}
133
134- (void)performTest
135{
136    [self allocate];
137    [self clearStack];
138
139    // at this point _test_blocks holds realloc'd blocks and _disguised_blocks holds disguised pointers to the original blocks
140    // run a collection and verify that all the blocks in _disguised_blocks get reaped
141    if ([self result] != FAILED)
142        [self requestFullCollectionWithCompletionCallback:^{ [self checkFreed]; [self testFinished]; }];
143}
144
145
146@end
147
148#define MallocFreeBlockCount 1000
149@interface MallocFree : MallocTest
150@end
151
152@implementation MallocFree
153
154- (id)init
155{
156    return [super initWithCount:MallocFreeBlockCount];
157}
158
159- (void)performTest
160{
161    // allocate a bunch of blocks
162    dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
163    dispatch_apply(_block_count, q, ^(size_t i){
164        _test_blocks[i] = malloc_zone_malloc([self auto_zone], random()%BLOCK_SIZE_MAX + 1);
165        _disguised_blocks[i] = [self disguise:_test_blocks[i]];
166    });
167
168    // now free all the blocks
169#if USE_DISPATCH
170    for (int i=0; i<_block_count; i++) {
171        malloc_zone_free([self auto_zone], _test_blocks[i]);
172        _test_blocks[i] = NULL;
173    }
174#else
175    dispatch_apply(_block_count, q, ^(size_t i){
176        malloc_zone_free([self auto_zone], _test_blocks[i]);
177        _test_blocks[i] = NULL;
178    });
179#endif
180
181    // at this point _test_blocks holds realloc'd blocks and _disguised_blocks holds disguised pointers to the original blocks
182    // run a collection and verify that all the blocks in _disguised_blocks get reaped
183    if ([self result] != FAILED)
184        [self requestFullCollectionWithCompletionCallback:^{ [self checkFreed]; [self testFinished]; }];
185}
186
187@end
188
189@interface BulkAllocate : MallocTest
190@end
191
192#define BulkAllocateBlockCount 30000
193@implementation BulkAllocate
194- (id)init
195{
196    return [super initWithCount:BulkAllocateBlockCount];
197}
198
199- (void)allocate
200{
201    _block_count = auto_zone_batch_allocate([self auto_zone], 300, AUTO_MEMORY_UNSCANNED, false, true, _test_blocks, BulkAllocateBlockCount);
202
203    if (_block_count == 0)
204        [self fail:@"auto_zone_batch_allocate() returned no blocks"];
205
206    for (int i=0; i<_block_count; i++) {
207        _disguised_blocks[i] = [self disguise:_test_blocks[i]];
208        _test_blocks[i] = NULL;
209    }
210}
211
212- (void)performTest
213{
214    [self allocate];
215    [self clearStack];
216    if ([self result] != FAILED)
217        [self requestFullCollectionWithCompletionCallback:^{ [self checkFreed]; [self testFinished]; }];
218}
219
220
221@end
222
223