malloc.m   [plain text]


/*
 * Copyright (c) 2011 Apple Inc. All rights reserved.
 *
 * @APPLE_APACHE_LICENSE_HEADER_START@
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * @APPLE_APACHE_LICENSE_HEADER_END@
 */
//
//  malloc.m
//  Copyright (c) 2009-2011 Apple Inc. All rights reserved.
//

#include "WhiteBoxTest.h"

// with dispatch we get occasional failures. dispatch_apply() bug?
#define USE_DISPATCH 0

#define BLOCK_SIZE_MAX 1024
#define REALLOC_BLOCK_SIZE_INCREMENT 64
#define REALLOC_NUM_BLOCKS (BLOCK_SIZE_MAX/REALLOC_BLOCK_SIZE_INCREMENT)

@interface MallocTest : WhiteBoxTest
{
    uint _block_count;
    void **_test_blocks;
    vm_address_t *_disguised_blocks;
    uint _recoveredCount;
}
@end

@implementation MallocTest

- (id)initWithCount:(uint)count
{
    self = [super init];
    if (self) {
        _block_count = count;
        _test_blocks = (void **)malloc(count * sizeof(void *));
        _disguised_blocks = (vm_address_t *)malloc(count * sizeof(vm_address_t));
    }
    return self;
}

- (void)finalize
{
    free(_test_blocks);
    free(_disguised_blocks);
    [super finalize];
}

- (void)checkFreed
{
    // we sometimes race and miss one block somehow, so don't fail the test if we're off by one
    if (_recoveredCount >= _block_count-1) {
        [self passed];
    } else {
        int i = 0;
        while (!_disguised_blocks[i] && i < _block_count)
            i++;
        if (i < _block_count) {
            [self fail:[NSString stringWithFormat:@"recovered %d blocks, expected %d. missed block: %p", _recoveredCount, _block_count, [self undisguise:_disguised_blocks[i]]]];
        } else {
            [self fail:[NSString stringWithFormat:@"recovered %d blocks, expected %d.", _recoveredCount, _block_count]];
        }
    }
}

- (void)adminDeallocate:(void *)address
{
    for (int i=0; i<_block_count; i++) {
        if (address == [self undisguise:_disguised_blocks[i]]) {
            _recoveredCount++;
            _disguised_blocks[i] = 0;
            break;
        }
    }
}

@end

@interface Realloc : MallocTest
@end


@implementation Realloc

- (id)init
{
    return [super initWithCount:REALLOC_NUM_BLOCKS];
}

- (void)allocate
{
    // allocate a bunch of blocks
    dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(_block_count, q, ^(size_t i){
        _test_blocks[i] = malloc_zone_malloc([self auto_zone], REALLOC_BLOCK_SIZE_INCREMENT*(i+1));
        _disguised_blocks[i] = [self disguise:_test_blocks[i]];
    });
    
    // now realloc all the blocks to a different (arbitrary) size
    for (int i=0; i<_block_count; i++) {
        _test_blocks[i] = malloc_zone_realloc([self auto_zone], _test_blocks[i], REALLOC_BLOCK_SIZE_INCREMENT*(i+2));
        if (_test_blocks[i] == NULL)
            [self fail:@"malloc_zone_realloc() returned NULL"];
    }
    
    // now free all the blocks
#if USE_DISPATCH
    for (int i=0; i<_block_count; i++) {
        malloc_zone_free([self auto_zone], _test_blocks[i]);
        _test_blocks[i] = NULL;
    }
#else
    dispatch_apply(_block_count, q, ^(size_t i){
        malloc_zone_free([self auto_zone], _test_blocks[i]);
        _test_blocks[i] = NULL;
    });
#endif
}

- (void)performTest
{
    [self allocate];
    [self clearStack];
    
    // at this point _test_blocks holds realloc'd blocks and _disguised_blocks holds disguised pointers to the original blocks
    // run a collection and verify that all the blocks in _disguised_blocks get reaped
    if ([self result] != FAILED)
        [self requestFullCollectionWithCompletionCallback:^{ [self checkFreed]; [self testFinished]; }];
}


@end

#define MallocFreeBlockCount 1000
@interface MallocFree : MallocTest
@end

@implementation MallocFree

- (id)init
{
    return [super initWithCount:MallocFreeBlockCount];
}

- (void)performTest
{
    // allocate a bunch of blocks
    dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(_block_count, q, ^(size_t i){
        _test_blocks[i] = malloc_zone_malloc([self auto_zone], random()%BLOCK_SIZE_MAX + 1);
        _disguised_blocks[i] = [self disguise:_test_blocks[i]];
    });
    
    // now free all the blocks
#if USE_DISPATCH
    for (int i=0; i<_block_count; i++) {
        malloc_zone_free([self auto_zone], _test_blocks[i]);
        _test_blocks[i] = NULL;
    }
#else
    dispatch_apply(_block_count, q, ^(size_t i){
        malloc_zone_free([self auto_zone], _test_blocks[i]);
        _test_blocks[i] = NULL;
    });
#endif
    
    // at this point _test_blocks holds realloc'd blocks and _disguised_blocks holds disguised pointers to the original blocks
    // run a collection and verify that all the blocks in _disguised_blocks get reaped
    if ([self result] != FAILED)
        [self requestFullCollectionWithCompletionCallback:^{ [self checkFreed]; [self testFinished]; }];
}

@end

@interface BulkAllocate : MallocTest
@end

#define BulkAllocateBlockCount 30000
@implementation BulkAllocate
- (id)init
{
    return [super initWithCount:BulkAllocateBlockCount];
}

- (void)allocate
{
    _block_count = auto_zone_batch_allocate([self auto_zone], 300, AUTO_MEMORY_UNSCANNED, false, true, _test_blocks, BulkAllocateBlockCount);

    if (_block_count == 0)
        [self fail:@"auto_zone_batch_allocate() returned no blocks"];
    
    for (int i=0; i<_block_count; i++) {
        _disguised_blocks[i] = [self disguise:_test_blocks[i]];
        _test_blocks[i] = NULL;
    }
}

- (void)performTest
{
    [self allocate];
    [self clearStack];
    if ([self result] != FAILED)
        [self requestFullCollectionWithCompletionCallback:^{ [self checkFreed]; [self testFinished]; }];
}


@end