blob: 3db17e7e08dfd41c405fbfd947f44cdcbebdf0f4 [file] [log] [blame]
Fathi Boudra422bf772019-12-02 11:10:16 +02001#!/usr/bin/env python3
2#
J-Alves99e31182022-09-14 17:04:19 +01003# Copyright (c) 2019-2022 Arm Limited. All rights reserved.
Fathi Boudra422bf772019-12-02 11:10:16 +02004#
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
20import utils
21import coverity_tf_conf
22
23
24def tarball_name(filename):
25 "Isolate the tarball name without the filename's extension."
26 # Handle a selection of "composite" extensions
27 for ext in [".tar.gz", ".tar.bz2"]:
28 if filename.endswith(ext):
29 return filename[:-len(ext)]
30 # For all other extensions, let the vanilla splitext() function handle it
31 return os.path.splitext(filename)[0]
32
33assert tarball_name("foo.gz") == "foo"
34assert tarball_name("bar.tar.gz") == "bar"
35assert tarball_name("baz.tar.bz2") == "baz"
36
37
38def get_coverity_tool():
Sandrine Bailleuxe2c2ff92023-11-20 11:16:55 +010039 coverity_tarball = "coverity_tool.tgz"
Saheer Babuebfc4a02025-02-10 10:43:51 +000040 url = "${DOWNLOAD_SERVER_URL}/tf-a/tf-a-coverity/" + coverity_tarball
Fathi Boudra422bf772019-12-02 11:10:16 +020041 print("Downloading Coverity Build tool from %s..." % url)
42 file_handle = urllib.request.urlopen(url)
43 output = open(coverity_tarball, "wb")
44 output.write(file_handle.read())
45 output.close()
46 print("Download complete.")
47
48 print("\nUnpacking tarball %s..." % coverity_tarball)
49 tarfile.open(coverity_tarball).extractall()
50 print("Tarball unpacked.")
51
52 print("\nNow please load the Coverity tool in your PATH...")
53 print("E.g.:")
Madhukar Pappireddy5c44bc62025-03-19 14:04:47 -050054 cov_version = "2024.6.1"
Sandrine Bailleuxb8cf0c52023-11-20 11:17:32 +010055 cov_dir_name = "cov-analysis-linux64-" + cov_version
Fathi Boudra422bf772019-12-02 11:10:16 +020056 cov_dir_path = os.path.abspath(os.path.join(cov_dir_name, "bin"))
57 print(" export PATH=%s$PATH" % (cov_dir_path + os.pathsep))
58
Fathi Boudra422bf772019-12-02 11:10:16 +020059def print_coverage(coverity_dir, tf_dir, exclude_paths=[], log_filename=None):
60 analyzed = []
61 not_analyzed = []
62 excluded = []
63
64 # Print the coverage report to a file (or stdout if no file is specified)
65 if log_filename is not None:
66 log_file = open(log_filename, "w")
67 else:
68 log_file = sys.stdout
69
70 # Get the list of files analyzed by Coverity.
71 #
72 # To do that, we examine the build log file Coverity generated and look for
Zelalemc9531f82020-08-04 15:37:08 -050073 # compilation lines. These are the lines starting with "COMPILING:" or
74 # "EXECUTING:". We consider only those lines that actually compile C files,
75 # i.e. lines of the form:
Fathi Boudra422bf772019-12-02 11:10:16 +020076 # gcc -c file.c -o file.o
77 # This filters out other compilation lines like generation of dependency files
78 # (*.d) and such.
79 # We then extract the C filename.
80 coverity_build_log = os.path.join(coverity_dir, "build-log.txt")
81 with open(coverity_build_log, encoding="utf-8") as build_log:
82 for line in build_log:
Zelalemc9531f82020-08-04 15:37:08 -050083 line = re.sub('//','/', line)
Chris Kaycfd97ca2025-06-18 18:38:55 +010084 results = re.search(r"\[STATUS\] Compiling (.+\.c)", line)
Fathi Boudra422bf772019-12-02 11:10:16 +020085 if results is not None:
86 filename = results.group(1)
Chris Kaycfd97ca2025-06-18 18:38:55 +010087
88 if os.path.isabs(filename):
89 filename = os.path.relpath(filename, tf_dir)
90
Fathi Boudra422bf772019-12-02 11:10:16 +020091 if filename not in analyzed:
92 analyzed.append(filename)
93
94 # Now get the list of C files in the Trusted Firmware source tree.
95 # Header files and assembly files are ignored, as well as anything that
96 # matches the patterns list in the exclude_paths[] list.
97 # Build a list of files that are in this source tree but were not analyzed
98 # by comparing the 2 sets of files.
99 all_files_count = 0
100 old_cwd = os.path.abspath(os.curdir)
101 os.chdir(tf_dir)
102 git_process = utils.exec_prog("git", ["ls-files", "*.c"],
103 out=subprocess.PIPE, out_text_mode=True)
104 for filename in git_process.stdout:
105 # Remove final \n in filename
106 filename = filename.strip()
107
108 def is_excluded(filename, excludes):
109 for pattern in excludes:
110 if re.match(pattern[0], filename):
111 excluded.append((filename, pattern[1]))
112 return True
113 return False
114
115 if is_excluded(filename, exclude_paths):
116 continue
117
118 # Keep track of the number of C files in the source tree. Used to
119 # compute the coverage percentage at the end.
120 all_files_count += 1
121 if filename not in analyzed:
122 not_analyzed.append(filename)
123 os.chdir(old_cwd)
124
125 # Compute the coverage percentage
126 # Note: The 1.0 factor here is used to make a float division instead of an
127 # integer one.
128 percentage = (1 - ((1.0 * len(not_analyzed) ) / all_files_count)) * 100
129
130 #
131 # Print a report
132 #
133 log_file.write("Files coverage: %d%%\n\n" % percentage)
134 log_file.write("Analyzed %d files\n" % len(analyzed))
135
136 if len(excluded) > 0:
137 log_file.write("\n%d files were ignored on purpose:\n" % len(excluded))
138 for exc in excluded:
139 log_file.write(" - {0:50} (Reason: {1})\n".format(exc[0], exc[1]))
140
141 if len(not_analyzed) > 0:
142 log_file.write("\n%d files were not analyzed:\n" % len(not_analyzed))
143 for f in not_analyzed:
144 log_file.write(" - %s\n" % f)
145 log_file.write("""
146===============================================================================
147Please investigate why the above files are not run through Coverity.
148
149There are 2 possible reasons:
150
1511) The build coverage is insufficient. Please review the tf-cov-make script to
152 add the missing build config(s) that will involve the file in the build.
153
1542) The file is expected to be ignored, for example because it is deprecated
155 code. Please update the TF Coverity configuration to list the file and
156 indicate the reason why it is safe to ignore it.
157===============================================================================
158""")
159 log_file.close()
160
161
162def parse_cmd_line(argv, prog_name):
163 parser = argparse.ArgumentParser(
164 prog=prog_name,
165 description="Run Coverity on Trusted Firmware",
166 epilog="""
167 Please ensure the AArch64 & AArch32 cross-toolchains are loaded in your
168 PATH. Ditto for the Coverity tools. If you don't have the latter then
169 you can use the --get-coverity-tool to download them for you.
170 """)
171 parser.add_argument("--tf", default=None,
172 metavar="<Trusted Firmware source dir>",
173 help="Specify the location of ARM Trusted Firmware sources to analyze")
174 parser.add_argument("--get-coverity-tool", default=False,
175 help="Download the Coverity build tool and exit",
176 action="store_true")
177 parser.add_argument("--mode", choices=["offline", "online"], default="online",
178 help="Choose between online or offline mode for the analysis")
179 parser.add_argument("--output", "-o",
180 help="Name of the output file containing the results of the analysis")
181 parser.add_argument("--build-cmd", "-b",
182 help="Command used to build TF through Coverity")
183 parser.add_argument("--analysis-profile", "-p",
184 action="append", nargs=1,
185 help="Analysis profile for a local analysis")
186 args = parser.parse_args(argv)
187
188 # Set a default name for the output file if none is provided.
189 # If running in offline mode, this will be a text file;
190 # If running in online mode, this will be a tarball name.
191 if not args.output:
192 if args.mode == "offline":
193 args.output = "arm-tf-coverity-report.txt"
194 else:
195 args.output = "arm-tf-coverity-results.tgz"
196
197 return args
198
199
200if __name__ == "__main__":
201 prog_name = sys.argv[0]
202 args = parse_cmd_line(sys.argv[1:], prog_name)
203
204 # If the user asked to download the Coverity build tool then just do that
205 # and exit.
206 if args.get_coverity_tool:
207 # If running locally, use the commercial version of Coverity from the
208 # EUHPC cluster.
209 if args.mode == "offline":
210 print("To load the Coverity tools, use the following command:")
211 print("export PATH=/arm/tools/coverity/static-analysis/8.7.1/bin/:$PATH")
212 else:
213 get_coverity_tool()
214 sys.exit(0)
215
216 if args.tf is None:
217 print("ERROR: Please specify the Trusted Firmware sources using the --tf option.",
218 file=sys.stderr)
219 sys.exit(1)
220
221 # Get some important paths in the platform-ci scripts
222 tf_scripts_dir = os.path.abspath(os.path.dirname(prog_name))
223 tf_coverity_dir = os.path.join(os.path.normpath(
224 os.path.join(tf_scripts_dir, os.pardir, os.pardir)),"coverity")
225
226 if not args.build_cmd:
227 tf_build_script = os.path.join(tf_scripts_dir, "tf-cov-make")
228 args.build_cmd = tf_build_script + " " + args.tf
229
230 run_coverity_script = os.path.join(tf_coverity_dir, "run_coverity.sh")
231
232 ret = subprocess.call([run_coverity_script, "check_tools", args.mode])
233 if ret != 0:
234 sys.exit(1)
235
236 ret = subprocess.call([run_coverity_script, "configure"])
237 if ret != 0:
238 sys.exit(1)
239
240 ret = subprocess.call([run_coverity_script, "build", args.build_cmd])
241 if ret != 0:
242 sys.exit(1)
243
244 if args.mode == "online":
245 ret = subprocess.call([run_coverity_script, "package", args.output])
246 else:
247 for profile in args.analysis_profile:
248 ret = subprocess.call([run_coverity_script, "analyze",
249 args.output,
250 args.tf,
251 profile[0]])
252 if ret != 0:
253 break
254 if ret != 0:
255 print("An error occured (%d)." % ret, file=sys.stderr)
256 sys.exit(ret)
257
258 print("-----------------------------------------------------------------")
259 print("Results can be found in file '%s'" % args.output)
260 if args.mode == "online":
261 print("This tarball can be uploaded at Coverity Scan Online:" )
262 print("https://scan.coverity.com/projects/arm-software-arm-trusted-firmware/builds/new?tab=upload")
263 print("-----------------------------------------------------------------")
264
Madhukar Pappireddyd9df3562025-03-20 19:35:13 +0100265 print_coverage("cov-int", args.tf, coverity_tf_conf.exclude_paths, "tf_coverage.log")
266 with open("tf_coverage.log") as log_file:
267 for line in log_file:
268 print(line, end="")