Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[sanitizer] Fix asserts in asan and tsan in pthread interceptors. #75394

Merged
merged 1 commit into from Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions compiler-rt/lib/sanitizer_common/sanitizer_flags.inc
Expand Up @@ -279,3 +279,6 @@ COMMON_FLAG(bool, test_only_replace_dlopen_main_program, false,
COMMON_FLAG(bool, enable_symbolizer_markup, SANITIZER_FUCHSIA,
"Use sanitizer symbolizer markup, available on Linux "
"and always set true for Fuchsia.")

COMMON_FLAG(bool, detect_invalid_join, true,
"If set, check invalid joins of threads.")
23 changes: 19 additions & 4 deletions compiler-rt/lib/sanitizer_common/sanitizer_thread_arg_retval.cpp
Expand Up @@ -23,6 +23,9 @@ void ThreadArgRetval::CreateLocked(uptr thread, bool detached,
Data& t = data_[thread];
t = {};
t.gen = gen_++;
static_assert(sizeof(gen_) == sizeof(u32) && kInvalidGen == UINT32_MAX);
if (gen_ == kInvalidGen)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I missed if (gen_ == kInvalidGen) = 0,
so we don't need the one introduced with b96deb8

gen_ = 0;
t.detached = detached;
t.args = args;
}
Expand Down Expand Up @@ -53,16 +56,28 @@ void ThreadArgRetval::Finish(uptr thread, void* retval) {
u32 ThreadArgRetval::BeforeJoin(uptr thread) const {
__sanitizer::Lock lock(&mtx_);
auto t = data_.find(thread);
CHECK(t);
CHECK(!t->second.detached);
return t->second.gen;
if (t && !t->second.detached) {
return t->second.gen;
}
if (!common_flags()->detect_invalid_join)
return kInvalidGen;
const char* reason = "unknown";
if (!t) {
reason = "already joined";
} else if (t->second.detached) {
reason = "detached";
}
Report("ERROR: %s: Joining %s thread, aborting.\n", SanitizerToolName,
reason);
Die();
}

void ThreadArgRetval::AfterJoin(uptr thread, u32 gen) {
__sanitizer::Lock lock(&mtx_);
auto t = data_.find(thread);
if (!t || gen != t->second.gen) {
// Thread was reused and erased by any other event.
// Thread was reused and erased by any other event, or we had an invalid
// join.
return;
}
CHECK(!t->second.detached);
Expand Down
Expand Up @@ -93,6 +93,7 @@ class SANITIZER_MUTEX ThreadArgRetval {
// will keep pointers alive forever, missing leaks caused by cancelation.

private:
static const u32 kInvalidGen = UINT32_MAX;
struct Data {
Args args;
u32 gen; // Avoid collision if thread id re-used.
Expand Down
@@ -1,13 +1,14 @@
// RUN: %clangxx -pthread %s -o %t
// RUN: %run %t 0
// RUN: %env_tool_opts=detect_invalid_join=false %run %t 0

// FIXME: Crashes on some bots in pthread_exit.
// RUN: %run %t %if tsan %{ 0 %} %else %{ 1 %}
// RUN: %env_tool_opts=detect_invalid_join=false %run %t %if tsan %{ 0 %} %else %{ 1 %}

// REQUIRES: glibc

#include <assert.h>
#include <ctime>
#include <errno.h>
#include <pthread.h>
#include <stdint.h>
#include <stdlib.h>
Expand All @@ -24,6 +25,7 @@ static void *fn(void *args) {

int main(int argc, char **argv) {
use_exit = atoi(argv[1]);
bool check_invalid_join = !use_exit;
pthread_t thread[5];
assert(!pthread_create(&thread[0], nullptr, fn, (void *)1000));
assert(!pthread_create(&thread[1], nullptr, fn, (void *)1001));
Expand All @@ -42,6 +44,8 @@ int main(int argc, char **argv) {
while (pthread_tryjoin_np(thread[1], &res))
sleep(1);
assert(~(uintptr_t)res == 1001);
assert(check_invalid_join ||
(pthread_tryjoin_np(thread[1], &res) == EBUSY));
}

{
Expand All @@ -50,12 +54,15 @@ int main(int argc, char **argv) {
while (pthread_timedjoin_np(thread[2], &res, &tm))
sleep(1);
assert(~(uintptr_t)res == 1002);
assert(check_invalid_join ||
(pthread_timedjoin_np(thread[2], &res, &tm) == ESRCH));
}

{
void *res;
assert(!pthread_join(thread[3], &res));
assert(~(uintptr_t)res == 1003);
assert(check_invalid_join || (pthread_join(thread[3], &res) == ESRCH));
}

return 0;
Expand Down
@@ -0,0 +1,47 @@
// RUN: %clangxx -pthread %s -o %t

// RUN: %env_tool_opts=detect_invalid_join=true not %run %t 0 2>&1 | FileCheck %s
// RUN: %env_tool_opts=detect_invalid_join=true not %run %t 1 2>&1 | FileCheck %s
// RUN: %env_tool_opts=detect_invalid_join=true not %run %t 2 2>&1 | FileCheck %s
// RUN: %env_tool_opts=detect_invalid_join=true not %run %t 3 2>&1 | FileCheck %s --check-prefix=DETACH

// REQUIRES: glibc && (asan || hwasan || lsan)

#include <assert.h>
#include <ctime>
#include <errno.h>
#include <pthread.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>

static void *fn(void *args) {
sleep(1);
return nullptr;
}

int main(int argc, char **argv) {
int n = atoi(argv[1]);
pthread_t thread;
assert(!pthread_create(&thread, nullptr, fn, nullptr));
void *res;
if (n == 0) {
while (pthread_tryjoin_np(thread, &res))
sleep(1);
pthread_tryjoin_np(thread, &res);
} else if (n == 1) {
timespec tm = {0, 1};
while (pthread_timedjoin_np(thread, &res, &tm))
sleep(1);
pthread_timedjoin_np(thread, &res, &tm);
} else if (n == 2) {
assert(!pthread_join(thread, &res));
pthread_join(thread, &res);
} else if (n == 3) {
assert(!pthread_detach(thread));
pthread_join(thread, &res);
}
// CHECK: Joining already joined thread
// DETACH: Joining detached thread
return 0;
}