diff --git a/centipede/puzzles/thread_uint32_cmp_1.cc b/centipede/puzzles/thread_uint32_cmp_1.cc index f2be45a2..a5890477 100644 --- a/centipede/puzzles/thread_uint32_cmp_1.cc +++ b/centipede/puzzles/thread_uint32_cmp_1.cc @@ -16,9 +16,7 @@ // We should be able to solve it w/o cmp features *or* w/o auto dictionary. // RUN: Run && SolutionIs Fuzz // RUN: Run --use_auto_dictionary=0 && SolutionIs Fuzz -// TODO(b/295378866): Centipede runner currently fails to collect dictionary -// entries from merged threads, so if we disable cmp features we can't crack -// this puzzle. Enable "Run --use_cmp_features=0 && SolutionIs Fuzz" +// RUN: Run --use_cmp_features=0 && SolutionIs Fuzz #include #include diff --git a/centipede/runner.cc b/centipede/runner.cc index c4d87161..90d945d9 100644 --- a/centipede/runner.cc +++ b/centipede/runner.cc @@ -151,6 +151,18 @@ void ThreadLocalRunnerState::OnThreadStop() { prev_tls->next = next_tls; if (next_tls != nullptr) next_tls->prev = prev_tls; } + tls.next = tls.prev = nullptr; + if (tls.ignore) return; + // Create a detached copy on heap and add it to detached_tls_list to + // collect its coverage later. + // + // TODO(xinhaoyuan): Consider refactoring the list operations into class + // methods instead of duplicating them. + ThreadLocalRunnerState *detached_tls = new ThreadLocalRunnerState(tls); + auto *old_list = state.detached_tls_list; + detached_tls->next = old_list; + state.detached_tls_list = detached_tls; + if (old_list != nullptr) old_list->prev = detached_tls; } static size_t GetPeakRSSMb() { @@ -233,6 +245,16 @@ static void CheckWatchdogLimits() { } } +void GlobalRunnerState::CleanUpDetachedTls() { + LockGuard lock(tls_list_mu); + ThreadLocalRunnerState *it_next = nullptr; + for (auto *it = detached_tls_list; it; it = it_next) { + it_next = it->next; + delete it; + } + detached_tls_list = nullptr; +} + void GlobalRunnerState::StartWatchdogThread() { if (state.run_time_flags.timeout_per_input == 0 && state.run_time_flags.timeout_per_batch == 0 && @@ -313,6 +335,7 @@ static void WriteFeaturesToFile(FILE *file, const feature_t *features, __attribute__((noinline)) // so that we see it in profile. static void PrepareCoverage(bool full_clear) { + state.CleanUpDetachedTls(); if (state.run_time_flags.path_level != 0) { state.ForEachTls([](ThreadLocalRunnerState &tls) { tls.path_ring_buffer.Reset(state.run_time_flags.path_level); @@ -929,6 +952,8 @@ GlobalRunnerState::~GlobalRunnerState() { StartSendingOutputsToEngine(outputs_blobseq); FinishSendingOutputsToEngine(outputs_blobseq); } + // Always clean up detached TLSs to avoid leakage. + CleanUpDetachedTls(); } // If HasFlag(:shmem:), state.arg1 and state.arg2 are the names diff --git a/centipede/runner.h b/centipede/runner.h index 9d57c2f3..812a51e5 100644 --- a/centipede/runner.h +++ b/centipede/runner.h @@ -111,6 +111,7 @@ struct ThreadLocalRunnerState { CmpTrace<0, 64> cmp_traceN; // Set this to true if the thread needs to be ignored in ForEachTLS. + // It should be always false if the state is in the global detached_tls_list. bool ignore; }; @@ -191,7 +192,9 @@ struct GlobalRunnerState { // Doubly linked list of TLSs of all live threads. ThreadLocalRunnerState *tls_list; - pthread_mutex_t tls_list_mu; // Guards tls_list. + // Doubly linked list of detached TLSs. + ThreadLocalRunnerState *detached_tls_list; + pthread_mutex_t tls_list_mu; // Guards tls_list and detached_tls_list. // Iterates all TLS objects under tls_list_mu, except those with `ignore` set. // Calls `callback()` on every TLS. template @@ -200,8 +203,14 @@ struct GlobalRunnerState { for (auto *it = tls_list; it; it = it->next) { if (!it->ignore) callback(*it); } + for (auto *it = detached_tls_list; it; it = it->next) { + callback(*it); + } } + // Reclaims all TLSs in detached_tls_list and cleans up the list. + void CleanUpDetachedTls(); + // Computed by DlInfo(). // Usually, the main object is the executable binary containing main() // and most of the executable code (we assume that the target is