blob: fb2f4019fe02c553c49d54ea4d524be016c2baaa [file] [log] [blame]
Leonardo Sandovald05adab2020-08-10 14:01:54 -05001#!/usr/bin/env python3
2#
Xinyu Zhangde20cda2023-08-14 14:46:35 +08003# Copyright (c) 2019-2023, Arm Limited. All rights reserved.
Leonardo Sandovald05adab2020-08-10 14:01:54 -05004#
5# SPDX-License-Identifier: BSD-3-Clause
6#
7
8#
9# Run the Coverity tool on the Trusted Firmware and produce a tarball ready to
10# be submitted to Coverity Scan Online.
11#
12
13import sys
14import argparse
15import urllib.request
16import tarfile
17import os
18import subprocess
19import re
Leonardo Sandovalc503d7e2020-08-10 15:47:11 -050020
21# local libraries
22sys.path.append(os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "script", "tf-coverity")))
Leonardo Sandovald05adab2020-08-10 14:01:54 -050023import utils
24import coverity_tf_conf
25
26
27def tarball_name(filename):
28 "Isolate the tarball name without the filename's extension."
29 # Handle a selection of "composite" extensions
30 for ext in [".tar.gz", ".tar.bz2"]:
31 if filename.endswith(ext):
32 return filename[:-len(ext)]
33 # For all other extensions, let the vanilla splitext() function handle it
34 return os.path.splitext(filename)[0]
35
36assert tarball_name("foo.gz") == "foo"
37assert tarball_name("bar.tar.gz") == "bar"
38assert tarball_name("baz.tar.bz2") == "baz"
39
Leonardo Sandovald05adab2020-08-10 14:01:54 -050040def print_coverage(coverity_dir, tf_dir, exclude_paths=[], log_filename=None):
41 analyzed = []
42 not_analyzed = []
43 excluded = []
44
45 # Print the coverage report to a file (or stdout if no file is specified)
46 if log_filename is not None:
47 log_file = open(log_filename, "w")
48 else:
49 log_file = sys.stdout
50
51 # Get the list of files analyzed by Coverity.
52 #
53 # To do that, we examine the build log file Coverity generated and look for
54 # compilation lines. These are the lines starting with "COMPILING:" or
55 # "EXECUTING:". We consider only those lines that actually compile C files,
56 # i.e. lines of the form:
57 # gcc -c file.c -o file.o
58 # This filters out other compilation lines like generation of dependency files
59 # (*.d) and such.
60 # We then extract the C filename.
61 coverity_build_log = os.path.join(coverity_dir, "build-log.txt")
62 with open(coverity_build_log, encoding="utf-8") as build_log:
63 for line in build_log:
64 line = re.sub('//','/', line)
Leonardo Sandovalc503d7e2020-08-10 15:47:11 -050065 results = re.search("(?:COMPILING|EXECUTING):.*-o.*\.o .*-c (.*\.c)", line)
Leonardo Sandovald05adab2020-08-10 14:01:54 -050066 if results is not None:
67 filename = results.group(1)
68 if filename not in analyzed:
69 analyzed.append(filename)
70
71 # Now get the list of C files in the Trusted Firmware source tree.
72 # Header files and assembly files are ignored, as well as anything that
73 # matches the patterns list in the exclude_paths[] list.
74 # Build a list of files that are in this source tree but were not analyzed
75 # by comparing the 2 sets of files.
76 all_files_count = 0
77 old_cwd = os.path.abspath(os.curdir)
78 os.chdir(tf_dir)
79 git_process = utils.exec_prog("git", ["ls-files", "*.c"],
80 out=subprocess.PIPE, out_text_mode=True)
81 for filename in git_process.stdout:
82 # Remove final \n in filename
83 filename = filename.strip()
84
Leonardo Sandovalc503d7e2020-08-10 15:47:11 -050085 # Expand to absolute path
86 filename = os.path.abspath(filename)
87
Leonardo Sandovald05adab2020-08-10 14:01:54 -050088 def is_excluded(filename, excludes):
89 for pattern in excludes:
90 if re.match(pattern[0], filename):
91 excluded.append((filename, pattern[1]))
92 return True
93 return False
94
95 if is_excluded(filename, exclude_paths):
96 continue
97
98 # Keep track of the number of C files in the source tree. Used to
99 # compute the coverage percentage at the end.
100 all_files_count += 1
101 if filename not in analyzed:
102 not_analyzed.append(filename)
103 os.chdir(old_cwd)
104
105 # Compute the coverage percentage
106 # Note: The 1.0 factor here is used to make a float division instead of an
107 # integer one.
108 percentage = (1 - ((1.0 * len(not_analyzed) ) / all_files_count)) * 100
109
110 #
111 # Print a report
112 #
Leonardo Sandovald05adab2020-08-10 14:01:54 -0500113
114 if len(excluded) > 0:
115 log_file.write("\n%d files were ignored on purpose:\n" % len(excluded))
116 for exc in excluded:
117 log_file.write(" - {0:50} (Reason: {1})\n".format(exc[0], exc[1]))
118
Leonardo Sandovalc503d7e2020-08-10 15:47:11 -0500119 if len(analyzed) > 0:
120 log_file.write("\n%d files analyzed:\n" % len(analyzed))
121 for f in analyzed:
122 log_file.write(" - %s\n" % f)
123
Leonardo Sandovald05adab2020-08-10 14:01:54 -0500124 if len(not_analyzed) > 0:
125 log_file.write("\n%d files were not analyzed:\n" % len(not_analyzed))
126 for f in not_analyzed:
127 log_file.write(" - %s\n" % f)
128 log_file.write("""
129===============================================================================
130Please investigate why the above files are not run through Coverity.
131
132There are 2 possible reasons:
133
1341) The build coverage is insufficient. Please review the tf-cov-make script to
135 add the missing build config(s) that will involve the file in the build.
136
1372) The file is expected to be ignored, for example because it is deprecated
138 code. Please update the TF Coverity configuration to list the file and
139 indicate the reason why it is safe to ignore it.
140===============================================================================
141""")
Xinyu Zhangde20cda2023-08-14 14:46:35 +0800142
143 log_file.write("\n\n\nFiles coverage: %d%%\n\n" % percentage)
144 log_file.write("Analyzed %d files\n\n\n" % len(analyzed))
145
Leonardo Sandovald05adab2020-08-10 14:01:54 -0500146 log_file.close()
147
148
149def parse_cmd_line(argv, prog_name):
150 parser = argparse.ArgumentParser(
151 prog=prog_name,
Xinyu Zhangde20cda2023-08-14 14:46:35 +0800152 description="Run Coverity on Trusted Firmware M",
Leonardo Sandovald05adab2020-08-10 14:01:54 -0500153 epilog="""
Xinyu Zhangde20cda2023-08-14 14:46:35 +0800154 Please ensure the GNU toolchains are loaded in your PATH.
155 Ditto for the Coverity tools.
Leonardo Sandovald05adab2020-08-10 14:01:54 -0500156 """)
157 parser.add_argument("--tf", default=None,
Xinyu Zhangde20cda2023-08-14 14:46:35 +0800158 metavar="<Trusted Firmware M source dir>",
159 help="Specify the location of Trusted Firmware M sources to analyze")
Leonardo Sandovald05adab2020-08-10 14:01:54 -0500160 parser.add_argument("--mode", choices=["offline", "online"], default="online",
161 help="Choose between online or offline mode for the analysis")
162 parser.add_argument("--output", "-o",
163 help="Name of the output file containing the results of the analysis")
164 parser.add_argument("--build-cmd", "-b",
165 help="Command used to build TF through Coverity")
166 parser.add_argument("--analysis-profile", "-p",
167 action="append", nargs=1,
168 help="Analysis profile for a local analysis")
169 args = parser.parse_args(argv)
170
171 # Set a default name for the output file if none is provided.
172 # If running in offline mode, this will be a text file;
173 # If running in online mode, this will be a tarball name.
174 if not args.output:
175 if args.mode == "offline":
176 args.output = "arm-tf-coverity-report.txt"
177 else:
178 args.output = "arm-tf-coverity-results.tgz"
179
180 return args
181
182
183if __name__ == "__main__":
184 prog_name = sys.argv[0]
185 args = parse_cmd_line(sys.argv[1:], prog_name)
186
Leonardo Sandovald05adab2020-08-10 14:01:54 -0500187 if args.tf is None:
Xinyu Zhangde20cda2023-08-14 14:46:35 +0800188 print("ERROR: Please specify the Trusted Firmware M sources using the --tf option.",
Leonardo Sandovald05adab2020-08-10 14:01:54 -0500189 file=sys.stderr)
190 sys.exit(1)
191
192 # Get some important paths in the platform-ci scripts
Leonardo Sandovalc503d7e2020-08-10 15:47:11 -0500193 tf_root_dir = os.path.abspath(os.path.dirname(prog_name))
Leonardo Sandovald05adab2020-08-10 14:01:54 -0500194
195 if not args.build_cmd:
Leonardo Sandovalc503d7e2020-08-10 15:47:11 -0500196 args.build_cmd = os.path.join(tf_root_dir, "script", "tf-coverity", "tf-cov-make")
Leonardo Sandovald05adab2020-08-10 14:01:54 -0500197
Leonardo Sandovalc503d7e2020-08-10 15:47:11 -0500198 run_coverity_script = os.path.join(tf_root_dir, "coverity", "run_coverity.sh")
Leonardo Sandovald05adab2020-08-10 14:01:54 -0500199
200 ret = subprocess.call([run_coverity_script, "check_tools", args.mode])
201 if ret != 0:
202 sys.exit(1)
203
204 ret = subprocess.call([run_coverity_script, "configure"])
205 if ret != 0:
206 sys.exit(1)
207
208 ret = subprocess.call([run_coverity_script, "build", args.build_cmd])
209 if ret != 0:
210 sys.exit(1)
211
212 if args.mode == "online":
213 ret = subprocess.call([run_coverity_script, "package", args.output])
214 else:
215 for profile in args.analysis_profile:
216 ret = subprocess.call([run_coverity_script, "analyze",
217 args.output,
218 args.tf,
219 profile[0]])
220 if ret != 0:
221 break
222 if ret != 0:
223 print("An error occured (%d)." % ret, file=sys.stderr)
224 sys.exit(ret)
225
Leonardo Sandovald05adab2020-08-10 14:01:54 -0500226 print_coverage("cov-int", args.tf, coverity_tf_conf.exclude_paths, "tf_coverage.log")
227 with open("tf_coverage.log") as log_file:
228 for line in log_file:
229 print(line, end="")
Xinyu Zhangde20cda2023-08-14 14:46:35 +0800230
231 print("-----------------------------------------------------------------")
232 print("Results can be found in file '%s'" % args.output)
233 if args.mode == "online":
234 print("This tarball can be uploaded at Coverity Scan Online:" )
235 print("https://scan.coverity.com/builds?project=Trusted+Firmware-M")
236 print("-----------------------------------------------------------------")