Skip to content

Commit 3d53da1

Browse files
committed
run-clang-tidy-cached: surface fatal clang-tidy errors instead of caching empty output
Previously, when clang-tidy exited non-zero without producing any check findings (e.g. bad config, missing compile db, unknown check), stderr was discarded via `2>/dev/null` and an empty output file was cached as if the file had no findings. Subsequent runs would hit the cache and never notice. Redirect stderr into the output, detect non-zero exits with no findings as fatal, stop other worker threads via an atomic flag, and print the captured output before aborting the run. Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
1 parent ae0cf47 commit 3d53da1

1 file changed

Lines changed: 40 additions & 1 deletion

File tree

etc/run-clang-tidy-cached.cc

100755100644
Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ B=${0%%.cc}; [ "$B" -nt "$0" ] || c++ -std=c++17 -o"$B" "$0" && exec "$B" "$@";
3939
#include <unistd.h>
4040

4141
#include <algorithm>
42+
#include <atomic>
4243
#include <cerrno>
4344
#include <cinttypes>
4445
#include <csignal>
@@ -308,8 +309,15 @@ class ClangTidyRunner
308309

309310
const std::string uniquifier = "." + std::to_string(getpid());
310311
std::mutex queue_access_lock;
312+
std::atomic<bool> fatal_stop{false};
313+
std::string fatal_output;
314+
std::string fatal_file;
315+
std::mutex fatal_lock;
311316
auto clang_tidy_runner = [&]() {
312317
for (;;) {
318+
if (fatal_stop.load()) {
319+
return;
320+
}
313321
filepath_contenthash_t work;
314322
{
315323
const std::lock_guard<std::mutex> lock(queue_access_lock);
@@ -328,7 +336,7 @@ class ClangTidyRunner
328336
// it is easy to find with `ps` or `top`.
329337
const std::string command = clang_tidy_ + " '" + work.first.string()
330338
+ "'" + clang_tidy_args_ + "> '" + tmp_out
331-
+ "' 2>/dev/null";
339+
+ "' 2>&1";
332340
const int r = system(command.c_str());
333341
#ifdef WIFSIGNALED
334342
// NOLINTBEGIN
@@ -340,6 +348,31 @@ class ClangTidyRunner
340348
}
341349
// NOLINTEND
342350
#endif
351+
// Detect systemic clang-tidy failure (bad config, missing compile db,
352+
// etc.): non-zero exit with no check findings in output. Abort the
353+
// run and surface the error instead of silently caching empty output.
354+
#ifdef WIFEXITED
355+
// NOLINTBEGIN
356+
if (WIFEXITED(r) && WEXITSTATUS(r) != 0) {
357+
// NOLINTEND
358+
#else
359+
if (r != 0) {
360+
#endif
361+
const std::string out = GetContent(tmp_out);
362+
static const std::regex check_finding(
363+
R"(\[[a-zA-Z.]+-[a-zA-Z.-]+\])");
364+
if (!std::regex_search(out, check_finding)) {
365+
bool expected = false;
366+
if (fatal_stop.compare_exchange_strong(expected, true)) {
367+
const std::lock_guard<std::mutex> lock(fatal_lock);
368+
fatal_output = out;
369+
fatal_file = work.first.string();
370+
}
371+
std::error_code ignored_error;
372+
fs::remove(tmp_out, ignored_error);
373+
return;
374+
}
375+
}
343376
const std::string filter_filename = work.first.filename().string();
344377
RepairFilenameOccurences(filter_filename, tmp_out, tmp_out);
345378
fs::rename(tmp_out, final_out); // atomic replacement
@@ -356,6 +389,12 @@ class ClangTidyRunner
356389
if (print_progress) {
357390
fprintf(stderr, " \n"); // Clean out progress counter.
358391
}
392+
if (fatal_stop.load()) {
393+
std::cerr << "\nclang-tidy failed on " << fatal_file
394+
<< " (aborting; output below):\n"
395+
<< fatal_output << "\n";
396+
exit(EXIT_FAILURE);
397+
}
359398
}
360399

361400
private:

0 commit comments

Comments
 (0)