Skip to content

Commit 30ad0c9

Browse files
committedJun 26, 2016
[tsan] Intercept libcxx __release_shared to avoid false positive with weak_ptrs and destructors in C++
There is a "well-known" TSan false positive when using C++ weak_ptr/shared_ptr and code in destructors, e.g. described at <https://llvm.org/bugs/show_bug.cgi?id=22324>. The "standard" solution is to build and use a TSan-instrumented version of libcxx, which is not trivial for end-users. This patch tries a different approach (on OS X): It adds an interceptor for the specific function in libc++.dylib, which implements the atomic operation that needs to be visible to TSan. Differential Revision: http://reviews.llvm.org/D21609 llvm-svn: 273806
1 parent 9d08642 commit 30ad0c9

File tree

5 files changed

+209
-0
lines changed

5 files changed

+209
-0
lines changed
 

‎compiler-rt/lib/tsan/rtl/tsan_flags.inc

+2
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,5 @@ TSAN_FLAG(bool, die_after_fork, true,
7878
TSAN_FLAG(const char *, suppressions, "", "Suppressions file name.")
7979
TSAN_FLAG(bool, ignore_interceptors_accesses, false,
8080
"Ignore reads and writes from all interceptors.")
81+
TSAN_FLAG(bool, shared_ptr_interceptor, true,
82+
"Track atomic reference counting in libc++ shared_ptr and weak_ptr.")

‎compiler-rt/lib/tsan/rtl/tsan_interceptors_mac.cc

+46
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,52 @@ TSAN_INTERCEPTOR(void, xpc_connection_send_message_with_reply,
273273
(connection, message, replyq, new_handler);
274274
}
275275

276+
// On macOS, libc++ is always linked dynamically, so intercepting works the
277+
// usual way.
278+
#define STDCXX_INTERCEPTOR TSAN_INTERCEPTOR
279+
280+
namespace {
281+
struct fake_shared_weak_count {
282+
volatile a64 shared_owners;
283+
volatile a64 shared_weak_owners;
284+
virtual void _unused_0x0() = 0;
285+
virtual void _unused_0x8() = 0;
286+
virtual void on_zero_shared() = 0;
287+
virtual void _unused_0x18() = 0;
288+
virtual void on_zero_shared_weak() = 0;
289+
};
290+
} // namespace
291+
292+
// This adds a libc++ interceptor for:
293+
// void __shared_weak_count::__release_shared() _NOEXCEPT;
294+
// Shared and weak pointers in C++ maintain reference counts via atomics in
295+
// libc++.dylib, which are TSan-invisible, and this leads to false positives in
296+
// destructor code. This interceptor re-implements the whole function so that
297+
// the mo_acq_rel semantics of the atomic decrement are visible.
298+
//
299+
// Unfortunately, this interceptor cannot simply Acquire/Release some sync
300+
// object and call the original function, because it would have a race between
301+
// the sync and the destruction of the object. Calling both under a lock will
302+
// not work because the destructor can invoke this interceptor again (and even
303+
// in a different thread, so recursive locks don't help).
304+
STDCXX_INTERCEPTOR(void, _ZNSt3__119__shared_weak_count16__release_sharedEv,
305+
fake_shared_weak_count *o) {
306+
if (!flags()->shared_ptr_interceptor)
307+
return REAL(_ZNSt3__119__shared_weak_count16__release_sharedEv)(o);
308+
309+
SCOPED_TSAN_INTERCEPTOR(_ZNSt3__119__shared_weak_count16__release_sharedEv,
310+
o);
311+
if (__tsan_atomic64_fetch_add(&o->shared_owners, -1, mo_release) == 0) {
312+
Acquire(thr, pc, (uptr)&o->shared_owners);
313+
o->on_zero_shared();
314+
if (__tsan_atomic64_fetch_add(&o->shared_weak_owners, -1, mo_release) ==
315+
0) {
316+
Acquire(thr, pc, (uptr)&o->shared_weak_owners);
317+
o->on_zero_shared_weak();
318+
}
319+
}
320+
}
321+
276322
} // namespace __tsan
277323

278324
#endif // SANITIZER_MAC
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// RUN: %clang_tsan %s -o %t -framework Foundation
2+
// RUN: %env_tsan_opts=ignore_interceptors_accesses=1 %run %t 2>&1 | FileCheck %s
3+
4+
#import <Foundation/Foundation.h>
5+
6+
#import <memory>
7+
8+
struct InnerStruct {
9+
~InnerStruct() {
10+
fprintf(stderr, "~InnerStruct\n");
11+
}
12+
};
13+
14+
struct MyStruct {
15+
std::shared_ptr<InnerStruct> inner_object;
16+
~MyStruct() {
17+
fprintf(stderr, "~MyStruct\n");
18+
}
19+
};
20+
21+
int main(int argc, const char *argv[]) {
22+
fprintf(stderr, "Hello world.\n");
23+
24+
{
25+
std::shared_ptr<MyStruct> shared(new MyStruct());
26+
shared->inner_object = std::shared_ptr<InnerStruct>(new InnerStruct());
27+
}
28+
29+
fprintf(stderr, "Done.\n");
30+
}
31+
32+
// CHECK: Hello world.
33+
// CHECK: ~MyStruct
34+
// CHECK: ~InnerStruct
35+
// CHECK: Done.
36+
// CHECK-NOT: WARNING: ThreadSanitizer
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// RUN: %clang_tsan %s -o %t -framework Foundation
2+
// RUN: %env_tsan_opts=ignore_interceptors_accesses=1 %run %t 2>&1 | FileCheck %s
3+
4+
#import <Foundation/Foundation.h>
5+
6+
#import <assert.h>
7+
#import <memory>
8+
#import <stdatomic.h>
9+
10+
_Atomic(long) shared_call_counter = 0;
11+
_Atomic(long) weak_call_counter = 0;
12+
_Atomic(long) destructor_counter = 0;
13+
_Atomic(long) weak_destroyed_counter = 0;
14+
15+
struct MyStruct {
16+
_Atomic(long) self_counter = 0;
17+
virtual void shared_call() {
18+
atomic_fetch_add_explicit(&self_counter, 1, memory_order_relaxed);
19+
atomic_fetch_add_explicit(&shared_call_counter, 1, memory_order_relaxed);
20+
}
21+
virtual void weak_call() {
22+
atomic_fetch_add_explicit(&weak_call_counter, 1, memory_order_relaxed);
23+
}
24+
virtual ~MyStruct() {
25+
long n = self_counter;
26+
assert(n == 1000);
27+
atomic_fetch_add_explicit(&destructor_counter, 1, memory_order_relaxed);
28+
}
29+
};
30+
31+
int main(int argc, const char *argv[]) {
32+
fprintf(stderr, "Hello world.\n");
33+
34+
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
35+
36+
dispatch_group_t g = dispatch_group_create();
37+
38+
for (int i = 0; i < 1000; i++) {
39+
std::shared_ptr<MyStruct> shared(new MyStruct());
40+
std::weak_ptr<MyStruct> weak(shared);
41+
42+
dispatch_group_async(g, q, ^{
43+
for (int j = 0; j < 1000; j++) {
44+
std::shared_ptr<MyStruct> shared_copy(shared);
45+
shared_copy->shared_call();
46+
}
47+
});
48+
dispatch_group_async(g, q, ^{
49+
for (int j = 0; j < 1000; j++) {
50+
std::shared_ptr<MyStruct> weak_copy = weak.lock();
51+
if (weak_copy) {
52+
weak_copy->weak_call();
53+
} else {
54+
atomic_fetch_add_explicit(&weak_destroyed_counter, 1, memory_order_relaxed);
55+
break;
56+
}
57+
}
58+
});
59+
}
60+
61+
dispatch_group_wait(g, DISPATCH_TIME_FOREVER);
62+
63+
fprintf(stderr, "shared_call_counter = %ld\n", shared_call_counter);
64+
fprintf(stderr, "weak_call_counter = %ld\n", weak_call_counter);
65+
fprintf(stderr, "destructor_counter = %ld\n", destructor_counter);
66+
fprintf(stderr, "weak_destroyed_counter = %ld\n", weak_destroyed_counter);
67+
68+
fprintf(stderr, "Done.\n");
69+
}
70+
71+
// CHECK: Hello world.
72+
// CHECK: shared_call_counter = 1000000
73+
// CHECK: destructor_counter = 1000
74+
// CHECK: Done.
75+
// CHECK-NOT: WARNING: ThreadSanitizer
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// RUN: %clang_tsan %s -o %t -framework Foundation
2+
// RUN: %env_tsan_opts=ignore_interceptors_accesses=1 %run %t 2>&1 | FileCheck %s
3+
4+
#import <Foundation/Foundation.h>
5+
6+
#import <memory>
7+
8+
#import "../test.h"
9+
10+
long my_global;
11+
12+
struct MyStruct {
13+
void setGlobal() {
14+
my_global = 42;
15+
}
16+
~MyStruct() {
17+
my_global = 43;
18+
}
19+
};
20+
21+
int main(int argc, const char *argv[]) {
22+
fprintf(stderr, "Hello world.\n");
23+
print_address("addr=", 1, &my_global);
24+
barrier_init(&barrier, 2);
25+
26+
std::shared_ptr<MyStruct> shared(new MyStruct());
27+
28+
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
29+
30+
std::weak_ptr<MyStruct> weak(shared);
31+
32+
dispatch_async(q, ^{
33+
{
34+
std::shared_ptr<MyStruct> strong = weak.lock();
35+
if (!strong) exit(1);
36+
37+
strong->setGlobal();
38+
}
39+
barrier_wait(&barrier);
40+
});
41+
42+
barrier_wait(&barrier);
43+
shared.reset();
44+
45+
fprintf(stderr, "Done.\n");
46+
}
47+
48+
// CHECK: Hello world.
49+
// CHECK: Done.
50+
// CHECK-NOT: WARNING: ThreadSanitizer

0 commit comments

Comments
 (0)