diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml index 39915f9e..c8c2e5dc 100644 --- a/.github/workflows/analysis.yml +++ b/.github/workflows/analysis.yml @@ -94,19 +94,32 @@ jobs: sudo dpkg -i "pvs-package/pvs.deb" pvs-studio-analyzer credentials "${{ secrets.PvsStudioName }}" "${{ secrets.PvsStudioKey }}" - name: Build - run: pvs-studio-analyzer trace -- ./scripts/build.sh -c gcc -t debug + run: | + set -xeu + ./autogen.sh + export FLAGS="-Og" + ./configure CFLAGS="${FLAGS}" CXXFLAGS="${FLAGS}" + pvs-studio-analyzer trace -- make - name: Analyze run: | set -xeu pvs-studio-analyzer analyze -o pvs-analysis.log -j "$(nproc)" - plog-converter -a "64:1;OP:1,2,3;CS:1;MISRA:1,2" \ - -p "dosbox-staging" -v "${GITHUB_SHA:0:8}" -t "fullhtml" \ - -d "V1042" -o "pvs-analysis-report" "pvs-analysis.log" + criteria="GA:1,2;64:1;OP:1,2,3;CS:1;MISRA:1,2" + plog-converter -a "${criteria}" -d V1042 -t csv -o pvs-report.csv pvs-analysis.log + plog-converter -a "${criteria}" -d V1042 -t fullhtml -p dosbox-staging \ + -v "${GITHUB_SHA:0:8}" -o pvs-analysis-report pvs-analysis.log - name: Upload report uses: actions/upload-artifact@master with: name: pvs-analysis-report path: pvs-analysis-report + - name: Summarize report + env: + MAX_BUGS: 356 + run: | + echo "Full report is included in build Artifacts" + echo + ./scripts/count-pvs-bugs.py pvs-report.csv "${MAX_BUGS}" dynamic_matrix: name: ${{ matrix.compiler }} dynamic sanitizers diff --git a/scripts/count-pvs-bugs.py b/scripts/count-pvs-bugs.py new file mode 100755 index 00000000..f8afe5d5 --- /dev/null +++ b/scripts/count-pvs-bugs.py @@ -0,0 +1,78 @@ +#!/usr/bin/python3 + +# Copyright (C) 2020 Kevin Croft +# SPDX-License-Identifier: GPL-2.0-or-later + +""" +Count the number of issues found in an PVS-Studio report. + +Usage: count-pvs-issues.py REPORT [MAX-ISSUES] +Where: + - REPORT is a file in CSV-format + - MAX-ISSUES is as a positive integer indicating the maximum + issues that should be permitted before returning failure + to the shell. Default is non-limit. + +""" + +# pylint: disable=invalid-name +# pylint: disable=missing-docstring + +import collections +import csv +import os +import sys + +def parse_issues(filename): + """ + Returns a dict of int keys and a list of string values, where the: + - keys are V### PVS-Studio error codes + - values are the message of the issue as found in a specific file + + """ + issues = collections.defaultdict(list) + with open(filename) as csvfile: + reader = csv.DictReader(csvfile) + for row in reader: + full = row['ErrorCode'] # extract the full code as an URL string + code = full[full.rfind('V'):full.rfind('"')] # get the trailing "V###" code + if code.startswith('V'): + # Convert the V### string into an integer for easy sorting + issues[int(code[1:])].append(row['Message']) + return issues + + +def main(argv): + # assume success until proven otherwise + rcode = 0 + + # Get the issues and the total tally + issues = parse_issues(argv[1]) + tally = sum(len(messages) for messages in issues.values()) + + if tally > 0: + # Step through the codes and summarize + print("Issues are tallied and sorted by code:\n") + print(" code | issue-string in common to all instances | tally") + print(" ----- --------------------------------------------- -----") + + for code in sorted(issues.keys()): + messages = issues[code] + in_common = os.path.commonprefix(messages)[:45] + if len(in_common.split(' ')) < 4: + in_common = 'N/A (too little in-common between issues)' + print(f' [{code:4}] {in_common:45} : {len(messages)}') + + # Print the tally against the desired maximum + if len(sys.argv) == 3: + max_issues = int(sys.argv[2]) + print(f'\nTotal: {tally} issues (out of {max_issues} allowed)') + if tally > max_issues: + rcode = 1 + else: + print(f'\nTotal: {tally} issues') + + return rcode + +if __name__ == "__main__": + sys.exit(main(sys.argv))