tsan: add mutexsets to reports
With this change reports say what mutexes the threads hold around the racy memory accesses. llvm-svn: 169493
This commit is contained in:
parent
dbe7d7e965
commit
fd5ebcd1b0
|
@ -35,9 +35,9 @@ int main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CHECK: WARNING: ThreadSanitizer: heap-use-after-free
|
// CHECK: WARNING: ThreadSanitizer: heap-use-after-free
|
||||||
// CHECK: Write of size 4 at {{.*}} by main thread:
|
// CHECK: Write of size 4 at {{.*}} by main thread{{.*}}:
|
||||||
// CHECK: #0 Thread2
|
// CHECK: #0 Thread2
|
||||||
// CHECK: #1 main
|
// CHECK: #1 main
|
||||||
// CHECK: Previous write of size 8 at {{.*}} by thread 1:
|
// CHECK: Previous write of size 8 at {{.*}} by thread T1{{.*}}:
|
||||||
// CHECK: #0 free
|
// CHECK: #0 free
|
||||||
// CHECK: #1 Thread1
|
// CHECK: #1 Thread1
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
// RUN: %clang_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
int Global;
|
||||||
|
|
||||||
|
extern "C" void AnnotateIgnoreWritesBegin(const char *f, int l);
|
||||||
|
extern "C" void AnnotateIgnoreWritesEnd(const char *f, int l);
|
||||||
|
extern "C" void AnnotateIgnoreReadsBegin(const char *f, int l);
|
||||||
|
extern "C" void AnnotateIgnoreReadsEnd(const char *f, int l);
|
||||||
|
|
||||||
|
void *Thread(void *x) {
|
||||||
|
AnnotateIgnoreWritesBegin(__FILE__, __LINE__);
|
||||||
|
AnnotateIgnoreReadsBegin(__FILE__, __LINE__);
|
||||||
|
Global = 42;
|
||||||
|
AnnotateIgnoreReadsEnd(__FILE__, __LINE__);
|
||||||
|
AnnotateIgnoreWritesEnd(__FILE__, __LINE__);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
pthread_t t;
|
||||||
|
pthread_create(&t, 0, Thread, 0);
|
||||||
|
usleep(100000);
|
||||||
|
Global = 43;
|
||||||
|
pthread_join(t, 0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK-NOT: ThreadSanitizer
|
|
@ -32,9 +32,9 @@ int main() {
|
||||||
|
|
||||||
// CHECK: addr=[[ADDR:0x[0-9,a-f]+]]
|
// CHECK: addr=[[ADDR:0x[0-9,a-f]+]]
|
||||||
// CHECK: WARNING: ThreadSanitizer: data race
|
// CHECK: WARNING: ThreadSanitizer: data race
|
||||||
// CHECK: Write of size 1 at [[ADDR]] by thread 2:
|
// CHECK: Write of size 1 at [[ADDR]] by thread T2:
|
||||||
// CHECK: #0 memcpy
|
// CHECK: #0 memcpy
|
||||||
// CHECK: #1 Thread2
|
// CHECK: #1 Thread2
|
||||||
// CHECK: Previous write of size 1 at [[ADDR]] by thread 1:
|
// CHECK: Previous write of size 1 at [[ADDR]] by thread T1:
|
||||||
// CHECK: #0 memcpy
|
// CHECK: #0 memcpy
|
||||||
// CHECK: #1 Thread1
|
// CHECK: #1 Thread1
|
||||||
|
|
|
@ -32,5 +32,5 @@ int main() {
|
||||||
// CHECK: ptr1=[[PTR1:0x[0-9,a-f]+]]
|
// CHECK: ptr1=[[PTR1:0x[0-9,a-f]+]]
|
||||||
// CHECK: ptr2=[[PTR2:0x[0-9,a-f]+]]
|
// CHECK: ptr2=[[PTR2:0x[0-9,a-f]+]]
|
||||||
// CHECK: WARNING: ThreadSanitizer: data race
|
// CHECK: WARNING: ThreadSanitizer: data race
|
||||||
// CHECK: Write of size 1 at [[PTR2]] by thread 2:
|
// CHECK: Write of size 1 at [[PTR2]] by thread T2:
|
||||||
// CHECK: Previous write of size 4 at [[PTR1]] by thread 1:
|
// CHECK: Previous write of size 4 at [[PTR1]] by thread T1:
|
||||||
|
|
|
@ -32,5 +32,5 @@ int main() {
|
||||||
// CHECK: ptr1=[[PTR1:0x[0-9,a-f]+]]
|
// CHECK: ptr1=[[PTR1:0x[0-9,a-f]+]]
|
||||||
// CHECK: ptr2=[[PTR2:0x[0-9,a-f]+]]
|
// CHECK: ptr2=[[PTR2:0x[0-9,a-f]+]]
|
||||||
// CHECK: WARNING: ThreadSanitizer: data race
|
// CHECK: WARNING: ThreadSanitizer: data race
|
||||||
// CHECK: Write of size 4 at [[PTR1]] by thread 1:
|
// CHECK: Write of size 4 at [[PTR1]] by thread T1:
|
||||||
// CHECK: Previous write of size 1 at [[PTR2]] by thread 2:
|
// CHECK: Previous write of size 1 at [[PTR2]] by thread T2:
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
int Global;
|
||||||
|
pthread_mutex_t mtx;
|
||||||
|
|
||||||
|
void *Thread1(void *x) {
|
||||||
|
usleep(100*1000);
|
||||||
|
pthread_mutex_lock(&mtx);
|
||||||
|
Global++;
|
||||||
|
pthread_mutex_unlock(&mtx);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *Thread2(void *x) {
|
||||||
|
Global--;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
pthread_mutex_init(&mtx, 0);
|
||||||
|
pthread_t t[2];
|
||||||
|
pthread_create(&t[0], NULL, Thread1, NULL);
|
||||||
|
pthread_create(&t[1], NULL, Thread2, NULL);
|
||||||
|
pthread_join(t[0], NULL);
|
||||||
|
pthread_join(t[1], NULL);
|
||||||
|
pthread_mutex_destroy(&mtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK: WARNING: ThreadSanitizer: data race
|
||||||
|
// CHECK: Write of size 4 at {{.*}} by thread T1 (mutexes: write M1):
|
||||||
|
// CHECK: Previous write of size 4 at {{.*}} by thread T2:
|
||||||
|
// CHECK: Mutex M1 created at:
|
||||||
|
// CHECK: #0 pthread_mutex_init
|
||||||
|
// CHECK: #1 main {{.*}}/mutexset1.cc:23
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
int Global;
|
||||||
|
pthread_mutex_t mtx;
|
||||||
|
|
||||||
|
void *Thread1(void *x) {
|
||||||
|
pthread_mutex_lock(&mtx);
|
||||||
|
Global++;
|
||||||
|
pthread_mutex_unlock(&mtx);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *Thread2(void *x) {
|
||||||
|
usleep(100*1000);
|
||||||
|
Global--;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
pthread_mutex_init(&mtx, 0);
|
||||||
|
pthread_t t[2];
|
||||||
|
pthread_create(&t[0], NULL, Thread1, NULL);
|
||||||
|
pthread_create(&t[1], NULL, Thread2, NULL);
|
||||||
|
pthread_join(t[0], NULL);
|
||||||
|
pthread_join(t[1], NULL);
|
||||||
|
pthread_mutex_destroy(&mtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK: WARNING: ThreadSanitizer: data race
|
||||||
|
// CHECK: Write of size 4 at {{.*}} by thread T2:
|
||||||
|
// CHECK: Previous write of size 4 at {{.*}} by thread T1 (mutexes: write M1):
|
||||||
|
// CHECK: Mutex M1 created at:
|
||||||
|
// CHECK: #0 pthread_mutex_init
|
||||||
|
// CHECK: #1 main {{.*}}/mutexset2.cc:23
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
int Global;
|
||||||
|
pthread_mutex_t mtx1;
|
||||||
|
pthread_mutex_t mtx2;
|
||||||
|
|
||||||
|
void *Thread1(void *x) {
|
||||||
|
usleep(100*1000);
|
||||||
|
pthread_mutex_lock(&mtx1);
|
||||||
|
pthread_mutex_lock(&mtx2);
|
||||||
|
Global++;
|
||||||
|
pthread_mutex_unlock(&mtx2);
|
||||||
|
pthread_mutex_unlock(&mtx1);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *Thread2(void *x) {
|
||||||
|
Global--;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
pthread_mutex_init(&mtx1, 0);
|
||||||
|
pthread_mutex_init(&mtx2, 0);
|
||||||
|
pthread_t t[2];
|
||||||
|
pthread_create(&t[0], NULL, Thread1, NULL);
|
||||||
|
pthread_create(&t[1], NULL, Thread2, NULL);
|
||||||
|
pthread_join(t[0], NULL);
|
||||||
|
pthread_join(t[1], NULL);
|
||||||
|
pthread_mutex_destroy(&mtx1);
|
||||||
|
pthread_mutex_destroy(&mtx2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK: WARNING: ThreadSanitizer: data race
|
||||||
|
// CHECK: Write of size 4 at {{.*}} by thread T1 (mutexes: write M1, write M2):
|
||||||
|
// CHECK: Previous write of size 4 at {{.*}} by thread T2:
|
||||||
|
// CHECK: Mutex M1 created at:
|
||||||
|
// CHECK: #0 pthread_mutex_init
|
||||||
|
// CHECK: #1 main {{.*}}/mutexset3.cc:26
|
||||||
|
// CHECK: Mutex M2 created at:
|
||||||
|
// CHECK: #0 pthread_mutex_init
|
||||||
|
// CHECK: #1 main {{.*}}/mutexset3.cc:27
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
int Global;
|
||||||
|
pthread_mutex_t mtx1;
|
||||||
|
pthread_mutex_t mtx2;
|
||||||
|
|
||||||
|
void *Thread1(void *x) {
|
||||||
|
pthread_mutex_lock(&mtx1);
|
||||||
|
pthread_mutex_lock(&mtx2);
|
||||||
|
Global++;
|
||||||
|
pthread_mutex_unlock(&mtx2);
|
||||||
|
pthread_mutex_unlock(&mtx1);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *Thread2(void *x) {
|
||||||
|
usleep(100*1000);
|
||||||
|
Global--;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
pthread_mutex_init(&mtx1, 0);
|
||||||
|
pthread_mutex_init(&mtx2, 0);
|
||||||
|
pthread_t t[2];
|
||||||
|
pthread_create(&t[0], NULL, Thread1, NULL);
|
||||||
|
pthread_create(&t[1], NULL, Thread2, NULL);
|
||||||
|
pthread_join(t[0], NULL);
|
||||||
|
pthread_join(t[1], NULL);
|
||||||
|
pthread_mutex_destroy(&mtx1);
|
||||||
|
pthread_mutex_destroy(&mtx2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK: WARNING: ThreadSanitizer: data race
|
||||||
|
// CHECK: Write of size 4 at {{.*}} by thread T2:
|
||||||
|
// CHECK: Previous write of size 4 at {{.*}} by thread T1
|
||||||
|
// CHECK: (mutexes: write M1, write M2):
|
||||||
|
// CHECK: Mutex M1 created at:
|
||||||
|
// CHECK: #0 pthread_mutex_init
|
||||||
|
// CHECK: #1 main {{.*}}/mutexset4.cc:26
|
||||||
|
// CHECK: Mutex M2 created at:
|
||||||
|
// CHECK: #0 pthread_mutex_init
|
||||||
|
// CHECK: #1 main {{.*}}/mutexset4.cc:27
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
int Global;
|
||||||
|
pthread_mutex_t mtx1;
|
||||||
|
pthread_mutex_t mtx2;
|
||||||
|
|
||||||
|
void *Thread1(void *x) {
|
||||||
|
usleep(100*1000);
|
||||||
|
pthread_mutex_lock(&mtx1);
|
||||||
|
Global++;
|
||||||
|
pthread_mutex_unlock(&mtx1);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *Thread2(void *x) {
|
||||||
|
pthread_mutex_lock(&mtx2);
|
||||||
|
Global--;
|
||||||
|
pthread_mutex_unlock(&mtx2);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
pthread_mutex_init(&mtx1, 0);
|
||||||
|
pthread_mutex_init(&mtx2, 0);
|
||||||
|
pthread_t t[2];
|
||||||
|
pthread_create(&t[0], NULL, Thread1, NULL);
|
||||||
|
pthread_create(&t[1], NULL, Thread2, NULL);
|
||||||
|
pthread_join(t[0], NULL);
|
||||||
|
pthread_join(t[1], NULL);
|
||||||
|
pthread_mutex_destroy(&mtx1);
|
||||||
|
pthread_mutex_destroy(&mtx2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK: WARNING: ThreadSanitizer: data race
|
||||||
|
// CHECK: Write of size 4 at {{.*}} by thread T1 (mutexes: write M1):
|
||||||
|
// CHECK: Previous write of size 4 at {{.*}} by thread T2 (mutexes: write M2):
|
||||||
|
// CHECK: Mutex M1 created at:
|
||||||
|
// CHECK: #0 pthread_mutex_init
|
||||||
|
// CHECK: #1 main {{.*}}/mutexset5.cc:26
|
||||||
|
// CHECK: Mutex M2 created at:
|
||||||
|
// CHECK: #0 pthread_mutex_init
|
||||||
|
// CHECK: #1 main {{.*}}/mutexset5.cc:27
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
int Global;
|
||||||
|
pthread_mutex_t mtx1;
|
||||||
|
pthread_spinlock_t mtx2;
|
||||||
|
pthread_rwlock_t mtx3;
|
||||||
|
|
||||||
|
void *Thread1(void *x) {
|
||||||
|
usleep(100*1000);
|
||||||
|
pthread_mutex_lock(&mtx1);
|
||||||
|
Global++;
|
||||||
|
pthread_mutex_unlock(&mtx1);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *Thread2(void *x) {
|
||||||
|
pthread_mutex_lock(&mtx1);
|
||||||
|
pthread_mutex_unlock(&mtx1);
|
||||||
|
pthread_spin_lock(&mtx2);
|
||||||
|
pthread_rwlock_rdlock(&mtx3);
|
||||||
|
Global--;
|
||||||
|
pthread_spin_unlock(&mtx2);
|
||||||
|
pthread_rwlock_unlock(&mtx3);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
pthread_mutex_init(&mtx1, 0);
|
||||||
|
pthread_spin_init(&mtx2, 0);
|
||||||
|
pthread_rwlock_init(&mtx3, 0);
|
||||||
|
pthread_t t[2];
|
||||||
|
pthread_create(&t[0], NULL, Thread1, NULL);
|
||||||
|
pthread_create(&t[1], NULL, Thread2, NULL);
|
||||||
|
pthread_join(t[0], NULL);
|
||||||
|
pthread_join(t[1], NULL);
|
||||||
|
pthread_mutex_destroy(&mtx1);
|
||||||
|
pthread_spin_destroy(&mtx2);
|
||||||
|
pthread_rwlock_destroy(&mtx3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK: WARNING: ThreadSanitizer: data race
|
||||||
|
// CHECK: Write of size 4 at {{.*}} by thread T1 (mutexes: write M1):
|
||||||
|
// CHECK: Previous write of size 4 at {{.*}} by thread T2
|
||||||
|
// CHECK: (mutexes: write M2, read M3):
|
||||||
|
// CHECK: Mutex M1 created at:
|
||||||
|
// CHECK: #1 main {{.*}}/mutexset6.cc:31
|
||||||
|
// CHECK: Mutex M2 created at:
|
||||||
|
// CHECK: #1 main {{.*}}/mutexset6.cc:32
|
||||||
|
// CHECK: Mutex M3 created at:
|
||||||
|
// CHECK: #1 main {{.*}}/mutexset6.cc:33
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
int Global;
|
||||||
|
|
||||||
|
void *Thread1(void *x) {
|
||||||
|
usleep(100*1000);
|
||||||
|
Global++;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *Thread2(void *x) {
|
||||||
|
pthread_mutex_t mtx;
|
||||||
|
pthread_mutex_init(&mtx, 0);
|
||||||
|
pthread_mutex_lock(&mtx);
|
||||||
|
Global--;
|
||||||
|
pthread_mutex_unlock(&mtx);
|
||||||
|
pthread_mutex_destroy(&mtx);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
pthread_t t[2];
|
||||||
|
pthread_create(&t[0], NULL, Thread1, NULL);
|
||||||
|
pthread_create(&t[1], NULL, Thread2, NULL);
|
||||||
|
pthread_join(t[0], NULL);
|
||||||
|
pthread_join(t[1], NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK: WARNING: ThreadSanitizer: data race
|
||||||
|
// CHECK: Write of size 4 at {{.*}} by thread T1:
|
||||||
|
// CHECK: Previous write of size 4 at {{.*}} by thread T2
|
||||||
|
// CHECK: (mutexes: write M{{[0-9]+}}):
|
||||||
|
// CHECK: Mutex M{{[0-9]+}} is already destroyed
|
||||||
|
// CHECK-NOT: Mutex {{.*}} created at
|
||||||
|
|
|
@ -37,11 +37,11 @@ int main() {
|
||||||
// CHECK: addr=[[ADDR:0x[0-9,a-f]+]]
|
// CHECK: addr=[[ADDR:0x[0-9,a-f]+]]
|
||||||
// CHECK: WARNING: ThreadSanitizer: data race
|
// CHECK: WARNING: ThreadSanitizer: data race
|
||||||
// ...
|
// ...
|
||||||
// CHECK: Location is heap block of size 99 at [[ADDR]] allocated by thread 1:
|
// CHECK: Location is heap block of size 99 at [[ADDR]] allocated by thread T1:
|
||||||
// CHCEKL #0 malloc
|
// CHCEKL #0 malloc
|
||||||
// CHECK: #1 alloc
|
// CHECK: #1 alloc
|
||||||
// CHECK: #2 AllocThread
|
// CHECK: #2 AllocThread
|
||||||
// ...
|
// ...
|
||||||
// CHECK: Thread 1 (tid={{.*}}, finished) created at:
|
// CHECK: Thread T1 (tid={{.*}}, finished) created at:
|
||||||
// CHECK: #0 pthread_create
|
// CHECK: #0 pthread_create
|
||||||
// CHECK: #1 main
|
// CHECK: #1 main
|
||||||
|
|
|
@ -34,9 +34,9 @@ int main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CHECK: WARNING: ThreadSanitizer: data race
|
// CHECK: WARNING: ThreadSanitizer: data race
|
||||||
// CHECK-NEXT: Read of size 1 at {{.*}} by thread 2:
|
// CHECK-NEXT: Read of size 1 at {{.*}} by thread T2:
|
||||||
// CHECK-NEXT: #0 pthread_mutex_lock
|
// CHECK-NEXT: #0 pthread_mutex_lock
|
||||||
// CHECK-NEXT: #1 Thread2{{.*}} {{.*}}race_on_mutex.c:20{{(:3)?}} ({{.*}})
|
// CHECK-NEXT: #1 Thread2{{.*}} {{.*}}race_on_mutex.c:20{{(:3)?}} ({{.*}})
|
||||||
// CHECK: Previous write of size 1 at {{.*}} by thread 1:
|
// CHECK: Previous write of size 1 at {{.*}} by thread T1:
|
||||||
// CHECK-NEXT: #0 pthread_mutex_init {{.*}} ({{.*}})
|
// CHECK-NEXT: #0 pthread_mutex_init {{.*}} ({{.*}})
|
||||||
// CHECK-NEXT: #1 Thread1{{.*}} {{.*}}race_on_mutex.c:11{{(:3)?}} ({{.*}})
|
// CHECK-NEXT: #1 Thread1{{.*}} {{.*}}race_on_mutex.c:11{{(:3)?}} ({{.*}})
|
||||||
|
|
|
@ -34,10 +34,10 @@ int main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CHECK: WARNING: ThreadSanitizer: data race
|
// CHECK: WARNING: ThreadSanitizer: data race
|
||||||
// CHECK: Write of size 4 at {{.*}} by thread 2:
|
// CHECK: Write of size 4 at {{.*}} by thread T2:
|
||||||
// CHECK: Previous write of size 4 at {{.*}} by thread 1:
|
// CHECK: Previous write of size 4 at {{.*}} by thread T1:
|
||||||
// CHECK: #0 foobar
|
// CHECK: #0 foobar
|
||||||
// CHECK: #1 Thread1
|
// CHECK: #1 Thread1
|
||||||
// CHECK: Thread 1 (tid={{.*}}, finished) created at:
|
// CHECK: Thread T1 (tid={{.*}}, finished) created at:
|
||||||
// CHECK: #0 pthread_create
|
// CHECK: #0 pthread_create
|
||||||
// CHECK: #1 main
|
// CHECK: #1 main
|
||||||
|
|
|
@ -48,19 +48,19 @@ int main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CHECK: WARNING: ThreadSanitizer: data race
|
// CHECK: WARNING: ThreadSanitizer: data race
|
||||||
// CHECK-NEXT: Write of size 4 at {{.*}} by thread 1:
|
// CHECK-NEXT: Write of size 4 at {{.*}} by thread T1:
|
||||||
// CHECK-NEXT: #0 foo1{{.*}} {{.*}}simple_stack.c:9{{(:3)?}} ({{.*}})
|
// CHECK-NEXT: #0 foo1{{.*}} {{.*}}simple_stack.c:9{{(:3)?}} ({{.*}})
|
||||||
// CHECK-NEXT: #1 bar1{{.*}} {{.*}}simple_stack.c:14{{(:3)?}} ({{.*}})
|
// CHECK-NEXT: #1 bar1{{.*}} {{.*}}simple_stack.c:14{{(:3)?}} ({{.*}})
|
||||||
// CHECK-NEXT: #2 Thread1{{.*}} {{.*}}simple_stack.c:28{{(:3)?}} ({{.*}})
|
// CHECK-NEXT: #2 Thread1{{.*}} {{.*}}simple_stack.c:28{{(:3)?}} ({{.*}})
|
||||||
// CHECK: Previous read of size 4 at {{.*}} by thread 2:
|
// CHECK: Previous read of size 4 at {{.*}} by thread T2:
|
||||||
// CHECK-NEXT: #0 foo2{{.*}} {{.*}}simple_stack.c:18{{(:26)?}} ({{.*}})
|
// CHECK-NEXT: #0 foo2{{.*}} {{.*}}simple_stack.c:18{{(:26)?}} ({{.*}})
|
||||||
// CHECK-NEXT: #1 bar2{{.*}} {{.*}}simple_stack.c:23{{(:3)?}} ({{.*}})
|
// CHECK-NEXT: #1 bar2{{.*}} {{.*}}simple_stack.c:23{{(:3)?}} ({{.*}})
|
||||||
// CHECK-NEXT: #2 Thread2{{.*}} {{.*}}simple_stack.c:33{{(:3)?}} ({{.*}})
|
// CHECK-NEXT: #2 Thread2{{.*}} {{.*}}simple_stack.c:33{{(:3)?}} ({{.*}})
|
||||||
// CHECK: Thread 1 (tid={{.*}}, running) created at:
|
// CHECK: Thread T1 (tid={{.*}}, running) created at:
|
||||||
// CHECK-NEXT: #0 pthread_create {{.*}} ({{.*}})
|
// CHECK-NEXT: #0 pthread_create {{.*}} ({{.*}})
|
||||||
// CHECK-NEXT: #1 StartThread{{.*}} {{.*}}simple_stack.c:38{{(:3)?}} ({{.*}})
|
// CHECK-NEXT: #1 StartThread{{.*}} {{.*}}simple_stack.c:38{{(:3)?}} ({{.*}})
|
||||||
// CHECK-NEXT: #2 main{{.*}} {{.*}}simple_stack.c:43{{(:3)?}} ({{.*}})
|
// CHECK-NEXT: #2 main{{.*}} {{.*}}simple_stack.c:43{{(:3)?}} ({{.*}})
|
||||||
// CHECK: Thread 2 ({{.*}}) created at:
|
// CHECK: Thread T2 ({{.*}}) created at:
|
||||||
// CHECK-NEXT: #0 pthread_create {{.*}} ({{.*}})
|
// CHECK-NEXT: #0 pthread_create {{.*}} ({{.*}})
|
||||||
// CHECK-NEXT: #1 StartThread{{.*}} {{.*}}simple_stack.c:38{{(:3)?}} ({{.*}})
|
// CHECK-NEXT: #1 StartThread{{.*}} {{.*}}simple_stack.c:38{{(:3)?}} ({{.*}})
|
||||||
// CHECK-NEXT: #2 main{{.*}} {{.*}}simple_stack.c:44{{(:3)?}} ({{.*}})
|
// CHECK-NEXT: #2 main{{.*}} {{.*}}simple_stack.c:44{{(:3)?}} ({{.*}})
|
||||||
|
|
|
@ -43,7 +43,7 @@ int main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CHECK: WARNING: ThreadSanitizer: data race
|
// CHECK: WARNING: ThreadSanitizer: data race
|
||||||
// CHECK-NEXT: Write of size 4 at {{.*}} by thread 1:
|
// CHECK-NEXT: Write of size 4 at {{.*}} by thread T1:
|
||||||
// CHECK-NEXT: #0 foo1{{.*}} {{.*}}simple_stack2.cc:9{{(:3)?}} ({{.*}})
|
// CHECK-NEXT: #0 foo1{{.*}} {{.*}}simple_stack2.cc:9{{(:3)?}} ({{.*}})
|
||||||
// CHECK-NEXT: #1 bar1{{.*}} {{.*}}simple_stack2.cc:16{{(:3)?}} ({{.*}})
|
// CHECK-NEXT: #1 bar1{{.*}} {{.*}}simple_stack2.cc:16{{(:3)?}} ({{.*}})
|
||||||
// CHECK-NEXT: #2 Thread1{{.*}} {{.*}}simple_stack2.cc:34{{(:3)?}} ({{.*}})
|
// CHECK-NEXT: #2 Thread1{{.*}} {{.*}}simple_stack2.cc:34{{(:3)?}} ({{.*}})
|
||||||
|
|
|
@ -29,6 +29,6 @@ int main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CHECK: WARNING: ThreadSanitizer: data race
|
// CHECK: WARNING: ThreadSanitizer: data race
|
||||||
// CHECK: Thread 1 'Thread1'
|
// CHECK: Thread T1 'Thread1'
|
||||||
// CHECK: Thread 2 'Thread2'
|
// CHECK: Thread T2 'Thread2'
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ int main(int argc, char *argv[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CHECK: WARNING: ThreadSanitizer: data race
|
// CHECK: WARNING: ThreadSanitizer: data race
|
||||||
// CHECK: Write of size 4 at {{.*}} by thread 1:
|
// CHECK: Write of size 4 at {{.*}} by thread T1{{.*}}:
|
||||||
// CHECK: #0 Thread1(void*) {{.*}}write_in_reader_lock.cc:13
|
// CHECK: #0 Thread1(void*) {{.*}}write_in_reader_lock.cc:13
|
||||||
// CHECK: Previous read of size 4 at {{.*}} by main thread:
|
// CHECK: Previous read of size 4 at {{.*}} by main thread{{.*}}:
|
||||||
// CHECK: #0 main {{.*}}write_in_reader_lock.cc:23
|
// CHECK: #0 main {{.*}}write_in_reader_lock.cc:23
|
||||||
|
|
|
@ -139,6 +139,12 @@ T RoundDown(T p, u64 align) {
|
||||||
return (T)((u64)p & ~(align - 1));
|
return (T)((u64)p & ~(align - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Zeroizes high part, returns 'bits' lsb bits.
|
||||||
|
template<typename T>
|
||||||
|
T GetLsb(T v, int bits) {
|
||||||
|
return (T)((u64)v & ((1ull << bits) - 1));
|
||||||
|
}
|
||||||
|
|
||||||
struct MD5Hash {
|
struct MD5Hash {
|
||||||
u64 hash[2];
|
u64 hash[2];
|
||||||
bool operator==(const MD5Hash &other) const;
|
bool operator==(const MD5Hash &other) const;
|
||||||
|
|
|
@ -231,7 +231,7 @@ static T AtomicLoad(ThreadState *thr, uptr pc, const volatile T *a,
|
||||||
// Assume the access is atomic.
|
// Assume the access is atomic.
|
||||||
if (!IsAcquireOrder(mo) && sizeof(T) <= sizeof(a))
|
if (!IsAcquireOrder(mo) && sizeof(T) <= sizeof(a))
|
||||||
return *a;
|
return *a;
|
||||||
SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, (uptr)a, false);
|
SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, (uptr)a, false);
|
||||||
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
||||||
thr->clock.acquire(&s->clock);
|
thr->clock.acquire(&s->clock);
|
||||||
T v = *a;
|
T v = *a;
|
||||||
|
@ -253,7 +253,7 @@ static void AtomicStore(ThreadState *thr, uptr pc, volatile T *a, T v,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
__sync_synchronize();
|
__sync_synchronize();
|
||||||
SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, (uptr)a, true);
|
SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, (uptr)a, true);
|
||||||
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
||||||
thr->clock.ReleaseStore(&s->clock);
|
thr->clock.ReleaseStore(&s->clock);
|
||||||
*a = v;
|
*a = v;
|
||||||
|
@ -265,7 +265,7 @@ static void AtomicStore(ThreadState *thr, uptr pc, volatile T *a, T v,
|
||||||
|
|
||||||
template<typename T, T (*F)(volatile T *v, T op)>
|
template<typename T, T (*F)(volatile T *v, T op)>
|
||||||
static T AtomicRMW(ThreadState *thr, uptr pc, volatile T *a, T v, morder mo) {
|
static T AtomicRMW(ThreadState *thr, uptr pc, volatile T *a, T v, morder mo) {
|
||||||
SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, (uptr)a, true);
|
SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, (uptr)a, true);
|
||||||
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
||||||
if (IsAcqRelOrder(mo))
|
if (IsAcqRelOrder(mo))
|
||||||
thr->clock.acq_rel(&s->clock);
|
thr->clock.acq_rel(&s->clock);
|
||||||
|
@ -324,7 +324,7 @@ template<typename T>
|
||||||
static bool AtomicCAS(ThreadState *thr, uptr pc,
|
static bool AtomicCAS(ThreadState *thr, uptr pc,
|
||||||
volatile T *a, T *c, T v, morder mo, morder fmo) {
|
volatile T *a, T *c, T v, morder mo, morder fmo) {
|
||||||
(void)fmo; // Unused because llvm does not pass it yet.
|
(void)fmo; // Unused because llvm does not pass it yet.
|
||||||
SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, (uptr)a, true);
|
SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, (uptr)a, true);
|
||||||
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
||||||
if (IsAcqRelOrder(mo))
|
if (IsAcqRelOrder(mo))
|
||||||
thr->clock.acq_rel(&s->clock);
|
thr->clock.acq_rel(&s->clock);
|
||||||
|
|
|
@ -60,8 +60,9 @@ void *user_alloc(ThreadState *thr, uptr pc, uptr sz, uptr align) {
|
||||||
void *p = allocator()->Allocate(&thr->alloc_cache, sz, align);
|
void *p = allocator()->Allocate(&thr->alloc_cache, sz, align);
|
||||||
if (p == 0)
|
if (p == 0)
|
||||||
return 0;
|
return 0;
|
||||||
MBlock *b = (MBlock*)allocator()->GetMetaData(p);
|
MBlock *b = new(allocator()->GetMetaData(p)) MBlock;
|
||||||
b->size = sz;
|
b->size = sz;
|
||||||
|
b->head = 0;
|
||||||
b->alloc_tid = thr->unique_id;
|
b->alloc_tid = thr->unique_id;
|
||||||
b->alloc_stack_id = CurrentStackId(thr, pc);
|
b->alloc_stack_id = CurrentStackId(thr, pc);
|
||||||
if (CTX() && CTX()->initialized) {
|
if (CTX() && CTX()->initialized) {
|
||||||
|
@ -92,6 +93,7 @@ void user_free(ThreadState *thr, uptr pc, void *p) {
|
||||||
if (CTX() && CTX()->initialized && thr->in_rtl == 1) {
|
if (CTX() && CTX()->initialized && thr->in_rtl == 1) {
|
||||||
MemoryRangeFreed(thr, pc, (uptr)p, b->size);
|
MemoryRangeFreed(thr, pc, (uptr)p, b->size);
|
||||||
}
|
}
|
||||||
|
b->~MBlock();
|
||||||
allocator()->Deallocate(&thr->alloc_cache, p);
|
allocator()->Deallocate(&thr->alloc_cache, p);
|
||||||
SignalUnsafeCall(thr, pc);
|
SignalUnsafeCall(thr, pc);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,12 +30,13 @@ static MutexType CanLockTab[MutexTypeCount][MutexTypeCount] = {
|
||||||
/*0 MutexTypeInvalid*/ {},
|
/*0 MutexTypeInvalid*/ {},
|
||||||
/*1 MutexTypeTrace*/ {MutexTypeLeaf},
|
/*1 MutexTypeTrace*/ {MutexTypeLeaf},
|
||||||
/*2 MutexTypeThreads*/ {MutexTypeReport},
|
/*2 MutexTypeThreads*/ {MutexTypeReport},
|
||||||
/*3 MutexTypeReport*/ {},
|
/*3 MutexTypeReport*/ {MutexTypeSyncTab, MutexTypeMBlock},
|
||||||
/*4 MutexTypeSyncVar*/ {},
|
/*4 MutexTypeSyncVar*/ {},
|
||||||
/*5 MutexTypeSyncTab*/ {MutexTypeSyncVar},
|
/*5 MutexTypeSyncTab*/ {MutexTypeSyncVar},
|
||||||
/*6 MutexTypeSlab*/ {MutexTypeLeaf},
|
/*6 MutexTypeSlab*/ {MutexTypeLeaf},
|
||||||
/*7 MutexTypeAnnotations*/ {},
|
/*7 MutexTypeAnnotations*/ {},
|
||||||
/*8 MutexTypeAtExit*/ {MutexTypeSyncTab},
|
/*8 MutexTypeAtExit*/ {MutexTypeSyncTab},
|
||||||
|
/*9 MutexTypeMBlock*/ {MutexTypeSyncVar},
|
||||||
};
|
};
|
||||||
|
|
||||||
static bool CanLockAdj[MutexTypeCount][MutexTypeCount];
|
static bool CanLockAdj[MutexTypeCount][MutexTypeCount];
|
||||||
|
@ -122,6 +123,8 @@ DeadlockDetector::DeadlockDetector() {
|
||||||
|
|
||||||
void DeadlockDetector::Lock(MutexType t) {
|
void DeadlockDetector::Lock(MutexType t) {
|
||||||
// Printf("LOCK %d @%zu\n", t, seq_ + 1);
|
// Printf("LOCK %d @%zu\n", t, seq_ + 1);
|
||||||
|
CHECK_GT(t, MutexTypeInvalid);
|
||||||
|
CHECK_LT(t, MutexTypeCount);
|
||||||
u64 max_seq = 0;
|
u64 max_seq = 0;
|
||||||
u64 max_idx = MutexTypeInvalid;
|
u64 max_idx = MutexTypeInvalid;
|
||||||
for (int i = 0; i != MutexTypeCount; i++) {
|
for (int i = 0; i != MutexTypeCount; i++) {
|
||||||
|
|
|
@ -29,6 +29,7 @@ enum MutexType {
|
||||||
MutexTypeSlab,
|
MutexTypeSlab,
|
||||||
MutexTypeAnnotations,
|
MutexTypeAnnotations,
|
||||||
MutexTypeAtExit,
|
MutexTypeAtExit,
|
||||||
|
MutexTypeMBlock,
|
||||||
|
|
||||||
// This must be the last.
|
// This must be the last.
|
||||||
MutexTypeCount
|
MutexTypeCount
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
//===-- tsan_mutexset.cc --------------------------------------------------===//
|
||||||
|
//
|
||||||
|
// The LLVM Compiler Infrastructure
|
||||||
|
//
|
||||||
|
// This file is distributed under the University of Illinois Open Source
|
||||||
|
// License. See LICENSE.TXT for details.
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
//
|
||||||
|
// This file is a part of ThreadSanitizer (TSan), a race detector.
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
#include "tsan_mutexset.h"
|
||||||
|
#include "tsan_rtl.h"
|
||||||
|
|
||||||
|
namespace __tsan {
|
||||||
|
|
||||||
|
const uptr MutexSet::kMaxSize;
|
||||||
|
|
||||||
|
MutexSet::MutexSet() {
|
||||||
|
size_ = 0;
|
||||||
|
internal_memset(&descs_, 0, sizeof(descs_));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MutexSet::Add(u64 id, bool write, u64 epoch) {
|
||||||
|
// Look up existing mutex with the same id.
|
||||||
|
for (uptr i = 0; i < size_; i++) {
|
||||||
|
if (descs_[i].id == id) {
|
||||||
|
descs_[i].count++;
|
||||||
|
descs_[i].epoch = epoch;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// On overflow, find the oldest mutex and drop it.
|
||||||
|
if (size_ == kMaxSize) {
|
||||||
|
u64 minepoch = (u64)-1;
|
||||||
|
u64 mini = (u64)-1;
|
||||||
|
for (uptr i = 0; i < size_; i++) {
|
||||||
|
if (descs_[i].epoch < minepoch) {
|
||||||
|
minepoch = descs_[i].epoch;
|
||||||
|
mini = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RemovePos(mini);
|
||||||
|
CHECK_EQ(size_, kMaxSize - 1);
|
||||||
|
}
|
||||||
|
// Add new mutex descriptor.
|
||||||
|
descs_[size_].id = id;
|
||||||
|
descs_[size_].write = write;
|
||||||
|
descs_[size_].epoch = epoch;
|
||||||
|
descs_[size_].count = 1;
|
||||||
|
size_++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MutexSet::Del(u64 id, bool write) {
|
||||||
|
for (uptr i = 0; i < size_; i++) {
|
||||||
|
if (descs_[i].id == id) {
|
||||||
|
if (--descs_[i].count == 0)
|
||||||
|
RemovePos(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MutexSet::Remove(u64 id) {
|
||||||
|
for (uptr i = 0; i < size_; i++) {
|
||||||
|
if (descs_[i].id == id) {
|
||||||
|
RemovePos(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MutexSet::RemovePos(uptr i) {
|
||||||
|
CHECK_LT(i, size_);
|
||||||
|
descs_[i] = descs_[size_ - 1];
|
||||||
|
size_--;
|
||||||
|
}
|
||||||
|
|
||||||
|
uptr MutexSet::Size() const {
|
||||||
|
return size_;
|
||||||
|
}
|
||||||
|
|
||||||
|
MutexSet::Desc MutexSet::Get(uptr i) const {
|
||||||
|
CHECK_LT(i, size_);
|
||||||
|
return descs_[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace __tsan
|
|
@ -0,0 +1,65 @@
|
||||||
|
//===-- tsan_mutexset.h -----------------------------------------*- C++ -*-===//
|
||||||
|
//
|
||||||
|
// The LLVM Compiler Infrastructure
|
||||||
|
//
|
||||||
|
// This file is distributed under the University of Illinois Open Source
|
||||||
|
// License. See LICENSE.TXT for details.
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
//
|
||||||
|
// This file is a part of ThreadSanitizer (TSan), a race detector.
|
||||||
|
//
|
||||||
|
// MutexSet holds the set of mutexes currently held by a thread.
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
#ifndef TSAN_MUTEXSET_H
|
||||||
|
#define TSAN_MUTEXSET_H
|
||||||
|
|
||||||
|
#include "tsan_defs.h"
|
||||||
|
|
||||||
|
namespace __tsan {
|
||||||
|
|
||||||
|
class MutexSet {
|
||||||
|
public:
|
||||||
|
// Holds limited number of mutexes.
|
||||||
|
// The oldest mutexes are discarded on overflow.
|
||||||
|
static const uptr kMaxSize = 64;
|
||||||
|
struct Desc {
|
||||||
|
u64 id;
|
||||||
|
u64 epoch;
|
||||||
|
int count;
|
||||||
|
bool write;
|
||||||
|
};
|
||||||
|
|
||||||
|
MutexSet();
|
||||||
|
// The 'id' is obtained from SyncVar::GetId().
|
||||||
|
void Add(u64 id, bool write, u64 epoch);
|
||||||
|
void Del(u64 id, bool write);
|
||||||
|
void Remove(u64 id); // Removes the mutex completely (if it's destroyed).
|
||||||
|
uptr Size() const;
|
||||||
|
Desc Get(uptr i) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
#ifndef TSAN_GO
|
||||||
|
uptr size_;
|
||||||
|
Desc descs_[kMaxSize];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void RemovePos(uptr i);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Go does not have mutexes, so do not spend memory and time.
|
||||||
|
// (Go sync.Mutex is actually a semaphore -- can be unlocked
|
||||||
|
// in different goroutine).
|
||||||
|
#ifdef TSAN_GO
|
||||||
|
MutexSet::MutexSet() {}
|
||||||
|
void MutexSet::Add(u64 id, bool write, u64 epoch) {}
|
||||||
|
void MutexSet::Del(u64 id, bool write) {}
|
||||||
|
void MutexSet::Remove(u64 id) {}
|
||||||
|
void MutexSet::RemovePos(uptr i) {}
|
||||||
|
uptr MutexSet::Size() const { return 0; }
|
||||||
|
MutexSet::Desc MutexSet::Get(uptr i) const { return Desc(); }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace __tsan
|
||||||
|
|
||||||
|
#endif // TSAN_REPORT_H
|
|
@ -25,6 +25,10 @@ ReportDesc::ReportDesc()
|
||||||
, sleep() {
|
, sleep() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ReportMop::ReportMop()
|
||||||
|
: mset(MBlockReportMutex) {
|
||||||
|
}
|
||||||
|
|
||||||
ReportDesc::~ReportDesc() {
|
ReportDesc::~ReportDesc() {
|
||||||
// FIXME(dvyukov): it must be leaking a lot of memory.
|
// FIXME(dvyukov): it must be leaking a lot of memory.
|
||||||
}
|
}
|
||||||
|
@ -67,15 +71,27 @@ void PrintStack(const ReportStack *ent) {
|
||||||
Printf("\n");
|
Printf("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void PrintMutexSet(Vector<ReportMopMutex> const& mset) {
|
||||||
|
for (uptr i = 0; i < mset.Size(); i++) {
|
||||||
|
if (i == 0)
|
||||||
|
Printf(" (mutexes:");
|
||||||
|
const ReportMopMutex m = mset[i];
|
||||||
|
Printf(" %s M%llu", m.write ? "write" : "read", m.id);
|
||||||
|
Printf(i == mset.Size() - 1 ? ")" : ",");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void PrintMop(const ReportMop *mop, bool first) {
|
static void PrintMop(const ReportMop *mop, bool first) {
|
||||||
Printf(" %s of size %d at %p",
|
Printf(" %s of size %d at %p",
|
||||||
(first ? (mop->write ? "Write" : "Read")
|
(first ? (mop->write ? "Write" : "Read")
|
||||||
: (mop->write ? "Previous write" : "Previous read")),
|
: (mop->write ? "Previous write" : "Previous read")),
|
||||||
mop->size, (void*)mop->addr);
|
mop->size, (void*)mop->addr);
|
||||||
if (mop->tid == 0)
|
if (mop->tid == 0)
|
||||||
Printf(" by main thread:\n");
|
Printf(" by main thread");
|
||||||
else
|
else
|
||||||
Printf(" by thread %d:\n", mop->tid);
|
Printf(" by thread T%d", mop->tid);
|
||||||
|
PrintMutexSet(mop->mset);
|
||||||
|
Printf(":\n");
|
||||||
PrintStack(mop->stack);
|
PrintStack(mop->stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,24 +106,26 @@ static void PrintLocation(const ReportLocation *loc) {
|
||||||
if (loc->tid == 0)
|
if (loc->tid == 0)
|
||||||
Printf(" by main thread:\n");
|
Printf(" by main thread:\n");
|
||||||
else
|
else
|
||||||
Printf(" by thread %d:\n", loc->tid);
|
Printf(" by thread T%d:\n", loc->tid);
|
||||||
PrintStack(loc->stack);
|
PrintStack(loc->stack);
|
||||||
} else if (loc->type == ReportLocationStack) {
|
} else if (loc->type == ReportLocationStack) {
|
||||||
Printf(" Location is stack of thread %d:\n\n", loc->tid);
|
Printf(" Location is stack of thread T%d:\n\n", loc->tid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void PrintMutex(const ReportMutex *rm) {
|
static void PrintMutex(const ReportMutex *rm) {
|
||||||
if (rm->stack == 0)
|
if (rm->destroyed) {
|
||||||
return;
|
Printf(" Mutex M%llu is already destroyed.\n\n", rm->id);
|
||||||
Printf(" Mutex %d created at:\n", rm->id);
|
} else {
|
||||||
|
Printf(" Mutex M%llu created at:\n", rm->id);
|
||||||
PrintStack(rm->stack);
|
PrintStack(rm->stack);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void PrintThread(const ReportThread *rt) {
|
static void PrintThread(const ReportThread *rt) {
|
||||||
if (rt->id == 0) // Little sense in describing the main thread.
|
if (rt->id == 0) // Little sense in describing the main thread.
|
||||||
return;
|
return;
|
||||||
Printf(" Thread %d", rt->id);
|
Printf(" Thread T%d", rt->id);
|
||||||
if (rt->name)
|
if (rt->name)
|
||||||
Printf(" '%s'", rt->name);
|
Printf(" '%s'", rt->name);
|
||||||
Printf(" (tid=%zu, %s)", rt->pid, rt->running ? "running" : "finished");
|
Printf(" (tid=%zu, %s)", rt->pid, rt->running ? "running" : "finished");
|
||||||
|
|
|
@ -38,14 +38,20 @@ struct ReportStack {
|
||||||
int col;
|
int col;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ReportMopMutex {
|
||||||
|
u64 id;
|
||||||
|
bool write;
|
||||||
|
};
|
||||||
|
|
||||||
struct ReportMop {
|
struct ReportMop {
|
||||||
int tid;
|
int tid;
|
||||||
uptr addr;
|
uptr addr;
|
||||||
int size;
|
int size;
|
||||||
bool write;
|
bool write;
|
||||||
int nmutex;
|
Vector<ReportMopMutex> mset;
|
||||||
int *mutex;
|
|
||||||
ReportStack *stack;
|
ReportStack *stack;
|
||||||
|
|
||||||
|
ReportMop();
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ReportLocationType {
|
enum ReportLocationType {
|
||||||
|
@ -76,7 +82,8 @@ struct ReportThread {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ReportMutex {
|
struct ReportMutex {
|
||||||
int id;
|
u64 id;
|
||||||
|
bool destroyed;
|
||||||
ReportStack *stack;
|
ReportStack *stack;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -291,6 +291,7 @@ void TraceSwitch(ThreadState *thr) {
|
||||||
TraceHeader *hdr = &thr->trace.headers[trace];
|
TraceHeader *hdr = &thr->trace.headers[trace];
|
||||||
hdr->epoch0 = thr->fast_state.epoch();
|
hdr->epoch0 = thr->fast_state.epoch();
|
||||||
hdr->stack0.ObtainCurrent(thr, 0);
|
hdr->stack0.ObtainCurrent(thr, 0);
|
||||||
|
hdr->mset0 = thr->mset;
|
||||||
thr->nomalloc--;
|
thr->nomalloc--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
#include "tsan_vector.h"
|
#include "tsan_vector.h"
|
||||||
#include "tsan_report.h"
|
#include "tsan_report.h"
|
||||||
#include "tsan_platform.h"
|
#include "tsan_platform.h"
|
||||||
|
#include "tsan_mutexset.h"
|
||||||
|
|
||||||
#if SANITIZER_WORDSIZE != 64
|
#if SANITIZER_WORDSIZE != 64
|
||||||
# error "ThreadSanitizer is supported only on 64-bit platforms"
|
# error "ThreadSanitizer is supported only on 64-bit platforms"
|
||||||
|
@ -50,6 +51,10 @@ struct MBlock {
|
||||||
u32 alloc_tid;
|
u32 alloc_tid;
|
||||||
u32 alloc_stack_id;
|
u32 alloc_stack_id;
|
||||||
SyncVar *head;
|
SyncVar *head;
|
||||||
|
|
||||||
|
MBlock()
|
||||||
|
: mtx(MutexTypeMBlock, StatMtxMBlock) {
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifndef TSAN_GO
|
#ifndef TSAN_GO
|
||||||
|
@ -300,6 +305,7 @@ struct ThreadState {
|
||||||
uptr *shadow_stack;
|
uptr *shadow_stack;
|
||||||
uptr *shadow_stack_end;
|
uptr *shadow_stack_end;
|
||||||
#endif
|
#endif
|
||||||
|
MutexSet mset;
|
||||||
ThreadClock clock;
|
ThreadClock clock;
|
||||||
#ifndef TSAN_GO
|
#ifndef TSAN_GO
|
||||||
AllocatorCache alloc_cache;
|
AllocatorCache alloc_cache;
|
||||||
|
@ -447,7 +453,8 @@ class ScopedReport {
|
||||||
~ScopedReport();
|
~ScopedReport();
|
||||||
|
|
||||||
void AddStack(const StackTrace *stack);
|
void AddStack(const StackTrace *stack);
|
||||||
void AddMemoryAccess(uptr addr, Shadow s, const StackTrace *stack);
|
void AddMemoryAccess(uptr addr, Shadow s, const StackTrace *stack,
|
||||||
|
const MutexSet *mset);
|
||||||
void AddThread(const ThreadContext *tctx);
|
void AddThread(const ThreadContext *tctx);
|
||||||
void AddMutex(const SyncVar *s);
|
void AddMutex(const SyncVar *s);
|
||||||
void AddLocation(uptr addr, uptr size);
|
void AddLocation(uptr addr, uptr size);
|
||||||
|
@ -459,11 +466,13 @@ class ScopedReport {
|
||||||
Context *ctx_;
|
Context *ctx_;
|
||||||
ReportDesc *rep_;
|
ReportDesc *rep_;
|
||||||
|
|
||||||
|
void AddMutex(u64 id);
|
||||||
|
|
||||||
ScopedReport(const ScopedReport&);
|
ScopedReport(const ScopedReport&);
|
||||||
void operator = (const ScopedReport&);
|
void operator = (const ScopedReport&);
|
||||||
};
|
};
|
||||||
|
|
||||||
void RestoreStack(int tid, const u64 epoch, StackTrace *stk);
|
void RestoreStack(int tid, const u64 epoch, StackTrace *stk, MutexSet *mset);
|
||||||
|
|
||||||
void StatAggregate(u64 *dst, u64 *src);
|
void StatAggregate(u64 *dst, u64 *src);
|
||||||
void StatOutput(u64 *stat);
|
void StatOutput(u64 *stat);
|
||||||
|
@ -577,7 +586,10 @@ uptr TraceParts();
|
||||||
|
|
||||||
extern "C" void __tsan_trace_switch();
|
extern "C" void __tsan_trace_switch();
|
||||||
void ALWAYS_INLINE INLINE TraceAddEvent(ThreadState *thr, FastState fs,
|
void ALWAYS_INLINE INLINE TraceAddEvent(ThreadState *thr, FastState fs,
|
||||||
EventType typ, uptr addr) {
|
EventType typ, u64 addr) {
|
||||||
|
DCHECK_GE((int)typ, 0);
|
||||||
|
DCHECK_LE((int)typ, 7);
|
||||||
|
DCHECK_EQ(GetLsb(addr, 61), addr);
|
||||||
StatInc(thr, StatEvents);
|
StatInc(thr, StatEvents);
|
||||||
u64 pos = fs.GetTracePos();
|
u64 pos = fs.GetTracePos();
|
||||||
if (UNLIKELY((pos % kTracePartSize) == 0)) {
|
if (UNLIKELY((pos % kTracePartSize) == 0)) {
|
||||||
|
|
|
@ -28,7 +28,7 @@ void MutexCreate(ThreadState *thr, uptr pc, uptr addr,
|
||||||
StatInc(thr, StatMutexCreate);
|
StatInc(thr, StatMutexCreate);
|
||||||
if (!linker_init && IsAppMem(addr))
|
if (!linker_init && IsAppMem(addr))
|
||||||
MemoryWrite1Byte(thr, pc, addr);
|
MemoryWrite1Byte(thr, pc, addr);
|
||||||
SyncVar *s = ctx->synctab.GetAndLock(thr, pc, addr, true);
|
SyncVar *s = ctx->synctab.GetOrCreateAndLock(thr, pc, addr, true);
|
||||||
s->is_rw = rw;
|
s->is_rw = rw;
|
||||||
s->is_recursive = recursive;
|
s->is_recursive = recursive;
|
||||||
s->is_linker_init = linker_init;
|
s->is_linker_init = linker_init;
|
||||||
|
@ -61,11 +61,12 @@ void MutexDestroy(ThreadState *thr, uptr pc, uptr addr) {
|
||||||
trace.ObtainCurrent(thr, pc);
|
trace.ObtainCurrent(thr, pc);
|
||||||
rep.AddStack(&trace);
|
rep.AddStack(&trace);
|
||||||
FastState last(s->last_lock);
|
FastState last(s->last_lock);
|
||||||
RestoreStack(last.tid(), last.epoch(), &trace);
|
RestoreStack(last.tid(), last.epoch(), &trace, 0);
|
||||||
rep.AddStack(&trace);
|
rep.AddStack(&trace);
|
||||||
rep.AddLocation(s->addr, 1);
|
rep.AddLocation(s->addr, 1);
|
||||||
OutputReport(ctx, rep);
|
OutputReport(ctx, rep);
|
||||||
}
|
}
|
||||||
|
thr->mset.Remove(s->GetId());
|
||||||
DestroyAndFree(s);
|
DestroyAndFree(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,9 +75,9 @@ void MutexLock(ThreadState *thr, uptr pc, uptr addr) {
|
||||||
DPrintf("#%d: MutexLock %zx\n", thr->tid, addr);
|
DPrintf("#%d: MutexLock %zx\n", thr->tid, addr);
|
||||||
if (IsAppMem(addr))
|
if (IsAppMem(addr))
|
||||||
MemoryRead1Byte(thr, pc, addr);
|
MemoryRead1Byte(thr, pc, addr);
|
||||||
|
SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, true);
|
||||||
thr->fast_state.IncrementEpoch();
|
thr->fast_state.IncrementEpoch();
|
||||||
TraceAddEvent(thr, thr->fast_state, EventTypeLock, addr);
|
TraceAddEvent(thr, thr->fast_state, EventTypeLock, s->GetId());
|
||||||
SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true);
|
|
||||||
if (s->owner_tid == SyncVar::kInvalidTid) {
|
if (s->owner_tid == SyncVar::kInvalidTid) {
|
||||||
CHECK_EQ(s->recursion, 0);
|
CHECK_EQ(s->recursion, 0);
|
||||||
s->owner_tid = thr->tid;
|
s->owner_tid = thr->tid;
|
||||||
|
@ -98,6 +99,7 @@ void MutexLock(ThreadState *thr, uptr pc, uptr addr) {
|
||||||
StatInc(thr, StatMutexRecLock);
|
StatInc(thr, StatMutexRecLock);
|
||||||
}
|
}
|
||||||
s->recursion++;
|
s->recursion++;
|
||||||
|
thr->mset.Add(s->GetId(), true, thr->fast_state.epoch());
|
||||||
s->mtx.Unlock();
|
s->mtx.Unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,9 +108,9 @@ void MutexUnlock(ThreadState *thr, uptr pc, uptr addr) {
|
||||||
DPrintf("#%d: MutexUnlock %zx\n", thr->tid, addr);
|
DPrintf("#%d: MutexUnlock %zx\n", thr->tid, addr);
|
||||||
if (IsAppMem(addr))
|
if (IsAppMem(addr))
|
||||||
MemoryRead1Byte(thr, pc, addr);
|
MemoryRead1Byte(thr, pc, addr);
|
||||||
|
SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, true);
|
||||||
thr->fast_state.IncrementEpoch();
|
thr->fast_state.IncrementEpoch();
|
||||||
TraceAddEvent(thr, thr->fast_state, EventTypeUnlock, addr);
|
TraceAddEvent(thr, thr->fast_state, EventTypeUnlock, s->GetId());
|
||||||
SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true);
|
|
||||||
if (s->recursion == 0) {
|
if (s->recursion == 0) {
|
||||||
if (!s->is_broken) {
|
if (!s->is_broken) {
|
||||||
s->is_broken = true;
|
s->is_broken = true;
|
||||||
|
@ -134,6 +136,7 @@ void MutexUnlock(ThreadState *thr, uptr pc, uptr addr) {
|
||||||
StatInc(thr, StatMutexRecUnlock);
|
StatInc(thr, StatMutexRecUnlock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
thr->mset.Del(s->GetId(), true);
|
||||||
s->mtx.Unlock();
|
s->mtx.Unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,9 +146,9 @@ void MutexReadLock(ThreadState *thr, uptr pc, uptr addr) {
|
||||||
StatInc(thr, StatMutexReadLock);
|
StatInc(thr, StatMutexReadLock);
|
||||||
if (IsAppMem(addr))
|
if (IsAppMem(addr))
|
||||||
MemoryRead1Byte(thr, pc, addr);
|
MemoryRead1Byte(thr, pc, addr);
|
||||||
|
SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, false);
|
||||||
thr->fast_state.IncrementEpoch();
|
thr->fast_state.IncrementEpoch();
|
||||||
TraceAddEvent(thr, thr->fast_state, EventTypeRLock, addr);
|
TraceAddEvent(thr, thr->fast_state, EventTypeRLock, s->GetId());
|
||||||
SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, false);
|
|
||||||
if (s->owner_tid != SyncVar::kInvalidTid) {
|
if (s->owner_tid != SyncVar::kInvalidTid) {
|
||||||
Printf("ThreadSanitizer WARNING: read lock of a write locked mutex\n");
|
Printf("ThreadSanitizer WARNING: read lock of a write locked mutex\n");
|
||||||
PrintCurrentStack(thr, pc);
|
PrintCurrentStack(thr, pc);
|
||||||
|
@ -154,6 +157,7 @@ void MutexReadLock(ThreadState *thr, uptr pc, uptr addr) {
|
||||||
thr->clock.acquire(&s->clock);
|
thr->clock.acquire(&s->clock);
|
||||||
s->last_lock = thr->fast_state.raw();
|
s->last_lock = thr->fast_state.raw();
|
||||||
StatInc(thr, StatSyncAcquire);
|
StatInc(thr, StatSyncAcquire);
|
||||||
|
thr->mset.Add(s->GetId(), false, thr->fast_state.epoch());
|
||||||
s->mtx.ReadUnlock();
|
s->mtx.ReadUnlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,9 +167,9 @@ void MutexReadUnlock(ThreadState *thr, uptr pc, uptr addr) {
|
||||||
StatInc(thr, StatMutexReadUnlock);
|
StatInc(thr, StatMutexReadUnlock);
|
||||||
if (IsAppMem(addr))
|
if (IsAppMem(addr))
|
||||||
MemoryRead1Byte(thr, pc, addr);
|
MemoryRead1Byte(thr, pc, addr);
|
||||||
|
SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, true);
|
||||||
thr->fast_state.IncrementEpoch();
|
thr->fast_state.IncrementEpoch();
|
||||||
TraceAddEvent(thr, thr->fast_state, EventTypeRUnlock, addr);
|
TraceAddEvent(thr, thr->fast_state, EventTypeRUnlock, s->GetId());
|
||||||
SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true);
|
|
||||||
if (s->owner_tid != SyncVar::kInvalidTid) {
|
if (s->owner_tid != SyncVar::kInvalidTid) {
|
||||||
Printf("ThreadSanitizer WARNING: read unlock of a write "
|
Printf("ThreadSanitizer WARNING: read unlock of a write "
|
||||||
"locked mutex\n");
|
"locked mutex\n");
|
||||||
|
@ -176,6 +180,7 @@ void MutexReadUnlock(ThreadState *thr, uptr pc, uptr addr) {
|
||||||
thr->clock.release(&s->read_clock);
|
thr->clock.release(&s->read_clock);
|
||||||
StatInc(thr, StatSyncRelease);
|
StatInc(thr, StatSyncRelease);
|
||||||
s->mtx.Unlock();
|
s->mtx.Unlock();
|
||||||
|
thr->mset.Del(s->GetId(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MutexReadOrWriteUnlock(ThreadState *thr, uptr pc, uptr addr) {
|
void MutexReadOrWriteUnlock(ThreadState *thr, uptr pc, uptr addr) {
|
||||||
|
@ -183,18 +188,22 @@ void MutexReadOrWriteUnlock(ThreadState *thr, uptr pc, uptr addr) {
|
||||||
DPrintf("#%d: MutexReadOrWriteUnlock %zx\n", thr->tid, addr);
|
DPrintf("#%d: MutexReadOrWriteUnlock %zx\n", thr->tid, addr);
|
||||||
if (IsAppMem(addr))
|
if (IsAppMem(addr))
|
||||||
MemoryRead1Byte(thr, pc, addr);
|
MemoryRead1Byte(thr, pc, addr);
|
||||||
SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true);
|
SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, true);
|
||||||
|
bool write = true;
|
||||||
if (s->owner_tid == SyncVar::kInvalidTid) {
|
if (s->owner_tid == SyncVar::kInvalidTid) {
|
||||||
// Seems to be read unlock.
|
// Seems to be read unlock.
|
||||||
|
write = false;
|
||||||
StatInc(thr, StatMutexReadUnlock);
|
StatInc(thr, StatMutexReadUnlock);
|
||||||
thr->fast_state.IncrementEpoch();
|
thr->fast_state.IncrementEpoch();
|
||||||
TraceAddEvent(thr, thr->fast_state, EventTypeRUnlock, addr);
|
TraceAddEvent(thr, thr->fast_state, EventTypeRUnlock, s->GetId());
|
||||||
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
||||||
thr->fast_synch_epoch = thr->fast_state.epoch();
|
thr->fast_synch_epoch = thr->fast_state.epoch();
|
||||||
thr->clock.release(&s->read_clock);
|
thr->clock.release(&s->read_clock);
|
||||||
StatInc(thr, StatSyncRelease);
|
StatInc(thr, StatSyncRelease);
|
||||||
} else if (s->owner_tid == thr->tid) {
|
} else if (s->owner_tid == thr->tid) {
|
||||||
// Seems to be write unlock.
|
// Seems to be write unlock.
|
||||||
|
thr->fast_state.IncrementEpoch();
|
||||||
|
TraceAddEvent(thr, thr->fast_state, EventTypeUnlock, s->GetId());
|
||||||
CHECK_GT(s->recursion, 0);
|
CHECK_GT(s->recursion, 0);
|
||||||
s->recursion--;
|
s->recursion--;
|
||||||
if (s->recursion == 0) {
|
if (s->recursion == 0) {
|
||||||
|
@ -204,8 +213,6 @@ void MutexReadOrWriteUnlock(ThreadState *thr, uptr pc, uptr addr) {
|
||||||
// The sequence of events is quite tricky and doubled in several places.
|
// The sequence of events is quite tricky and doubled in several places.
|
||||||
// First, it's a bug to increment the epoch w/o writing to the trace.
|
// First, it's a bug to increment the epoch w/o writing to the trace.
|
||||||
// Then, the acquire/release logic can be factored out as well.
|
// Then, the acquire/release logic can be factored out as well.
|
||||||
thr->fast_state.IncrementEpoch();
|
|
||||||
TraceAddEvent(thr, thr->fast_state, EventTypeUnlock, addr);
|
|
||||||
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
||||||
thr->fast_synch_epoch = thr->fast_state.epoch();
|
thr->fast_synch_epoch = thr->fast_state.epoch();
|
||||||
thr->clock.ReleaseStore(&s->clock);
|
thr->clock.ReleaseStore(&s->clock);
|
||||||
|
@ -218,13 +225,14 @@ void MutexReadOrWriteUnlock(ThreadState *thr, uptr pc, uptr addr) {
|
||||||
Printf("ThreadSanitizer WARNING: mutex unlock by another thread\n");
|
Printf("ThreadSanitizer WARNING: mutex unlock by another thread\n");
|
||||||
PrintCurrentStack(thr, pc);
|
PrintCurrentStack(thr, pc);
|
||||||
}
|
}
|
||||||
|
thr->mset.Del(s->GetId(), write);
|
||||||
s->mtx.Unlock();
|
s->mtx.Unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Acquire(ThreadState *thr, uptr pc, uptr addr) {
|
void Acquire(ThreadState *thr, uptr pc, uptr addr) {
|
||||||
CHECK_GT(thr->in_rtl, 0);
|
CHECK_GT(thr->in_rtl, 0);
|
||||||
DPrintf("#%d: Acquire %zx\n", thr->tid, addr);
|
DPrintf("#%d: Acquire %zx\n", thr->tid, addr);
|
||||||
SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, false);
|
SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, false);
|
||||||
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
||||||
thr->clock.acquire(&s->clock);
|
thr->clock.acquire(&s->clock);
|
||||||
StatInc(thr, StatSyncAcquire);
|
StatInc(thr, StatSyncAcquire);
|
||||||
|
@ -248,7 +256,7 @@ void AcquireGlobal(ThreadState *thr, uptr pc) {
|
||||||
void Release(ThreadState *thr, uptr pc, uptr addr) {
|
void Release(ThreadState *thr, uptr pc, uptr addr) {
|
||||||
CHECK_GT(thr->in_rtl, 0);
|
CHECK_GT(thr->in_rtl, 0);
|
||||||
DPrintf("#%d: Release %zx\n", thr->tid, addr);
|
DPrintf("#%d: Release %zx\n", thr->tid, addr);
|
||||||
SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true);
|
SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, true);
|
||||||
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
||||||
thr->clock.release(&s->clock);
|
thr->clock.release(&s->clock);
|
||||||
StatInc(thr, StatSyncRelease);
|
StatInc(thr, StatSyncRelease);
|
||||||
|
@ -258,7 +266,7 @@ void Release(ThreadState *thr, uptr pc, uptr addr) {
|
||||||
void ReleaseStore(ThreadState *thr, uptr pc, uptr addr) {
|
void ReleaseStore(ThreadState *thr, uptr pc, uptr addr) {
|
||||||
CHECK_GT(thr->in_rtl, 0);
|
CHECK_GT(thr->in_rtl, 0);
|
||||||
DPrintf("#%d: ReleaseStore %zx\n", thr->tid, addr);
|
DPrintf("#%d: ReleaseStore %zx\n", thr->tid, addr);
|
||||||
SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true);
|
SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, true);
|
||||||
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
thr->clock.set(thr->tid, thr->fast_state.epoch());
|
||||||
thr->clock.ReleaseStore(&s->clock);
|
thr->clock.ReleaseStore(&s->clock);
|
||||||
StatInc(thr, StatSyncRelease);
|
StatInc(thr, StatSyncRelease);
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include "sanitizer_common/sanitizer_libc.h"
|
#include "sanitizer_common/sanitizer_libc.h"
|
||||||
#include "sanitizer_common/sanitizer_placement_new.h"
|
#include "sanitizer_common/sanitizer_placement_new.h"
|
||||||
#include "sanitizer_common/sanitizer_stackdepot.h"
|
#include "sanitizer_common/sanitizer_stackdepot.h"
|
||||||
|
#include "sanitizer_common/sanitizer_common.h"
|
||||||
#include "tsan_platform.h"
|
#include "tsan_platform.h"
|
||||||
#include "tsan_rtl.h"
|
#include "tsan_rtl.h"
|
||||||
#include "tsan_suppressions.h"
|
#include "tsan_suppressions.h"
|
||||||
|
@ -25,6 +26,8 @@
|
||||||
|
|
||||||
namespace __tsan {
|
namespace __tsan {
|
||||||
|
|
||||||
|
using namespace __sanitizer; // NOLINT
|
||||||
|
|
||||||
void TsanCheckFailed(const char *file, int line, const char *cond,
|
void TsanCheckFailed(const char *file, int line, const char *cond,
|
||||||
u64 v1, u64 v2) {
|
u64 v1, u64 v2) {
|
||||||
ScopedInRtl in_rtl;
|
ScopedInRtl in_rtl;
|
||||||
|
@ -134,7 +137,7 @@ void ScopedReport::AddStack(const StackTrace *stack) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScopedReport::AddMemoryAccess(uptr addr, Shadow s,
|
void ScopedReport::AddMemoryAccess(uptr addr, Shadow s,
|
||||||
const StackTrace *stack) {
|
const StackTrace *stack, const MutexSet *mset) {
|
||||||
void *mem = internal_alloc(MBlockReportMop, sizeof(ReportMop));
|
void *mem = internal_alloc(MBlockReportMop, sizeof(ReportMop));
|
||||||
ReportMop *mop = new(mem) ReportMop;
|
ReportMop *mop = new(mem) ReportMop;
|
||||||
rep_->mops.PushBack(mop);
|
rep_->mops.PushBack(mop);
|
||||||
|
@ -142,8 +145,27 @@ void ScopedReport::AddMemoryAccess(uptr addr, Shadow s,
|
||||||
mop->addr = addr + s.addr0();
|
mop->addr = addr + s.addr0();
|
||||||
mop->size = s.size();
|
mop->size = s.size();
|
||||||
mop->write = s.is_write();
|
mop->write = s.is_write();
|
||||||
mop->nmutex = 0;
|
|
||||||
mop->stack = SymbolizeStack(*stack);
|
mop->stack = SymbolizeStack(*stack);
|
||||||
|
for (uptr i = 0; i < mset->Size(); i++) {
|
||||||
|
MutexSet::Desc d = mset->Get(i);
|
||||||
|
u64 uid = 0;
|
||||||
|
uptr addr = SyncVar::SplitId(d.id, &uid);
|
||||||
|
SyncVar *s = ctx_->synctab.GetIfExistsAndLock(addr, false);
|
||||||
|
// Check that the mutex is still alive.
|
||||||
|
// Another mutex can be created at the same address,
|
||||||
|
// so check uid as well.
|
||||||
|
if (s && s->CheckId(uid)) {
|
||||||
|
ReportMopMutex mtx = {s->uid, d.write};
|
||||||
|
mop->mset.PushBack(mtx);
|
||||||
|
AddMutex(s);
|
||||||
|
} else {
|
||||||
|
ReportMopMutex mtx = {d.id, d.write};
|
||||||
|
mop->mset.PushBack(mtx);
|
||||||
|
AddMutex(d.id);
|
||||||
|
}
|
||||||
|
if (s)
|
||||||
|
s->mtx.ReadUnlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScopedReport::AddThread(const ThreadContext *tctx) {
|
void ScopedReport::AddThread(const ThreadContext *tctx) {
|
||||||
|
@ -175,13 +197,31 @@ static ThreadContext *FindThread(int unique_id) {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void ScopedReport::AddMutex(const SyncVar *s) {
|
void ScopedReport::AddMutex(const SyncVar *s) {
|
||||||
|
for (uptr i = 0; i < rep_->mutexes.Size(); i++) {
|
||||||
|
if (rep_->mutexes[i]->id == s->uid)
|
||||||
|
return;
|
||||||
|
}
|
||||||
void *mem = internal_alloc(MBlockReportMutex, sizeof(ReportMutex));
|
void *mem = internal_alloc(MBlockReportMutex, sizeof(ReportMutex));
|
||||||
ReportMutex *rm = new(mem) ReportMutex();
|
ReportMutex *rm = new(mem) ReportMutex();
|
||||||
rep_->mutexes.PushBack(rm);
|
rep_->mutexes.PushBack(rm);
|
||||||
rm->id = 42;
|
rm->id = s->uid;
|
||||||
|
rm->destroyed = false;
|
||||||
rm->stack = SymbolizeStack(s->creation_stack);
|
rm->stack = SymbolizeStack(s->creation_stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScopedReport::AddMutex(u64 id) {
|
||||||
|
for (uptr i = 0; i < rep_->mutexes.Size(); i++) {
|
||||||
|
if (rep_->mutexes[i]->id == id)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
void *mem = internal_alloc(MBlockReportMutex, sizeof(ReportMutex));
|
||||||
|
ReportMutex *rm = new(mem) ReportMutex();
|
||||||
|
rep_->mutexes.PushBack(rm);
|
||||||
|
rm->id = id;
|
||||||
|
rm->destroyed = true;
|
||||||
|
rm->stack = 0;
|
||||||
|
}
|
||||||
|
|
||||||
void ScopedReport::AddLocation(uptr addr, uptr size) {
|
void ScopedReport::AddLocation(uptr addr, uptr size) {
|
||||||
if (addr == 0)
|
if (addr == 0)
|
||||||
return;
|
return;
|
||||||
|
@ -248,7 +288,10 @@ const ReportDesc *ScopedReport::GetReport() const {
|
||||||
return rep_;
|
return rep_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RestoreStack(int tid, const u64 epoch, StackTrace *stk) {
|
void RestoreStack(int tid, const u64 epoch, StackTrace *stk, MutexSet *mset) {
|
||||||
|
// This function restores stack trace and mutex set for the thread/epoch.
|
||||||
|
// It does so by getting stack trace and mutex set at the beginning of
|
||||||
|
// trace part, and then replaying the trace till the given epoch.
|
||||||
ThreadContext *tctx = CTX()->threads[tid];
|
ThreadContext *tctx = CTX()->threads[tid];
|
||||||
if (tctx == 0)
|
if (tctx == 0)
|
||||||
return;
|
return;
|
||||||
|
@ -269,6 +312,7 @@ void RestoreStack(int tid, const u64 epoch, StackTrace *stk) {
|
||||||
TraceHeader* hdr = &trace->headers[partidx];
|
TraceHeader* hdr = &trace->headers[partidx];
|
||||||
if (epoch < hdr->epoch0)
|
if (epoch < hdr->epoch0)
|
||||||
return;
|
return;
|
||||||
|
const u64 epoch0 = RoundDown(epoch, TraceSize());
|
||||||
const u64 eend = epoch % TraceSize();
|
const u64 eend = epoch % TraceSize();
|
||||||
const u64 ebegin = RoundDown(eend, kTracePartSize);
|
const u64 ebegin = RoundDown(eend, kTracePartSize);
|
||||||
DPrintf("#%d: RestoreStack epoch=%zu ebegin=%zu eend=%zu partidx=%d\n",
|
DPrintf("#%d: RestoreStack epoch=%zu ebegin=%zu eend=%zu partidx=%d\n",
|
||||||
|
@ -278,12 +322,14 @@ void RestoreStack(int tid, const u64 epoch, StackTrace *stk) {
|
||||||
stack[i] = hdr->stack0.Get(i);
|
stack[i] = hdr->stack0.Get(i);
|
||||||
DPrintf2(" #%02lu: pc=%zx\n", i, stack[i]);
|
DPrintf2(" #%02lu: pc=%zx\n", i, stack[i]);
|
||||||
}
|
}
|
||||||
|
if (mset)
|
||||||
|
*mset = hdr->mset0;
|
||||||
uptr pos = hdr->stack0.Size();
|
uptr pos = hdr->stack0.Size();
|
||||||
Event *events = (Event*)GetThreadTrace(tid);
|
Event *events = (Event*)GetThreadTrace(tid);
|
||||||
for (uptr i = ebegin; i <= eend; i++) {
|
for (uptr i = ebegin; i <= eend; i++) {
|
||||||
Event ev = events[i];
|
Event ev = events[i];
|
||||||
EventType typ = (EventType)(ev >> 61);
|
EventType typ = (EventType)(ev >> 61);
|
||||||
uptr pc = (uptr)(ev & 0xffffffffffffull);
|
uptr pc = (uptr)(ev & ((1ull << 61) - 1));
|
||||||
DPrintf2(" %zu typ=%d pc=%zx\n", i, typ, pc);
|
DPrintf2(" %zu typ=%d pc=%zx\n", i, typ, pc);
|
||||||
if (typ == EventTypeMop) {
|
if (typ == EventTypeMop) {
|
||||||
stack[pos] = pc;
|
stack[pos] = pc;
|
||||||
|
@ -293,6 +339,17 @@ void RestoreStack(int tid, const u64 epoch, StackTrace *stk) {
|
||||||
if (pos > 0)
|
if (pos > 0)
|
||||||
pos--;
|
pos--;
|
||||||
}
|
}
|
||||||
|
if (mset) {
|
||||||
|
if (typ == EventTypeLock) {
|
||||||
|
mset->Add(pc, true, epoch0 + i);
|
||||||
|
} else if (typ == EventTypeUnlock) {
|
||||||
|
mset->Del(pc, true);
|
||||||
|
} else if (typ == EventTypeRLock) {
|
||||||
|
mset->Add(pc, false, epoch0 + i);
|
||||||
|
} else if (typ == EventTypeRUnlock) {
|
||||||
|
mset->Del(pc, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
for (uptr j = 0; j <= pos; j++)
|
for (uptr j = 0; j <= pos; j++)
|
||||||
DPrintf2(" #%zu: %zx\n", j, stack[j]);
|
DPrintf2(" #%zu: %zx\n", j, stack[j]);
|
||||||
}
|
}
|
||||||
|
@ -456,15 +513,18 @@ void ReportRace(ThreadState *thr) {
|
||||||
traces[0].ObtainCurrent(thr, toppc);
|
traces[0].ObtainCurrent(thr, toppc);
|
||||||
if (IsFiredSuppression(ctx, rep, traces[0]))
|
if (IsFiredSuppression(ctx, rep, traces[0]))
|
||||||
return;
|
return;
|
||||||
|
InternalScopedBuffer<MutexSet> mset2(1);
|
||||||
|
new(mset2.data()) MutexSet();
|
||||||
Shadow s2(thr->racy_state[1]);
|
Shadow s2(thr->racy_state[1]);
|
||||||
RestoreStack(s2.tid(), s2.epoch(), &traces[1]);
|
RestoreStack(s2.tid(), s2.epoch(), &traces[1], mset2.data());
|
||||||
|
|
||||||
if (HandleRacyStacks(thr, traces, addr_min, addr_max))
|
if (HandleRacyStacks(thr, traces, addr_min, addr_max))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (uptr i = 0; i < kMop; i++) {
|
for (uptr i = 0; i < kMop; i++) {
|
||||||
Shadow s(thr->racy_state[i]);
|
Shadow s(thr->racy_state[i]);
|
||||||
rep.AddMemoryAccess(addr, s, &traces[i]);
|
rep.AddMemoryAccess(addr, s, &traces[i],
|
||||||
|
i == 0 ? &thr->mset : mset2.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags()->suppress_java && IsJavaNonsense(rep.GetReport()))
|
if (flags()->suppress_java && IsJavaNonsense(rep.GetReport()))
|
||||||
|
|
|
@ -305,6 +305,7 @@ void ThreadJoin(ThreadState *thr, uptr pc, int tid) {
|
||||||
Printf("ThreadSanitizer: join of non-existent thread\n");
|
Printf("ThreadSanitizer: join of non-existent thread\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// FIXME(dvyukov): print message and continue (it's user error).
|
||||||
CHECK_EQ(tctx->detached, false);
|
CHECK_EQ(tctx->detached, false);
|
||||||
CHECK_EQ(tctx->status, ThreadStatusFinished);
|
CHECK_EQ(tctx->status, ThreadStatusFinished);
|
||||||
thr->clock.acquire(&tctx->sync);
|
thr->clock.acquire(&tctx->sync);
|
||||||
|
|
|
@ -253,6 +253,7 @@ void StatOutput(u64 *stat) {
|
||||||
name[StatMtxSlab] = " Slab ";
|
name[StatMtxSlab] = " Slab ";
|
||||||
name[StatMtxAtExit] = " Atexit ";
|
name[StatMtxAtExit] = " Atexit ";
|
||||||
name[StatMtxAnnotations] = " Annotations ";
|
name[StatMtxAnnotations] = " Annotations ";
|
||||||
|
name[StatMtxMBlock] = " MBlock ";
|
||||||
|
|
||||||
Printf("Statistics:\n");
|
Printf("Statistics:\n");
|
||||||
for (int i = 0; i < StatCnt; i++)
|
for (int i = 0; i < StatCnt; i++)
|
||||||
|
|
|
@ -255,6 +255,7 @@ enum StatType {
|
||||||
StatMtxSlab,
|
StatMtxSlab,
|
||||||
StatMtxAnnotations,
|
StatMtxAnnotations,
|
||||||
StatMtxAtExit,
|
StatMtxAtExit,
|
||||||
|
StatMtxMBlock,
|
||||||
|
|
||||||
// This must be the last.
|
// This must be the last.
|
||||||
StatCnt
|
StatCnt
|
||||||
|
|
|
@ -17,9 +17,10 @@
|
||||||
|
|
||||||
namespace __tsan {
|
namespace __tsan {
|
||||||
|
|
||||||
SyncVar::SyncVar(uptr addr)
|
SyncVar::SyncVar(uptr addr, u64 uid)
|
||||||
: mtx(MutexTypeSyncVar, StatMtxSyncVar)
|
: mtx(MutexTypeSyncVar, StatMtxSyncVar)
|
||||||
, addr(addr)
|
, addr(addr)
|
||||||
|
, uid(uid)
|
||||||
, owner_tid(kInvalidTid)
|
, owner_tid(kInvalidTid)
|
||||||
, last_lock()
|
, last_lock()
|
||||||
, recursion()
|
, recursion()
|
||||||
|
@ -47,8 +48,17 @@ SyncTab::~SyncTab() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SyncVar* SyncTab::GetAndLock(ThreadState *thr, uptr pc,
|
SyncVar* SyncTab::GetOrCreateAndLock(ThreadState *thr, uptr pc,
|
||||||
uptr addr, bool write_lock) {
|
uptr addr, bool write_lock) {
|
||||||
|
return GetAndLock(thr, pc, addr, write_lock, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncVar* SyncTab::GetIfExistsAndLock(uptr addr, bool write_lock) {
|
||||||
|
return GetAndLock(0, 0, addr, write_lock, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncVar* SyncTab::GetAndLock(ThreadState *thr, uptr pc,
|
||||||
|
uptr addr, bool write_lock, bool create) {
|
||||||
#ifndef TSAN_GO
|
#ifndef TSAN_GO
|
||||||
if (PrimaryAllocator::PointerIsMine((void*)addr)) {
|
if (PrimaryAllocator::PointerIsMine((void*)addr)) {
|
||||||
MBlock *b = user_mblock(thr, (void*)addr);
|
MBlock *b = user_mblock(thr, (void*)addr);
|
||||||
|
@ -59,9 +69,12 @@ SyncVar* SyncTab::GetAndLock(ThreadState *thr, uptr pc,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (res == 0) {
|
if (res == 0) {
|
||||||
|
if (!create)
|
||||||
|
return 0;
|
||||||
StatInc(thr, StatSyncCreated);
|
StatInc(thr, StatSyncCreated);
|
||||||
void *mem = internal_alloc(MBlockSync, sizeof(SyncVar));
|
void *mem = internal_alloc(MBlockSync, sizeof(SyncVar));
|
||||||
res = new(mem) SyncVar(addr);
|
const u64 uid = atomic_fetch_add(&uid_gen_, 1, memory_order_relaxed);
|
||||||
|
res = new(mem) SyncVar(addr, uid);
|
||||||
res->creation_stack.ObtainCurrent(thr, pc);
|
res->creation_stack.ObtainCurrent(thr, pc);
|
||||||
res->next = b->head;
|
res->next = b->head;
|
||||||
b->head = res;
|
b->head = res;
|
||||||
|
@ -87,6 +100,8 @@ SyncVar* SyncTab::GetAndLock(ThreadState *thr, uptr pc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!create)
|
||||||
|
return 0;
|
||||||
{
|
{
|
||||||
Lock l(&p->mtx);
|
Lock l(&p->mtx);
|
||||||
SyncVar *res = p->val;
|
SyncVar *res = p->val;
|
||||||
|
@ -97,7 +112,8 @@ SyncVar* SyncTab::GetAndLock(ThreadState *thr, uptr pc,
|
||||||
if (res == 0) {
|
if (res == 0) {
|
||||||
StatInc(thr, StatSyncCreated);
|
StatInc(thr, StatSyncCreated);
|
||||||
void *mem = internal_alloc(MBlockSync, sizeof(SyncVar));
|
void *mem = internal_alloc(MBlockSync, sizeof(SyncVar));
|
||||||
res = new(mem) SyncVar(addr);
|
const u64 uid = atomic_fetch_add(&uid_gen_, 1, memory_order_relaxed);
|
||||||
|
res = new(mem) SyncVar(addr, uid);
|
||||||
#ifndef TSAN_GO
|
#ifndef TSAN_GO
|
||||||
res->creation_stack.ObtainCurrent(thr, pc);
|
res->creation_stack.ObtainCurrent(thr, pc);
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -50,12 +50,13 @@ class StackTrace {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SyncVar {
|
struct SyncVar {
|
||||||
explicit SyncVar(uptr addr);
|
explicit SyncVar(uptr addr, u64 uid);
|
||||||
|
|
||||||
static const int kInvalidTid = -1;
|
static const int kInvalidTid = -1;
|
||||||
|
|
||||||
Mutex mtx;
|
Mutex mtx;
|
||||||
const uptr addr;
|
const uptr addr;
|
||||||
|
const u64 uid; // Globally unique id.
|
||||||
SyncClock clock;
|
SyncClock clock;
|
||||||
SyncClock read_clock; // Used for rw mutexes only.
|
SyncClock read_clock; // Used for rw mutexes only.
|
||||||
StackTrace creation_stack;
|
StackTrace creation_stack;
|
||||||
|
@ -69,6 +70,18 @@ struct SyncVar {
|
||||||
SyncVar *next; // In SyncTab hashtable.
|
SyncVar *next; // In SyncTab hashtable.
|
||||||
|
|
||||||
uptr GetMemoryConsumption();
|
uptr GetMemoryConsumption();
|
||||||
|
u64 GetId() const {
|
||||||
|
// 47 lsb is addr, then 14 bits is low part of uid, then 3 zero bits.
|
||||||
|
return GetLsb((u64)addr | (uid << 47), 61);
|
||||||
|
}
|
||||||
|
bool CheckId(u64 uid) const {
|
||||||
|
CHECK_EQ(uid, GetLsb(uid, 14));
|
||||||
|
return GetLsb(this->uid, 14) == uid;
|
||||||
|
}
|
||||||
|
static uptr SplitId(u64 id, u64 *uid) {
|
||||||
|
*uid = id >> 47;
|
||||||
|
return (uptr)GetLsb(id, 47);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class SyncTab {
|
class SyncTab {
|
||||||
|
@ -76,9 +89,9 @@ class SyncTab {
|
||||||
SyncTab();
|
SyncTab();
|
||||||
~SyncTab();
|
~SyncTab();
|
||||||
|
|
||||||
// If the SyncVar does not exist yet, it is created.
|
SyncVar* GetOrCreateAndLock(ThreadState *thr, uptr pc,
|
||||||
SyncVar* GetAndLock(ThreadState *thr, uptr pc,
|
|
||||||
uptr addr, bool write_lock);
|
uptr addr, bool write_lock);
|
||||||
|
SyncVar* GetIfExistsAndLock(uptr addr, bool write_lock);
|
||||||
|
|
||||||
// If the SyncVar does not exist, returns 0.
|
// If the SyncVar does not exist, returns 0.
|
||||||
SyncVar* GetAndRemove(ThreadState *thr, uptr pc, uptr addr);
|
SyncVar* GetAndRemove(ThreadState *thr, uptr pc, uptr addr);
|
||||||
|
@ -96,9 +109,13 @@ class SyncTab {
|
||||||
// FIXME: Implement something more sane.
|
// FIXME: Implement something more sane.
|
||||||
static const int kPartCount = 1009;
|
static const int kPartCount = 1009;
|
||||||
Part tab_[kPartCount];
|
Part tab_[kPartCount];
|
||||||
|
atomic_uint64_t uid_gen_;
|
||||||
|
|
||||||
int PartIdx(uptr addr);
|
int PartIdx(uptr addr);
|
||||||
|
|
||||||
|
SyncVar* GetAndLock(ThreadState *thr, uptr pc,
|
||||||
|
uptr addr, bool write_lock, bool create);
|
||||||
|
|
||||||
SyncTab(const SyncTab&); // Not implemented.
|
SyncTab(const SyncTab&); // Not implemented.
|
||||||
void operator = (const SyncTab&); // Not implemented.
|
void operator = (const SyncTab&); // Not implemented.
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include "tsan_defs.h"
|
#include "tsan_defs.h"
|
||||||
#include "tsan_mutex.h"
|
#include "tsan_mutex.h"
|
||||||
#include "tsan_sync.h"
|
#include "tsan_sync.h"
|
||||||
|
#include "tsan_mutexset.h"
|
||||||
|
|
||||||
namespace __tsan {
|
namespace __tsan {
|
||||||
|
|
||||||
|
@ -43,6 +44,7 @@ typedef u64 Event;
|
||||||
struct TraceHeader {
|
struct TraceHeader {
|
||||||
StackTrace stack0; // Start stack for the trace.
|
StackTrace stack0; // Start stack for the trace.
|
||||||
u64 epoch0; // Start epoch for the trace.
|
u64 epoch0; // Start epoch for the trace.
|
||||||
|
MutexSet mset0;
|
||||||
#ifndef TSAN_GO
|
#ifndef TSAN_GO
|
||||||
uptr stack0buf[kTraceStackSize];
|
uptr stack0buf[kTraceStackSize];
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
//===-- tsan_mutexset_test.cc ---------------------------------------------===//
|
||||||
|
//
|
||||||
|
// The LLVM Compiler Infrastructure
|
||||||
|
//
|
||||||
|
// This file is distributed under the University of Illinois Open Source
|
||||||
|
// License. See LICENSE.TXT for details.
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
//
|
||||||
|
// This file is a part of ThreadSanitizer (TSan), a race detector.
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
#include "tsan_mutexset.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
namespace __tsan {
|
||||||
|
|
||||||
|
static void Expect(const MutexSet &mset, uptr i, u64 id, bool write, u64 epoch,
|
||||||
|
int count) {
|
||||||
|
MutexSet::Desc d = mset.Get(i);
|
||||||
|
EXPECT_EQ(id, d.id);
|
||||||
|
EXPECT_EQ(write, d.write);
|
||||||
|
EXPECT_EQ(epoch, d.epoch);
|
||||||
|
EXPECT_EQ(count, d.count);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MutexSet, Basic) {
|
||||||
|
MutexSet mset;
|
||||||
|
EXPECT_EQ(mset.Size(), (uptr)0);
|
||||||
|
|
||||||
|
mset.Add(1, true, 2);
|
||||||
|
EXPECT_EQ(mset.Size(), (uptr)1);
|
||||||
|
Expect(mset, 0, 1, true, 2, 1);
|
||||||
|
mset.Del(1, true);
|
||||||
|
EXPECT_EQ(mset.Size(), (uptr)0);
|
||||||
|
|
||||||
|
mset.Add(3, true, 4);
|
||||||
|
mset.Add(5, false, 6);
|
||||||
|
EXPECT_EQ(mset.Size(), (uptr)2);
|
||||||
|
Expect(mset, 0, 3, true, 4, 1);
|
||||||
|
Expect(mset, 1, 5, false, 6, 1);
|
||||||
|
mset.Del(3, true);
|
||||||
|
EXPECT_EQ(mset.Size(), (uptr)1);
|
||||||
|
mset.Del(5, false);
|
||||||
|
EXPECT_EQ(mset.Size(), (uptr)0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MutexSet, DoubleAdd) {
|
||||||
|
MutexSet mset;
|
||||||
|
mset.Add(1, true, 2);
|
||||||
|
EXPECT_EQ(mset.Size(), (uptr)1);
|
||||||
|
Expect(mset, 0, 1, true, 2, 1);
|
||||||
|
|
||||||
|
mset.Add(1, true, 2);
|
||||||
|
EXPECT_EQ(mset.Size(), (uptr)1);
|
||||||
|
Expect(mset, 0, 1, true, 2, 2);
|
||||||
|
|
||||||
|
mset.Del(1, true);
|
||||||
|
EXPECT_EQ(mset.Size(), (uptr)1);
|
||||||
|
Expect(mset, 0, 1, true, 2, 1);
|
||||||
|
|
||||||
|
mset.Del(1, true);
|
||||||
|
EXPECT_EQ(mset.Size(), (uptr)0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MutexSet, DoubleDel) {
|
||||||
|
MutexSet mset;
|
||||||
|
mset.Add(1, true, 2);
|
||||||
|
EXPECT_EQ(mset.Size(), (uptr)1);
|
||||||
|
mset.Del(1, true);
|
||||||
|
EXPECT_EQ(mset.Size(), (uptr)0);
|
||||||
|
mset.Del(1, true);
|
||||||
|
EXPECT_EQ(mset.Size(), (uptr)0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MutexSet, Remove) {
|
||||||
|
MutexSet mset;
|
||||||
|
mset.Add(1, true, 2);
|
||||||
|
mset.Add(1, true, 2);
|
||||||
|
mset.Add(3, true, 4);
|
||||||
|
mset.Add(3, true, 4);
|
||||||
|
EXPECT_EQ(mset.Size(), (uptr)2);
|
||||||
|
|
||||||
|
mset.Remove(1);
|
||||||
|
EXPECT_EQ(mset.Size(), (uptr)1);
|
||||||
|
Expect(mset, 0, 3, true, 4, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MutexSet, Full) {
|
||||||
|
MutexSet mset;
|
||||||
|
for (uptr i = 0; i < MutexSet::kMaxSize; i++) {
|
||||||
|
mset.Add(i, true, i + 1);
|
||||||
|
}
|
||||||
|
EXPECT_EQ(mset.Size(), MutexSet::kMaxSize);
|
||||||
|
for (uptr i = 0; i < MutexSet::kMaxSize; i++) {
|
||||||
|
Expect(mset, i, i, true, i + 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uptr i = 0; i < MutexSet::kMaxSize; i++) {
|
||||||
|
mset.Add(i, true, i + 1);
|
||||||
|
}
|
||||||
|
EXPECT_EQ(mset.Size(), MutexSet::kMaxSize);
|
||||||
|
for (uptr i = 0; i < MutexSet::kMaxSize; i++) {
|
||||||
|
Expect(mset, i, i, true, i + 1, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MutexSet, Overflow) {
|
||||||
|
MutexSet mset;
|
||||||
|
for (uptr i = 0; i < MutexSet::kMaxSize; i++) {
|
||||||
|
mset.Add(i, true, i + 1);
|
||||||
|
mset.Add(i, true, i + 1);
|
||||||
|
}
|
||||||
|
mset.Add(100, true, 200);
|
||||||
|
EXPECT_EQ(mset.Size(), MutexSet::kMaxSize);
|
||||||
|
for (uptr i = 0; i < MutexSet::kMaxSize; i++) {
|
||||||
|
if (i == 0)
|
||||||
|
Expect(mset, i, 63, true, 64, 2);
|
||||||
|
else if (i == MutexSet::kMaxSize - 1)
|
||||||
|
Expect(mset, i, 100, true, 200, 1);
|
||||||
|
else
|
||||||
|
Expect(mset, i, i, true, i + 1, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace __tsan
|
|
@ -36,7 +36,7 @@ TEST(Sync, Table) {
|
||||||
uintptr_t addr = rand_r(&seed) % (kRange - 1) + 1;
|
uintptr_t addr = rand_r(&seed) % (kRange - 1) + 1;
|
||||||
if (rand_r(&seed) % 2) {
|
if (rand_r(&seed) % 2) {
|
||||||
// Get or add.
|
// Get or add.
|
||||||
SyncVar *v = tab.GetAndLock(thr, pc, addr, true);
|
SyncVar *v = tab.GetOrCreateAndLock(thr, pc, addr, true);
|
||||||
EXPECT_TRUE(golden[addr] == 0 || golden[addr] == v);
|
EXPECT_TRUE(golden[addr] == 0 || golden[addr] == v);
|
||||||
EXPECT_EQ(v->addr, addr);
|
EXPECT_EQ(v->addr, addr);
|
||||||
golden[addr] = v;
|
golden[addr] = v;
|
||||||
|
|
Loading…
Reference in New Issue