Skip to content

Commit c80029e

Browse files
committed
Fix: postprocess compiler errors with --generate-reproducer
When --generate-reproducer is enabled, failed analyses were not postprocessed, causing compiler errors to not appear as reports. Moved postprocessing and cleanup logic into handle_analysis_result() to ensure it runs regardless of reproducer generation. Removed analyzer-specific checker logic and unused handle_failure() function.
1 parent 463081e commit c80029e

2 files changed

Lines changed: 81 additions & 36 deletions

File tree

analyzer/codechecker_analyzer/analysis_manager.py

Lines changed: 25 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131
from . import gcc_toolchain
3232

3333
from .analyzers import analyzer_types
34-
from .analyzers.config_handler import CheckerState
3534
from .analyzers.clangsa.analyzer import ClangSA
35+
from .analyzers.config_handler import CheckerState
3636

3737
LOG = get_logger('analyzer')
3838

@@ -305,31 +305,6 @@ def handle_reproducer(source_analyzer, rh, zip_file, actions_map):
305305
LOG.debug("ZIP file written at '%s'", zip_file)
306306

307307

308-
def handle_failure(
309-
source_analyzer, rh, zip_file, result_base, actions_map, skip_handlers,
310-
rs_handler: ReviewStatusHandler
311-
):
312-
"""
313-
If the analysis fails a debug zip is packed together which contains
314-
build, analysis information and source files to be able to
315-
reproduce the failed analysis.
316-
"""
317-
handle_reproducer(source_analyzer, rh, zip_file, actions_map)
318-
319-
# In case of compiler errors the error message still needs to be collected
320-
# from the standard output by this postprocess phase so we can present them
321-
# as CodeChecker reports.
322-
checks = source_analyzer.config_handler.checks()
323-
state = checks.get('clang-diagnostic-error', (CheckerState.ENABLED, ''))[0]
324-
if state == CheckerState.ENABLED:
325-
rh.postprocess_result(skip_handlers, rs_handler)
326-
327-
# Remove files that successfully analyzed earlier on.
328-
plist_file = result_base + ".plist"
329-
if os.path.exists(plist_file):
330-
os.remove(plist_file)
331-
332-
333308
def setup_process_timeout(proc, timeout,
334309
failure_callback=None):
335310
"""
@@ -554,20 +529,34 @@ def handle_analysis_result(success, zip_file=zip_file):
554529
necessary files are collected for debugging. These .zip files can
555530
also be generated by --generate-reproducer flag.
556531
"""
557-
if generate_reproducer:
558-
handle_reproducer(source_analyzer, rh,
559-
os.path.join(reproducer_dir, zip_file),
560-
actions_map)
561-
562532
if success:
563533
handle_success(rh, result_file, result_base,
564534
filter_handlers, rs_handler,
565535
capture_analysis_output, success_dir)
566-
elif not generate_reproducer:
567-
handle_failure(source_analyzer, rh,
568-
os.path.join(failed_dir, zip_file),
569-
result_base, actions_map, skip_handlers,
570-
rs_handler)
536+
else:
537+
# Postprocess failed analyses to capture compiler errors
538+
# in reports/statistics, unless clang-diagnostic-error
539+
# checker is explicitly disabled.
540+
checks = source_analyzer.config_handler.checks()
541+
state = checks.get('clang-diagnostic-error',
542+
(CheckerState.ENABLED, ''))[0]
543+
if state != CheckerState.DISABLED:
544+
rh.postprocess_result(skip_handlers, rs_handler)
545+
546+
# Remove stale plist files from previous successful runs
547+
plist_file = result_base + ".plist"
548+
if os.path.exists(plist_file):
549+
os.remove(plist_file)
550+
551+
# Generate reproducer or failure zip
552+
if generate_reproducer:
553+
handle_reproducer(source_analyzer, rh,
554+
os.path.join(reproducer_dir, zip_file),
555+
actions_map)
556+
elif not success:
557+
handle_reproducer(source_analyzer, rh,
558+
os.path.join(failed_dir, zip_file),
559+
actions_map)
571560

572561
if rh.analyzer_returncode == 0:
573562
handle_analysis_result(success=True)

analyzer/tests/functional/analyze/test_analyze.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,62 @@ def test_reproducer(self):
484484

485485
shutil.rmtree(reproducer_dir)
486486

487+
def test_reproducer_postprocesses_failures(self):
488+
"""
489+
Test that --generate-reproducer still postprocesses compiler errors.
490+
This is a regression test for a bug where --generate-reproducer
491+
prevented postprocessing, causing failed analyses to not appear in
492+
statistics or parse output.
493+
"""
494+
build_json = os.path.join(self.test_workspace, "build.json")
495+
reproducer_dir = os.path.join(self.report_dir, "reproducer")
496+
source_file = os.path.join(self.test_dir, "failure.c")
497+
498+
build_log = [{"directory": self.test_workspace,
499+
"command": "gcc -c " + source_file,
500+
"file": source_file}]
501+
502+
with open(build_json, 'w',
503+
encoding="utf-8", errors="ignore") as outfile:
504+
json.dump(build_log, outfile)
505+
506+
# Analyze with --generate-reproducer
507+
analyze_cmd = [self._codechecker_cmd, "analyze", build_json,
508+
"--analyzers", "clang-tidy",
509+
"-o", self.report_dir, "--generate-reproducer"]
510+
511+
process = subprocess.Popen(
512+
analyze_cmd,
513+
stdout=subprocess.PIPE,
514+
stderr=subprocess.PIPE,
515+
cwd=self.test_dir,
516+
encoding="utf-8",
517+
errors="ignore")
518+
out, _ = process.communicate()
519+
520+
# Verify that the failed analysis appears in statistics
521+
self.assertIn("Failed to analyze", out)
522+
self.assertIn("failure.c", out)
523+
524+
# Parse the reports and verify the file is counted
525+
# Before the fix, this would show "0" processed files
526+
parse_cmd = [self._codechecker_cmd, "parse", self.report_dir]
527+
process = subprocess.Popen(
528+
parse_cmd,
529+
stdout=subprocess.PIPE,
530+
stderr=subprocess.PIPE,
531+
cwd=self.test_dir,
532+
encoding="utf-8",
533+
errors="ignore")
534+
out, _ = process.communicate()
535+
536+
# The key bug: with --generate-reproducer, postprocessing was skipped
537+
# so parse would show 0 processed files. After the fix, it should
538+
# show 1 processed file.
539+
self.assertIn("Number of processed analyzer result files | 1", out)
540+
541+
shutil.rmtree(reproducer_dir)
542+
487543
def test_robustness_for_dependencygen_failure(self):
488544
"""
489545
Test if failure ZIP is created even if the dependency generator creates

0 commit comments

Comments
 (0)