Skip to content

Commit c1d01f5

Browse files
committed
fuzz: enable running fuzz test cases in Debug mode
When building with BUILD_FOR_FUZZING=OFF BUILD_FUZZ_BINARY=ON CMAKE_BUILD_TYPE=Debug allow the fuzz binary to execute given test cases (without actual fuzzing) to make it easier to reproduce fuzz test failures in a more normal debug build. In Debug builds, deterministic fuzz behaviour is controlled via a runtime variable, which is normally false, but set to true automatically in the fuzz binary, unless the FUZZ_NONDETERMINISM environment variable is set.
1 parent 639279e commit c1d01f5

File tree

6 files changed

+48
-16
lines changed

6 files changed

+48
-16
lines changed

src/pow.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ bool PermittedDifficultyTransition(const Consensus::Params& params, int64_t heig
139139
// the most significant bit of the last byte of the hash is set.
140140
bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params& params)
141141
{
142-
if constexpr (G_FUZZING) return (hash.data()[31] & 0x80) == 0;
142+
if (EnableFuzzDeterminism()) return (hash.data()[31] & 0x80) == 0;
143143
return CheckProofOfWorkImpl(hash, nBits, params);
144144
}
145145

src/test/fuzz/fuzz.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,18 @@ static void initialize()
147147
std::cerr << "No fuzz target compiled for " << g_fuzz_target << "." << std::endl;
148148
std::exit(EXIT_FAILURE);
149149
}
150-
if constexpr (!G_FUZZING) {
151-
std::cerr << "Must compile with -DBUILD_FOR_FUZZING=ON to execute a fuzz target." << std::endl;
150+
if constexpr (!G_FUZZING_BUILD && !G_ABORT_ON_FAILED_ASSUME) {
151+
std::cerr << "Must compile with -DBUILD_FOR_FUZZING=ON or in Debug mode to execute a fuzz target." << std::endl;
152152
std::exit(EXIT_FAILURE);
153153
}
154+
if (!EnableFuzzDeterminism()) {
155+
if (std::getenv("FUZZ_NONDETERMINISM")) {
156+
std::cerr << "Warning: FUZZ_NONDETERMINISM env var set, results may be inconsistent with fuzz build" << std::endl;
157+
} else {
158+
g_enable_dynamic_fuzz_determinism = true;
159+
assert(EnableFuzzDeterminism());
160+
}
161+
}
154162
Assert(!g_test_one_input);
155163
g_test_one_input = &it->second.test_one_input;
156164
it->second.opts.init();

src/test/util/random.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ void SeedRandomStateForTest(SeedRand seedtype)
2525
// no longer truly random. It should be enough to get the seed once for the
2626
// process.
2727
static const auto g_ctx_seed = []() -> std::optional<uint256> {
28-
if constexpr (G_FUZZING) return {};
28+
if (EnableFuzzDeterminism()) return {};
2929
// If RANDOM_CTX_SEED is set, use that as seed.
3030
if (const char* num{std::getenv(RANDOM_CTX_SEED)}) {
3131
if (auto num_parsed{uint256::FromUserHex(num)}) {
@@ -40,7 +40,7 @@ void SeedRandomStateForTest(SeedRand seedtype)
4040
}();
4141

4242
g_seeded_g_prng_zero = seedtype == SeedRand::ZEROS;
43-
if constexpr (G_FUZZING) {
43+
if (EnableFuzzDeterminism()) {
4444
Assert(g_seeded_g_prng_zero); // Only SeedRandomStateForTest(SeedRand::ZEROS) is allowed in fuzz tests
4545
Assert(!g_used_g_prng); // The global PRNG must not have been used before SeedRandomStateForTest(SeedRand::ZEROS)
4646
}

src/test/util/setup_common.cpp

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ static void ExitFailure(std::string_view str_err)
112112
BasicTestingSetup::BasicTestingSetup(const ChainType chainType, TestOpts opts)
113113
: m_args{}
114114
{
115-
if constexpr (!G_FUZZING) {
115+
if (!EnableFuzzDeterminism()) {
116116
SeedRandomForTest(SeedRand::FIXED_SEED);
117117
}
118118
m_node.shutdown_signal = &m_interrupt;
@@ -203,7 +203,7 @@ BasicTestingSetup::~BasicTestingSetup()
203203
{
204204
m_node.ecc_context.reset();
205205
m_node.kernel.reset();
206-
if constexpr (!G_FUZZING) {
206+
if (!EnableFuzzDeterminism()) {
207207
SetMockTime(0s); // Reset mocktime for following tests
208208
}
209209
LogInstance().DisconnectTestLogger();
@@ -229,8 +229,9 @@ ChainTestingSetup::ChainTestingSetup(const ChainType chainType, TestOpts opts)
229229
m_node.scheduler->m_service_thread = std::thread(util::TraceThread, "scheduler", [&] { m_node.scheduler->serviceQueue(); });
230230
m_node.validation_signals =
231231
// Use synchronous task runner while fuzzing to avoid non-determinism
232-
G_FUZZING ? std::make_unique<ValidationSignals>(std::make_unique<util::ImmediateTaskRunner>()) :
233-
std::make_unique<ValidationSignals>(std::make_unique<SerialTaskRunner>(*m_node.scheduler));
232+
EnableFuzzDeterminism() ?
233+
std::make_unique<ValidationSignals>(std::make_unique<util::ImmediateTaskRunner>()) :
234+
std::make_unique<ValidationSignals>(std::make_unique<SerialTaskRunner>(*m_node.scheduler));
234235
{
235236
// Ensure deterministic coverage by waiting for m_service_thread to be running
236237
std::promise<void> promise;
@@ -255,7 +256,7 @@ ChainTestingSetup::ChainTestingSetup(const ChainType chainType, TestOpts opts)
255256
.notifications = *m_node.notifications,
256257
.signals = m_node.validation_signals.get(),
257258
// Use no worker threads while fuzzing to avoid non-determinism
258-
.worker_threads_num = G_FUZZING ? 0 : 2,
259+
.worker_threads_num = EnableFuzzDeterminism() ? 0 : 2,
259260
};
260261
if (opts.min_validation_cache) {
261262
chainman_opts.script_execution_cache_bytes = 0;

src/util/check.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,5 @@ void assertion_fail(std::string_view file, int line, std::string_view func, std:
3333
fwrite(str.data(), 1, str.size(), stderr);
3434
std::abort();
3535
}
36+
37+
std::atomic<bool> g_enable_dynamic_fuzz_determinism{false};

src/util/check.h

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,44 @@
77

88
#include <attributes.h>
99

10+
#include <atomic>
1011
#include <cassert> // IWYU pragma: export
1112
#include <stdexcept>
1213
#include <string>
1314
#include <string_view>
1415
#include <utility>
1516

16-
constexpr bool G_FUZZING{
17+
constexpr bool G_FUZZING_BUILD{
1718
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1819
true
1920
#else
2021
false
2122
#endif
2223
};
24+
constexpr bool G_ABORT_ON_FAILED_ASSUME{
25+
#ifdef ABORT_ON_FAILED_ASSUME
26+
true
27+
#else
28+
false
29+
#endif
30+
};
31+
32+
extern std::atomic<bool> g_enable_dynamic_fuzz_determinism;
33+
34+
inline bool EnableFuzzDeterminism()
35+
{
36+
if constexpr (G_FUZZING_BUILD) {
37+
return true;
38+
} else if constexpr (!G_ABORT_ON_FAILED_ASSUME) {
39+
// Running fuzz tests is always disabled if Assume() doesn't abort
40+
// (ie, non-fuzz non-debug builds), as otherwise tests which
41+
// should fail due to a failing Assume may still pass. As such,
42+
// we also statically disable fuzz determinism in that case.
43+
return false;
44+
} else {
45+
return g_enable_dynamic_fuzz_determinism;
46+
}
47+
}
2348

2449
std::string StrFormatInternalBug(std::string_view msg, std::string_view file, int line, std::string_view func);
2550

@@ -50,11 +75,7 @@ void assertion_fail(std::string_view file, int line, std::string_view func, std:
5075
template <bool IS_ASSERT, typename T>
5176
constexpr T&& inline_assertion_check(LIFETIMEBOUND T&& val, [[maybe_unused]] const char* file, [[maybe_unused]] int line, [[maybe_unused]] const char* func, [[maybe_unused]] const char* assertion)
5277
{
53-
if (IS_ASSERT || std::is_constant_evaluated() || G_FUZZING
54-
#ifdef ABORT_ON_FAILED_ASSUME
55-
|| true
56-
#endif
57-
) {
78+
if (IS_ASSERT || std::is_constant_evaluated() || G_FUZZING_BUILD || G_ABORT_ON_FAILED_ASSUME) {
5879
if (!val) {
5980
assertion_fail(file, line, func, assertion);
6081
}

0 commit comments

Comments
 (0)