Coverage Report

Created: 2026-08-25 19:02

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
12.2k
    explicit ExpectedException(const std::string& msg) : std::runtime_error(msg) {}
17
};
18
19
struct ThrowTask {
20
12.3k
    void operator()() const { throw ExpectedException("fail"); }
21
};
22
23
struct CounterTask {
24
    std::atomic_uint32_t& m_counter;
25
68.8k
    explicit CounterTask(std::atomic_uint32_t& counter) : m_counter{counter} {}
26
68.7k
    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
81.1k
{
32
81.1k
    try {
33
81.1k
        future.get();
34
81.1k
    } catch (const ExpectedException&) {
35
12.3k
        fail_counter++;
36
12.3k
    } 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
81.1k
}
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
// Global to verify we always have the same number of threads.
47
size_t g_num_workers = 3;
48
49
static void StartPoolIfNeeded()
50
134
{
51
134
    if (g_pool.WorkersCount() == g_num_workers) return;
  Branch (51:9): [True: 133, False: 1]
52
1
    g_pool.Start(g_num_workers);
53
1
}
54
55
static void setup_threadpool_test()
56
0
{
57
    // Disable logging entirely. It seems to cause memory leaks.
58
0
    LogInstance().DisableLogging();
59
0
}
60
61
FUZZ_TARGET(threadpool, .init = setup_threadpool_test)
62
134
{
63
    // Because LibAFL calls fork() after calling the init setup function,
64
    // the child processes end up having one thread active and no workers.
65
    // To work around this limitation, start thread pool inside the first runner.
66
134
    StartPoolIfNeeded();
67
68
134
    FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
69
70
134
    const uint32_t num_tasks = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, 1024);
71
134
    assert(g_pool.WorkersCount() == g_num_workers);
  Branch (71:5): [True: 134, False: 0]
72
134
    assert(g_pool.WorkQueueSize() == 0);
  Branch (72:5): [True: 134, False: 0]
73
74
    // Counters
75
134
    std::atomic_uint32_t task_counter{0};
76
134
    uint32_t fail_counter{0};
77
134
    uint32_t expected_task_counter{0};
78
134
    uint32_t expected_fail_tasks{0};
79
80
134
    std::queue<std::future<void>> futures;
81
81.3k
    for (uint32_t i = 0; i < num_tasks; ++i) {
  Branch (81:26): [True: 81.1k, False: 134]
82
81.1k
        const bool will_throw = fuzzed_data_provider.ConsumeBool();
83
81.1k
        const bool wait_immediately = fuzzed_data_provider.ConsumeBool();
84
85
81.1k
        std::future<void> fut;
86
81.1k
        if (will_throw) {
  Branch (86:13): [True: 12.3k, False: 68.8k]
87
12.3k
            expected_fail_tasks++;
88
12.3k
            fut = *Assert(g_pool.Submit(ThrowTask{}));
89
68.8k
        } else {
90
68.8k
            expected_task_counter++;
91
68.8k
            fut = *Assert(g_pool.Submit(CounterTask{task_counter}));
92
68.8k
        }
93
94
        // If caller wants to wait immediately, consume the future here (safe).
95
81.1k
        if (wait_immediately) {
  Branch (95:13): [True: 12.2k, False: 68.9k]
96
            // Waits for this task to complete immediately; prior queued tasks may also complete
97
            // as they were queued earlier.
98
12.2k
            GetFuture(fut, fail_counter);
99
68.9k
        } else {
100
            // Store task for a posterior check
101
68.9k
            futures.emplace(std::move(fut));
102
68.9k
        }
103
81.1k
    }
104
105
    // Drain remaining futures
106
69.0k
    while (!futures.empty()) {
  Branch (106:12): [True: 68.9k, False: 134]
107
68.9k
        auto fut = std::move(futures.front());
108
68.9k
        futures.pop();
109
68.9k
        GetFuture(fut, fail_counter);
110
68.9k
    }
111
112
134
    assert(g_pool.WorkQueueSize() == 0);
  Branch (112:5): [True: 134, False: 0]
113
134
    assert(task_counter.load() == expected_task_counter);
  Branch (113:5): [True: 134, False: 0]
114
134
    assert(fail_counter == expected_fail_tasks);
  Branch (114:5): [True: 134, False: 0]
115
134
}