Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 25 additions & 36 deletions analyzer/codechecker_analyzer/analysis_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
from . import gcc_toolchain

from .analyzers import analyzer_types
from .analyzers.config_handler import CheckerState
from .analyzers.clangsa.analyzer import ClangSA
from .analyzers.config_handler import CheckerState

LOG = get_logger('analyzer')

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


def handle_failure(
source_analyzer, rh, zip_file, result_base, actions_map, skip_handlers,
rs_handler: ReviewStatusHandler
):
"""
If the analysis fails a debug zip is packed together which contains
build, analysis information and source files to be able to
reproduce the failed analysis.
"""
handle_reproducer(source_analyzer, rh, zip_file, actions_map)

# In case of compiler errors the error message still needs to be collected
# from the standard output by this postprocess phase so we can present them
# as CodeChecker reports.
checks = source_analyzer.config_handler.checks()
state = checks.get('clang-diagnostic-error', (CheckerState.ENABLED, ''))[0]
if state == CheckerState.ENABLED:
rh.postprocess_result(skip_handlers, rs_handler)

# Remove files that successfully analyzed earlier on.
plist_file = result_base + ".plist"
if os.path.exists(plist_file):
os.remove(plist_file)


def setup_process_timeout(proc, timeout,
failure_callback=None):
"""
Expand Down Expand Up @@ -554,20 +529,34 @@ def handle_analysis_result(success, zip_file=zip_file):
necessary files are collected for debugging. These .zip files can
also be generated by --generate-reproducer flag.
"""
if generate_reproducer:
handle_reproducer(source_analyzer, rh,
os.path.join(reproducer_dir, zip_file),
actions_map)

if success:
handle_success(rh, result_file, result_base,
filter_handlers, rs_handler,
capture_analysis_output, success_dir)
elif not generate_reproducer:
handle_failure(source_analyzer, rh,
os.path.join(failed_dir, zip_file),
result_base, actions_map, skip_handlers,
rs_handler)
else:
# Postprocess failed analyses to capture compiler errors
# in reports/statistics, unless clang-diagnostic-error
# checker is explicitly disabled.
checks = source_analyzer.config_handler.checks()
state = checks.get('clang-diagnostic-error',
(CheckerState.ENABLED, ''))[0]
if state != CheckerState.DISABLED:
rh.postprocess_result(skip_handlers, rs_handler)

# Remove stale plist files from previous successful runs
plist_file = result_base + ".plist"
if os.path.exists(plist_file):
os.remove(plist_file)

# Generate reproducer or failure zip
if generate_reproducer:
handle_reproducer(source_analyzer, rh,
os.path.join(reproducer_dir, zip_file),
actions_map)
elif not success:
handle_reproducer(source_analyzer, rh,
os.path.join(failed_dir, zip_file),
actions_map)

if rh.analyzer_returncode == 0:
handle_analysis_result(success=True)
Expand Down
56 changes: 56 additions & 0 deletions analyzer/tests/functional/analyze/test_analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,62 @@ def test_reproducer(self):

shutil.rmtree(reproducer_dir)

def test_reproducer_postprocesses_failures(self):
"""
Test that --generate-reproducer still postprocesses compiler errors.
This is a regression test for a bug where --generate-reproducer
prevented postprocessing, causing failed analyses to not appear in
statistics or parse output.
"""
build_json = os.path.join(self.test_workspace, "build.json")
reproducer_dir = os.path.join(self.report_dir, "reproducer")
source_file = os.path.join(self.test_dir, "failure.c")

build_log = [{"directory": self.test_workspace,
"command": "gcc -c " + source_file,
"file": source_file}]

with open(build_json, 'w',
encoding="utf-8", errors="ignore") as outfile:
json.dump(build_log, outfile)

# Analyze with --generate-reproducer
analyze_cmd = [self._codechecker_cmd, "analyze", build_json,
"--analyzers", "clang-tidy",
"-o", self.report_dir, "--generate-reproducer"]

process = subprocess.Popen(
analyze_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=self.test_dir,
encoding="utf-8",
errors="ignore")
out, _ = process.communicate()

# Verify that the failed analysis appears in statistics
self.assertIn("Failed to analyze", out)
self.assertIn("failure.c", out)

# Parse the reports and verify the file is counted
# Before the fix, this would show "0" processed files
parse_cmd = [self._codechecker_cmd, "parse", self.report_dir]
process = subprocess.Popen(
parse_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=self.test_dir,
encoding="utf-8",
errors="ignore")
out, _ = process.communicate()

# The key bug: with --generate-reproducer, postprocessing was skipped
# so parse would show 0 processed files. After the fix, it should
# show 1 processed file.
self.assertIn("Number of processed analyzer result files | 1", out)

shutil.rmtree(reproducer_dir)

def test_robustness_for_dependencygen_failure(self):
"""
Test if failure ZIP is created even if the dependency generator creates
Expand Down