Coverage Report

Created: 2026-06-12 16:53

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/root/bitcoin/src/test/fuzz/threadpool.cpp
Line
Count
Source
1
// Copyright (c) The Bitcoin Core developers
2
// Distributed under the MIT software license, see the accompanying
3
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5
#include <logging.h>
6
#include <util/threadpool.h>
7
8
#include <test/fuzz/FuzzedDataProvider.h>
9
#include <test/fuzz/fuzz.h>
10
11
#include <atomic>
12
#include <future>
13
#include <queue>
14
15
struct ExpectedException : std::runtime_error {
16
0
    explicit ExpectedException(const std::string& msg) : std::runtime_error(msg) {}
17
};
18
19
struct ThrowTask {
20
0
    void operator()() const { throw ExpectedException("fail"); }
21
};
22
23
struct CounterTask {
24
    std::atomic_uint32_t& m_counter;
25
0
    explicit CounterTask(std::atomic_uint32_t& counter) : m_counter{counter} {}
26
0
    void operator()() const { m_counter.fetch_add(1, std::memory_order_relaxed); }
27
};
28
29
// Waits for a future to complete. Increments 'fail_counter' if the expected exception is thrown.
30
static void GetFuture(std::future<void>& future, uint32_t& fail_counter)
31
0
{
32
0
    try {
33
0
        future.get();
34
0
    } catch (const ExpectedException&) {
35
0
        fail_counter++;
36
0
    } catch (...) {
37
0
        assert(false && "Unexpected exception type");
  Branch (37:9): [Folded - Ignored]
  Branch (37:9): [Folded - Ignored]
  Branch (37:9): [Folded - Ignored]
38
0
    }
39
0
}
40
41
// Global thread pool for fuzzing. Persisting it across iterations prevents
42
// the excessive thread creation/destruction overhead that can lead to
43
// instability in the fuzzing environment.
44
// This is also how we use it in the app's lifecycle.
45
ThreadPool g_pool{"fuzz"};
46
Mutex g_pool_mutex;
47
// Global to verify we always have the same number of threads.
48
size_t g_num_workers = 3;
49
50
static void StartPoolIfNeeded() EXCLUSIVE_LOCKS_REQUIRED(!g_pool_mutex)
51
0
{
52
0
    LOCK(g_pool_mutex);
53
0
    if (g_pool.WorkersCount() == g_num_workers) return;
  Branch (53:9): [True: 0, False: 0]
54
0
    g_pool.Start(g_num_workers);
55
0
}
56
57
static void setup_threadpool_test()
58
0
{
59
    // Disable logging entirely. It seems to cause memory leaks.
60
0
    LogInstance().DisableLogging();
61
0
}
62
63
FUZZ_TARGET(threadpool, .init = setup_threadpool_test) EXCLUSIVE_LOCKS_REQUIRED(!g_pool_mutex)
64
0
{
65
    // Because LibAFL calls fork() after calling the init setup function,
66
    // the child processes end up having one thread active and no workers.
67
    // To work around this limitation, start thread pool inside the first runner.
68
0
    StartPoolIfNeeded();
69
70
0
    FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
71
72
0
    const uint32_t num_tasks = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, 1024);
73
0
    assert(g_pool.WorkersCount() == g_num_workers);
  Branch (73:5): [True: 0, False: 0]
74
0
    assert(g_pool.WorkQueueSize() == 0);
  Branch (74:5): [True: 0, False: 0]
75
76
    // Counters
77
0
    std::atomic_uint32_t task_counter{0};
78
0
    uint32_t fail_counter{0};
79
0
    uint32_t expected_task_counter{0};
80
0
    uint32_t expected_fail_tasks{0};
81
82
0
    std::queue<std::future<void>> futures;
83
0
    for (uint32_t i = 0; i < num_tasks; ++i) {
  Branch (83:26): [True: 0, False: 0]
84
0
        const bool will_throw = fuzzed_data_provider.ConsumeBool();
85
0
        const bool wait_immediately = fuzzed_data_provider.ConsumeBool();
86
87
0
        std::future<void> fut;
88
0
        if (will_throw) {
  Branch (88:13): [True: 0, False: 0]
89
0
            expected_fail_tasks++;
90
0
            fut = *Assert(g_pool.Submit(ThrowTask{}));
91
0
        } else {
92
0
            expected_task_counter++;
93
0
            fut = *Assert(g_pool.Submit(CounterTask{task_counter}));
94
0
        }
95
96
        // If caller wants to wait immediately, consume the future here (safe).
97
0
        if (wait_immediately) {
  Branch (97:13): [True: 0, False: 0]
98
            // Waits for this task to complete immediately; prior queued tasks may also complete
99
            // as they were queued earlier.
100
0
            GetFuture(fut, fail_counter);
101
0
        } else {
102
            // Store task for a posterior check
103
0
            futures.emplace(std::move(fut));
104
0
        }
105
0
    }
106
107
    // Drain remaining futures
108
0
    while (!futures.empty()) {
  Branch (108:12): [True: 0, False: 0]
109
0
        auto fut = std::move(futures.front());
110
0
        futures.pop();
111
0
        GetFuture(fut, fail_counter);
112
0
    }
113
114
0
    assert(g_pool.WorkQueueSize() == 0);
  Branch (114:5): [True: 0, False: 0]
115
0
    assert(task_counter.load() == expected_task_counter);
  Branch (115:5): [True: 0, False: 0]
116
0
    assert(fail_counter == expected_fail_tasks);
  Branch (116:5): [True: 0, False: 0]
117
0
}