// Copyright (c) The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include struct ExpectedException : std::runtime_error { explicit ExpectedException(const std::string& msg) : std::runtime_error(msg) {} }; struct ThrowTask { void operator()() const { throw ExpectedException("fail"); } }; struct CounterTask { std::atomic_uint32_t& m_counter; explicit CounterTask(std::atomic_uint32_t& counter) : m_counter{counter} {} void operator()() const { m_counter.fetch_add(1, std::memory_order_relaxed); } }; // Waits for a future to complete. Increments 'fail_counter' if the expected exception is thrown. static void GetFuture(std::future& future, uint32_t& fail_counter) { try { future.get(); } catch (const ExpectedException&) { fail_counter++; } catch (...) { assert(false && "Unexpected exception type"); } } // Global thread pool for fuzzing. Persisting it across iterations prevents // the excessive thread creation/destruction overhead that can lead to // instability in the fuzzing environment. // This is also how we use it in the app's lifecycle. ThreadPool g_pool{"fuzz"}; Mutex g_pool_mutex; // Global to verify we always have the same number of threads. size_t g_num_workers = 3; static void StartPoolIfNeeded() EXCLUSIVE_LOCKS_REQUIRED(!g_pool_mutex) { LOCK(g_pool_mutex); if (g_pool.WorkersCount() == g_num_workers) return; g_pool.Start(g_num_workers); } static void setup_threadpool_test() { // Disable logging entirely. It seems to cause memory leaks. LogInstance().DisableLogging(); } FUZZ_TARGET(threadpool, .init = setup_threadpool_test) EXCLUSIVE_LOCKS_REQUIRED(!g_pool_mutex) { // Because LibAFL calls fork() after calling the init setup function, // the child processes end up having one thread active and no workers. // To work around this limitation, start thread pool inside the first runner. StartPoolIfNeeded(); FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); const uint32_t num_tasks = fuzzed_data_provider.ConsumeIntegralInRange(0, 1024); assert(g_pool.WorkersCount() == g_num_workers); assert(g_pool.WorkQueueSize() == 0); // Counters std::atomic_uint32_t task_counter{0}; uint32_t fail_counter{0}; uint32_t expected_task_counter{0}; uint32_t expected_fail_tasks{0}; std::queue> futures; for (uint32_t i = 0; i < num_tasks; ++i) { const bool will_throw = fuzzed_data_provider.ConsumeBool(); const bool wait_immediately = fuzzed_data_provider.ConsumeBool(); std::future fut; if (will_throw) { expected_fail_tasks++; fut = g_pool.Submit(ThrowTask{}); } else { expected_task_counter++; fut = g_pool.Submit(CounterTask{task_counter}); } // If caller wants to wait immediately, consume the future here (safe). if (wait_immediately) { // Waits for this task to complete immediately; prior queued tasks may also complete // as they were queued earlier. GetFuture(fut, fail_counter); } else { // Store task for a posterior check futures.emplace(std::move(fut)); } } // Drain remaining futures while (!futures.empty()) { auto fut = std::move(futures.front()); futures.pop(); GetFuture(fut, fail_counter); } assert(g_pool.WorkQueueSize() == 0); assert(task_counter.load() == expected_task_counter); assert(fail_counter == expected_fail_tasks); }