| //===----------------------------------------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| /// |
| /// \file |
| /// Integration tests for pthread_getattr_np. |
| /// |
| //===----------------------------------------------------------------------===// |
| |
| #include "hdr/pthread_macros.h" |
| #include "hdr/stdint_proxy.h" |
| #include "hdr/sys_mman_macros.h" |
| #include "src/pthread/pthread_attr_destroy.h" |
| #include "src/pthread/pthread_attr_getdetachstate.h" |
| #include "src/pthread/pthread_attr_getguardsize.h" |
| #include "src/pthread/pthread_attr_getstack.h" |
| #include "src/pthread/pthread_attr_init.h" |
| #include "src/pthread/pthread_attr_setdetachstate.h" |
| #include "src/pthread/pthread_attr_setstack.h" |
| #include "src/pthread/pthread_barrier_init.h" |
| #include "src/pthread/pthread_barrier_wait.h" |
| #include "src/pthread/pthread_create.h" |
| #include "src/pthread/pthread_detach.h" |
| #include "src/pthread/pthread_getattr_np.h" |
| #include "src/pthread/pthread_join.h" |
| #include "src/pthread/pthread_self.h" |
| #include "src/sys/mman/mmap.h" |
| #include "src/sys/mman/munmap.h" |
| #include "src/unistd/sysconf.h" |
| #include "test/IntegrationTest/test.h" |
| |
| static size_t pagesize() { |
| return static_cast<size_t>(LIBC_NAMESPACE::sysconf(_SC_PAGESIZE)); |
| } |
| |
| static void check_readable(const void *start, size_t size) { |
| size_t page_size = pagesize(); |
| auto *bytes = static_cast<const volatile char *>(start); |
| for (size_t offset = 0; offset < size; offset += page_size) |
| (void)bytes[offset]; |
| if (size > 0) |
| (void)bytes[size - 1]; |
| } |
| |
| static void wait_barrier(pthread_barrier_t &barrier) { |
| int res = LIBC_NAMESPACE::pthread_barrier_wait(&barrier); |
| ASSERT_TRUE(res == 0 || res == PTHREAD_BARRIER_SERIAL_THREAD); |
| } |
| |
| static pthread_barrier_t ready_barrier; |
| static pthread_barrier_t done_barrier; |
| |
| struct PthreadAttrValues { |
| int detachstate{-1}; |
| void *stackaddr{reinterpret_cast<void *>(1)}; |
| size_t stacksize{1234}; |
| size_t guardsize{1234}; |
| |
| PthreadAttrValues() = default; |
| |
| void populate_from(pthread_t th) { |
| pthread_attr_t attr; |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_getattr_np(th, &attr), 0); |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_getdetachstate(&attr, &detachstate), |
| 0); |
| ASSERT_EQ( |
| LIBC_NAMESPACE::pthread_attr_getstack(&attr, &stackaddr, &stacksize), |
| 0); |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_getguardsize(&attr, &guardsize), 0); |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_destroy(&attr), 0); |
| } |
| }; |
| |
| // Test 1: Main thread attributes |
| // Verifies that pthread_getattr_np on the main thread reports a detached state, |
| // a dynamic stack size (PTHREAD_STACK_DYNAMIC_NP), and a zero guard size. |
| static void test_main_thread() { |
| PthreadAttrValues values; |
| values.populate_from(LIBC_NAMESPACE::pthread_self()); |
| |
| ASSERT_EQ(values.detachstate, static_cast<int>(PTHREAD_CREATE_DETACHED)); |
| ASSERT_NE(values.stackaddr, static_cast<void *>(nullptr)); |
| ASSERT_EQ(values.stacksize, static_cast<size_t>(PTHREAD_STACK_DYNAMIC_NP)); |
| ASSERT_EQ(reinterpret_cast<uintptr_t>(values.stackaddr) % pagesize(), |
| static_cast<uintptr_t>(0)); |
| |
| uintptr_t local_var_addr = reinterpret_cast<uintptr_t>(&values); |
| uintptr_t stack_high = reinterpret_cast<uintptr_t>(values.stackaddr); |
| ASSERT_TRUE(local_var_addr < stack_high); |
| check_readable(&values, stack_high - local_var_addr); |
| |
| ASSERT_EQ(values.guardsize, static_cast<size_t>(0)); |
| } |
| |
| static void *child_default_func(void *arg) { |
| auto *values = static_cast<PthreadAttrValues *>(arg); |
| |
| values->populate_from(LIBC_NAMESPACE::pthread_self()); |
| |
| ASSERT_EQ(values->detachstate, static_cast<int>(PTHREAD_CREATE_JOINABLE)); |
| |
| ASSERT_NE(values->stackaddr, static_cast<void *>(nullptr)); |
| ASSERT_NE(values->stacksize, static_cast<size_t>(PTHREAD_STACK_DYNAMIC_NP)); |
| ASSERT_TRUE(values->stacksize > 0); |
| |
| int local_var = 0; |
| uintptr_t local_var_addr = reinterpret_cast<uintptr_t>(&local_var); |
| uintptr_t stack_low = reinterpret_cast<uintptr_t>(values->stackaddr); |
| uintptr_t stack_high = stack_low + values->stacksize; |
| ASSERT_TRUE(local_var_addr >= stack_low); |
| ASSERT_TRUE(local_var_addr < stack_high); |
| |
| check_readable(values->stackaddr, values->stacksize); |
| |
| ASSERT_EQ(values->guardsize, pagesize()); |
| |
| wait_barrier(ready_barrier); |
| wait_barrier(done_barrier); |
| |
| return nullptr; |
| } |
| |
| // Test 2: Child thread with default attributes |
| // Verifies that a joinable thread created with default attributes reports |
| // joinable state, an implementation-allocated stack, and a page-sized guard, |
| // both when queried by the thread itself and by the parent thread. |
| static void test_child_thread_default() { |
| PthreadAttrValues child_values; |
| pthread_t th; |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_create(&th, nullptr, child_default_func, |
| &child_values), |
| 0); |
| |
| wait_barrier(ready_barrier); |
| |
| // Query from the parent thread while child is still running. |
| PthreadAttrValues parent_values; |
| parent_values.populate_from(th); |
| |
| ASSERT_EQ(parent_values.detachstate, |
| static_cast<int>(PTHREAD_CREATE_JOINABLE)); |
| ASSERT_EQ(parent_values.stackaddr, child_values.stackaddr); |
| ASSERT_EQ(parent_values.stacksize, child_values.stacksize); |
| ASSERT_EQ(parent_values.guardsize, child_values.guardsize); |
| |
| wait_barrier(done_barrier); |
| |
| void *retval = nullptr; |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th, &retval), 0); |
| } |
| |
| static void *child_custom_func(void *arg) { |
| auto *values = static_cast<PthreadAttrValues *>(arg); |
| values->populate_from(LIBC_NAMESPACE::pthread_self()); |
| return nullptr; |
| } |
| |
| // Test 3: Child thread with custom stack |
| // Verifies that a thread created with a user-allocated stack reports the exact |
| // stack address and size, and reports a guard size of 0. |
| static void test_child_thread_custom_stack() { |
| size_t custom_stacksize = PTHREAD_STACK_MIN * 2; |
| void *custom_stack = |
| LIBC_NAMESPACE::mmap(nullptr, custom_stacksize, PROT_READ | PROT_WRITE, |
| MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
| ASSERT_NE(custom_stack, MAP_FAILED); |
| ASSERT_NE(custom_stack, static_cast<void *>(nullptr)); |
| |
| pthread_attr_t attr; |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_init(&attr), 0); |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_setstack(&attr, custom_stack, |
| custom_stacksize), |
| 0); |
| |
| PthreadAttrValues values; |
| pthread_t th; |
| ASSERT_EQ( |
| LIBC_NAMESPACE::pthread_create(&th, &attr, child_custom_func, &values), |
| 0); |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_destroy(&attr), 0); |
| |
| void *retval = nullptr; |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th, &retval), 0); |
| |
| ASSERT_EQ(values.stackaddr, custom_stack); |
| ASSERT_EQ(values.stacksize, custom_stacksize); |
| ASSERT_EQ(values.guardsize, static_cast<size_t>(0)); |
| ASSERT_EQ(values.detachstate, static_cast<int>(PTHREAD_CREATE_JOINABLE)); |
| |
| ASSERT_EQ(LIBC_NAMESPACE::munmap(custom_stack, custom_stacksize), 0); |
| } |
| |
| static void *child_detached_func(void *) { |
| PthreadAttrValues values; |
| values.populate_from(LIBC_NAMESPACE::pthread_self()); |
| |
| ASSERT_EQ(values.detachstate, static_cast<int>(PTHREAD_CREATE_DETACHED)); |
| ASSERT_EQ(values.guardsize, pagesize()); |
| |
| wait_barrier(done_barrier); |
| return nullptr; |
| } |
| |
| // Test 4: Child thread created detached |
| // Verifies that a thread created with PTHREAD_CREATE_DETACHED reports a |
| // detached state. |
| static void test_child_thread_detached() { |
| pthread_attr_t attr; |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_init(&attr), 0); |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_setdetachstate( |
| &attr, PTHREAD_CREATE_DETACHED), |
| 0); |
| |
| pthread_t th; |
| ASSERT_EQ( |
| LIBC_NAMESPACE::pthread_create(&th, &attr, child_detached_func, nullptr), |
| 0); |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_attr_destroy(&attr), 0); |
| |
| wait_barrier(done_barrier); |
| } |
| |
| static void *child_dynamic_detach_func(void *) { |
| wait_barrier(ready_barrier); |
| wait_barrier(done_barrier); |
| return nullptr; |
| } |
| |
| // Test 5: Dynamically detached child thread |
| // Verifies that detaching a running joinable thread transitions its reported |
| // detach state from PTHREAD_CREATE_JOINABLE to PTHREAD_CREATE_DETACHED. |
| static void test_child_thread_dynamic_detach() { |
| pthread_t th; |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_create(&th, nullptr, |
| child_dynamic_detach_func, nullptr), |
| 0); |
| |
| wait_barrier(ready_barrier); |
| |
| PthreadAttrValues values; |
| values.populate_from(th); |
| ASSERT_EQ(values.detachstate, static_cast<int>(PTHREAD_CREATE_JOINABLE)); |
| |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_detach(th), 0); |
| |
| values.populate_from(th); |
| ASSERT_EQ(values.detachstate, static_cast<int>(PTHREAD_CREATE_DETACHED)); |
| |
| wait_barrier(done_barrier); |
| } |
| |
| TEST_MAIN() { |
| // Barriers cannot be destroyed safely due to issue #221680. |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_barrier_init(&ready_barrier, nullptr, 2), |
| 0); |
| ASSERT_EQ(LIBC_NAMESPACE::pthread_barrier_init(&done_barrier, nullptr, 2), 0); |
| |
| test_main_thread(); |
| test_child_thread_default(); |
| test_child_thread_custom_stack(); |
| test_child_thread_detached(); |
| test_child_thread_dynamic_detach(); |
| return 0; |
| } |