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