1 // Copyright 2016 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <pthread.h>
6
7 #include <fbl/algorithm.h>
8 #include <fbl/alloc_checker.h>
9 #include <fbl/ref_counted.h>
10 #include <fbl/ref_ptr.h>
11 #include <unittest/unittest.h>
12
13 static uint8_t DestructionTrackerStorage[32];
14
15 template <bool EnableAdoptionValidator>
16 class DestructionTracker : public fbl::RefCounted<DestructionTracker<EnableAdoptionValidator>,
17 EnableAdoptionValidator> {
18 public:
DestructionTracker(bool * destroyed)19 explicit DestructionTracker(bool* destroyed)
20 : destroyed_(destroyed) {}
~DestructionTracker()21 ~DestructionTracker() { *destroyed_ = true; }
22
23 // During our death tests, we will be doing things which would normally be
24 // Very Bad for actually heap allocated objects. These tests only ever need
25 // a single DestructionTracker object to be allocated at a time. Overload
26 // new/delete so that we are using statically allocated storage and avoid
27 // doing bad things to our heap.
operator new(size_t size)28 void* operator new(size_t size) {
29 ZX_ASSERT(size <= sizeof(DestructionTrackerStorage));
30 return DestructionTrackerStorage;
31 }
32
operator new(size_t size,fbl::AllocChecker * ac)33 void* operator new(size_t size, fbl::AllocChecker* ac) {
34 ZX_ASSERT(ac != nullptr);
35 ZX_ASSERT(size <= sizeof(DestructionTrackerStorage));
36 ac->arm(size, true);
37 return DestructionTrackerStorage;
38 }
39
operator delete(void * ptr)40 void operator delete(void* ptr) {
41 ZX_ASSERT(ptr == DestructionTrackerStorage);
42 }
43
44 private:
45 bool* destroyed_;
46 };
47
48 static_assert(sizeof(DestructionTracker<true>) == sizeof(DestructionTracker<false>),
49 "DestructionTracker debug vs. release size mismatch!");
50 static_assert(sizeof(DestructionTracker<true>) <= sizeof(DestructionTrackerStorage),
51 "Not enough static storage for DestructionTracker<true|false>!");
52
53 template <bool EnableAdoptionValidator>
inc_and_dec(void * arg)54 static void* inc_and_dec(void* arg) {
55 auto tracker = reinterpret_cast<DestructionTracker<EnableAdoptionValidator>*>(arg);
56 for (size_t i = 0u; i < 500; ++i) {
57 fbl::RefPtr<DestructionTracker<EnableAdoptionValidator>> ptr(tracker);
58 }
59 return nullptr;
60 }
61
62 template <bool EnableAdoptionValidator>
ref_counted_test()63 static bool ref_counted_test() {
64 BEGIN_TEST;
65
66 bool destroyed = false;
67 {
68 fbl::AllocChecker ac;
69 fbl::RefPtr<DestructionTracker<EnableAdoptionValidator>> ptr =
70 fbl::AdoptRef(new (&ac) DestructionTracker<EnableAdoptionValidator>(&destroyed));
71 EXPECT_TRUE(ac.check());
72
73 EXPECT_FALSE(destroyed, "should not be destroyed");
74 void* arg = reinterpret_cast<void*>(ptr.get());
75
76 pthread_t threads[5];
77 for (size_t i = 0u; i < fbl::count_of(threads); ++i) {
78 int res = pthread_create(&threads[i], NULL, &inc_and_dec<EnableAdoptionValidator>, arg);
79 ASSERT_LE(0, res, "Failed to create inc_and_dec thread!");
80 }
81
82 inc_and_dec<EnableAdoptionValidator>(arg);
83
84 for (size_t i = 0u; i < fbl::count_of(threads); ++i)
85 pthread_join(threads[i], NULL);
86
87 EXPECT_FALSE(destroyed, "should not be destroyed after inc/dec pairs");
88 }
89 EXPECT_TRUE(destroyed, "should be when RefPtr falls out of scope");
90 END_TEST;
91 }
92
93 template <bool EnableAdoptionValidator>
make_ref_counted_test()94 static bool make_ref_counted_test() {
95 BEGIN_TEST;
96
97 bool destroyed = false;
98 {
99 auto ptr = fbl::MakeRefCounted<DestructionTracker<EnableAdoptionValidator>>(&destroyed);
100 EXPECT_FALSE(destroyed, "should not be destroyed");
101 }
102 EXPECT_TRUE(destroyed, "should be when RefPtr falls out of scope");
103
104 destroyed = false;
105 {
106 fbl::AllocChecker ac;
107 auto ptr2 = fbl::MakeRefCountedChecked<DestructionTracker<EnableAdoptionValidator>>(
108 &ac, &destroyed);
109 EXPECT_TRUE(ac.check());
110 }
111 EXPECT_TRUE(destroyed, "should be when RefPtr falls out of scope");
112
113 END_TEST;
114 }
115
wrap_dead_pointer_asserts()116 static bool wrap_dead_pointer_asserts() {
117 BEGIN_TEST;
118
119 bool destroyed = false;
120 DestructionTracker<true>* raw = nullptr;
121 {
122 // Create and adopt a ref-counted object, and let it go out of scope.
123 fbl::AllocChecker ac;
124 fbl::RefPtr<DestructionTracker<true>> ptr =
125 fbl::AdoptRef(new (&ac) DestructionTracker<true>(&destroyed));
126 EXPECT_TRUE(ac.check());
127 raw = ptr.get();
128 EXPECT_FALSE(destroyed);
129 }
130 EXPECT_TRUE(destroyed);
131
132 // Wrapping the now-destroyed object should trigger an assertion.
133 ASSERT_DEATH(
134 [](void* void_raw) {
135 auto raw = reinterpret_cast<DestructionTracker<true>*>(void_raw);
136 __UNUSED fbl::RefPtr<DestructionTracker<true>> zombie = fbl::WrapRefPtr(raw);
137 },
138 raw, "Assert should have fired after wraping dead object\n");
139
140 END_TEST;
141 }
142
extra_release_asserts()143 static bool extra_release_asserts() {
144 BEGIN_TEST;
145
146 // Create and adopt a ref-counted object.
147 bool destroyed = false;
148 fbl::AllocChecker ac;
149 DestructionTracker<true>* raw = new (&ac) DestructionTracker<true>(&destroyed);
150 ASSERT_TRUE(ac.check());
151 raw->Adopt();
152
153 // Manually release once, which should tell us to delete the object.
154 EXPECT_TRUE(raw->Release());
155 // (But it's not deleted since we didn't listen to the return value
156 // of Release())
157 EXPECT_FALSE(destroyed);
158
159 ASSERT_DEATH(
160 [](void* void_raw) {
161 auto raw = reinterpret_cast<DestructionTracker<true>*>(void_raw);
162 // Manually releasing again should trigger the assertion.
163 __UNUSED bool unused = raw->Release();
164 },
165 raw, "Assert should have fired after releasing object with ref count of zero\n");
166
167 // Do not attempt to actually delete the object. It was never actually heap
168 // allocated, so we are not leaking anything, and the system is in a bad
169 // state now. Attempting to delete the object can trigger other ASSERTs
170 // which will crash the test.
171
172 END_TEST;
173 }
174
wrap_after_last_release_asserts()175 static bool wrap_after_last_release_asserts() {
176 BEGIN_TEST;
177
178 // Create and adopt a ref-counted object.
179 bool destroyed = false;
180 fbl::AllocChecker ac;
181 DestructionTracker<true>* raw = new (&ac) DestructionTracker<true>(&destroyed);
182 ASSERT_TRUE(ac.check());
183 raw->Adopt();
184
185 // Manually release once, which should tell us to delete the object.
186 EXPECT_TRUE(raw->Release());
187 // (But it's not deleted since we didn't listen to the return value
188 // of Release())
189 EXPECT_FALSE(destroyed);
190
191 ASSERT_DEATH(
192 [](void* void_raw) {
193 auto raw = reinterpret_cast<DestructionTracker<true>*>(void_raw);
194 // Adding another ref (by wrapping) should trigger the assertion.
195 __UNUSED bool unused = raw->Release();
196 },
197 raw, "Assert should have fired after wraping object with ref count of zero\n");
198
199 // Do not attempt to actually delete the object. See previous comments.
200
201 END_TEST;
202 }
203
unadopted_add_ref_asserts()204 static bool unadopted_add_ref_asserts() {
205 BEGIN_TEST;
206
207 // Create an un-adopted ref-counted object.
208 bool destroyed = false;
209 fbl::AllocChecker ac;
210 DestructionTracker<true>* raw = new (&ac) DestructionTracker<true>(&destroyed);
211 ASSERT_TRUE(ac.check());
212
213 ASSERT_DEATH(
214 [](void* void_raw) {
215 auto raw = reinterpret_cast<DestructionTracker<true>*>(void_raw);
216 // Adding a ref (by wrapping) without adopting first should trigger an
217 // assertion.
218 fbl::RefPtr<DestructionTracker<true>> unadopted = fbl::WrapRefPtr(raw);
219 },
220 raw, "Assert should have fired after wraping non-adopted object\n");
221
222 // Do not attempt to actually delete the object. See previous comments.
223
224 END_TEST;
225 }
226
unadopted_release_asserts()227 static bool unadopted_release_asserts() {
228 BEGIN_TEST;
229
230 // Create an un-adopted ref-counted object.
231 bool destroyed = false;
232 fbl::AllocChecker ac;
233 DestructionTracker<true>* raw = new (&ac) DestructionTracker<true>(&destroyed);
234 ASSERT_TRUE(ac.check());
235
236 ASSERT_DEATH(
237 [](void* void_raw) {
238 auto raw = reinterpret_cast<DestructionTracker<true>*>(void_raw);
239 // Releasing without adopting first should trigger an assertion.
240 __UNUSED bool unused = raw->Release();
241 },
242 raw, "Assert should have fired after releasing non-adopted object\n");
243
244 // Do not attempt to actually delete the object. See previous comments.
245
246 END_TEST;
247 }
248
249 BEGIN_TEST_CASE(ref_counted_tests)
250 RUN_NAMED_TEST("Ref Counted (adoption validation on)", ref_counted_test<true>)
251 RUN_NAMED_TEST("Ref Counted (adoption validation off)", ref_counted_test<false>)
252 RUN_NAMED_TEST("Make Ref Counted (adoption validation on)", make_ref_counted_test<true>)
253 RUN_NAMED_TEST("Make Ref Counted (adoption validation off)", make_ref_counted_test<false>)
254 RUN_NAMED_TEST("Wrapping dead pointer should assert", wrap_dead_pointer_asserts)
255 RUN_NAMED_TEST("Extra release should assert", extra_release_asserts)
256 RUN_NAMED_TEST("Wrapping zero-count pointer should assert", wrap_after_last_release_asserts)
257 RUN_NAMED_TEST("AddRef on unadopted object should assert", unadopted_add_ref_asserts)
258 RUN_NAMED_TEST("Release on unadopted object should assert", unadopted_release_asserts)
259 END_TEST_CASE(ref_counted_tests);
260