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//  AssociativeRefRecovery.m
22//  Copyright (c) 2008-2011 Apple Inc. All rights reserved.
23//
24
25#import <auto_zone.h>
26#import <malloc/malloc.h>
27#import <objc/runtime.h>
28
29#import "BlackBoxTest.h"
30
31/*
32 This test verifies that associatied blocks are reclaimed along with the main block.
33 It tests a Foundation object, bridged CF object, and a non-object block.
34 */
35
36static void *_foundation_key;
37static void *_cf_bridged_key;
38static void *_non_object_key;
39
40
41@interface AssociativeRefRecovery : BlackBoxTest {
42    BOOL _main_block_finalized;
43    id _main_block;
44    vm_address_t _disguised_main_block;
45    __weak id _foundation_ref;
46    __weak CFArrayRef _cf_bridged_ref;
47    __weak void *_non_object;
48}
49
50- (void)verifyNotCollected;
51- (void)verifyCollected;
52
53@end
54
55@implementation AssociativeRefRecovery
56- (void)performTest
57{
58    _main_block = [TestFinalizer new];
59    _disguised_main_block = [self disguise:_main_block];
60
61    id obj = [NSObject new];
62    _foundation_ref = obj;
63    auto_zone_set_associative_ref([self auto_zone], _main_block, &_foundation_key, obj);
64
65    CFArrayRef cf = CFArrayCreate(NULL, NULL, 0, NULL);
66    CFMakeCollectable(cf);
67    _cf_bridged_ref = cf;
68    auto_zone_set_associative_ref([self auto_zone], (void *)_main_block, &_cf_bridged_key, (void *)cf);
69
70    void *block = malloc_zone_malloc([self auto_zone], 1);
71    _non_object = block;
72    auto_zone_set_associative_ref([self auto_zone], (void *)_main_block, &_non_object_key, block);
73    auto_zone_release([self auto_zone], block);
74
75    // Run a full collection
76    [self requestFullCollectionWithCompletionCallback:^{ [self verifyNotCollected]; }];
77}
78
79- (void)verifyNotCollected
80{
81    if (_main_block_finalized)
82        [self fail:@"main block was incorrectly collected"];
83
84    if (!_foundation_ref)
85        [self fail:@"associated foundation object was incorrectly collected"];
86    if (auto_zone_get_associative_ref([self auto_zone], _main_block, &_foundation_key) != _foundation_ref)
87        [self fail:@"failed to retrieve associated foundation object"];
88
89    if (!_cf_bridged_ref)
90        [self fail:@"associated bridged CF object was incorrectly collected"];
91    if (auto_zone_get_associative_ref([self auto_zone], _main_block, &_cf_bridged_key) != _cf_bridged_ref)
92        [self fail:@"failed to retrieve associated bridged CF object"];
93
94    if (!_non_object)
95        [self fail:@"associated non-object block was incorrectly collected"];
96    if (auto_zone_get_associative_ref([self auto_zone], _main_block, &_non_object_key) != _non_object)
97        [self fail:@"failed to retrieve associated non-object block"];
98
99    if ([self result] != FAILED) {
100        _main_block = nil;
101        [self requestFullCollectionWithCompletionCallback:^{ [self verifyCollected]; }];
102    }
103}
104
105- (void)verifyCollected
106{
107    if (!_main_block_finalized)
108        [self fail:@"main block was not collected"];
109    if (_foundation_ref)
110        [self fail:@"failed to collect block associated with foundation object"];
111    if (_cf_bridged_ref)
112        [self fail:@"failed to collect block associated with bridged CF object"];
113    if (_non_object)
114        [self fail:@"failed to collect block associated with non-object block"];
115
116    [self passed];
117    [self testFinished];
118}
119
120- (void)didFinalize:(TestFinalizer *)finalizer
121{
122    if (finalizer == [self undisguise:_disguised_main_block])
123        _main_block_finalized = YES;
124}
125
126@end
127
128
129@interface AssociativeLinkedList : BlackBoxTest {
130    NSPointerArray *objects;
131    NSUInteger count;
132}
133- (void)performTest;
134- (void)verifyNotCollected;
135@end
136
137@implementation AssociativeLinkedList
138
139static char next_key;
140
141static NSData *unscannedData() {
142    const NSUInteger length = 16;
143    NSMutableData *data = [NSMutableData dataWithLength:16];
144    uint8_t *bytes = (uint8_t *)[data mutableBytes];
145    for (NSUInteger i = 0; i < length; ++i) {
146        *bytes++ = (uint8_t)random();
147    }
148    return [data copyWithZone:NULL];
149}
150
151- (void)performTest {
152    objects = [NSPointerArray pointerArrayWithOptions:NSPointerFunctionsZeroingWeakMemory];
153
154    // created an associative linked list using unscanned objects. then verify that none of the elements have been prematurely collected.
155    id *list = auto_zone_allocate_object([self auto_zone], sizeof(id), AUTO_POINTERS_ONLY, false, true);
156    *list = unscannedData();
157    id current = *list;
158    for (int i = 0; i < 1000; i++) {
159        id next = unscannedData();
160        objc_setAssociatedObject(current, &next_key, next, OBJC_ASSOCIATION_ASSIGN);
161        current = next;
162    }
163    objc_setAssociatedObject(self, &next_key, (id)list, OBJC_ASSOCIATION_ASSIGN);
164
165    current = *list;
166    while (current != nil) {
167        [objects addPointer:current];
168        current = objc_getAssociatedObject(current, &next_key);
169    }
170    count = [objects count];
171
172    // Run a full collection
173    [self requestExhaustiveCollectionWithCompletionCallback:^{ [self verifyNotCollected]; }];
174}
175
176- (void)verifyNotCollected {
177    [objects compact];
178    if ([objects count] != count) {
179        [self fail:@"unscanned objects failed to stay alive across a collection."];
180    } else {
181        [self passed];
182    }
183    [self testFinished];
184}
185
186@end
187