blob: daa9d03a26ffd5cdd15b53f2c71b6465c53bff19 [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)
Manish V Badarkhed3a61fc2022-03-30 09:52:58 +010084 results = re.search("(?:COMPILING|EXECUTING):.*-c *(.*\.c).*-o.*\.o", line)
Fathi Boudra422bf772019-12-02 11:10:16 +020085 if results is not None:
86 filename = results.group(1)
87 if filename not in analyzed:
88 analyzed.append(filename)
89
90 # Now get the list of C files in the Trusted Firmware source tree.
91 # Header files and assembly files are ignored, as well as anything that
92 # matches the patterns list in the exclude_paths[] list.
93 # Build a list of files that are in this source tree but were not analyzed
94 # by comparing the 2 sets of files.
95 all_files_count = 0
96 old_cwd = os.path.abspath(os.curdir)
97 os.chdir(tf_dir)
98 git_process = utils.exec_prog("git", ["ls-files", "*.c"],
99 out=subprocess.PIPE, out_text_mode=True)
100 for filename in git_process.stdout:
101 # Remove final \n in filename
102 filename = filename.strip()
103
104 def is_excluded(filename, excludes):
105 for pattern in excludes:
106 if re.match(pattern[0], filename):
107 excluded.append((filename, pattern[1]))
108 return True
109 return False
110
111 if is_excluded(filename, exclude_paths):
112 continue
113
114 # Keep track of the number of C files in the source tree. Used to
115 # compute the coverage percentage at the end.
116 all_files_count += 1
117 if filename not in analyzed:
118 not_analyzed.append(filename)
119 os.chdir(old_cwd)
120
121 # Compute the coverage percentage
122 # Note: The 1.0 factor here is used to make a float division instead of an
123 # integer one.
124 percentage = (1 - ((1.0 * len(not_analyzed) ) / all_files_count)) * 100
125
126 #
127 # Print a report
128 #
129 log_file.write("Files coverage: %d%%\n\n" % percentage)
130 log_file.write("Analyzed %d files\n" % len(analyzed))
131
132 if len(excluded) > 0:
133 log_file.write("\n%d files were ignored on purpose:\n" % len(excluded))
134 for exc in excluded:
135 log_file.write(" - {0:50} (Reason: {1})\n".format(exc[0], exc[1]))
136
137 if len(not_analyzed) > 0:
138 log_file.write("\n%d files were not analyzed:\n" % len(not_analyzed))
139 for f in not_analyzed:
140 log_file.write(" - %s\n" % f)
141 log_file.write("""
142===============================================================================
143Please investigate why the above files are not run through Coverity.
144
145There are 2 possible reasons:
146
1471) The build coverage is insufficient. Please review the tf-cov-make script to
148 add the missing build config(s) that will involve the file in the build.
149
1502) The file is expected to be ignored, for example because it is deprecated
151 code. Please update the TF Coverity configuration to list the file and
152 indicate the reason why it is safe to ignore it.
153===============================================================================
154""")
155 log_file.close()
156
157
158def parse_cmd_line(argv, prog_name):
159 parser = argparse.ArgumentParser(
160 prog=prog_name,
161 description="Run Coverity on Trusted Firmware",
162 epilog="""
163 Please ensure the AArch64 & AArch32 cross-toolchains are loaded in your
164 PATH. Ditto for the Coverity tools. If you don't have the latter then
165 you can use the --get-coverity-tool to download them for you.
166 """)
167 parser.add_argument("--tf", default=None,
168 metavar="<Trusted Firmware source dir>",
169 help="Specify the location of ARM Trusted Firmware sources to analyze")
170 parser.add_argument("--get-coverity-tool", default=False,
171 help="Download the Coverity build tool and exit",
172 action="store_true")
173 parser.add_argument("--mode", choices=["offline", "online"], default="online",
174 help="Choose between online or offline mode for the analysis")
175 parser.add_argument("--output", "-o",
176 help="Name of the output file containing the results of the analysis")
177 parser.add_argument("--build-cmd", "-b",
178 help="Command used to build TF through Coverity")
179 parser.add_argument("--analysis-profile", "-p",
180 action="append", nargs=1,
181 help="Analysis profile for a local analysis")
182 args = parser.parse_args(argv)
183
184 # Set a default name for the output file if none is provided.
185 # If running in offline mode, this will be a text file;
186 # If running in online mode, this will be a tarball name.
187 if not args.output:
188 if args.mode == "offline":
189 args.output = "arm-tf-coverity-report.txt"
190 else:
191 args.output = "arm-tf-coverity-results.tgz"
192
193 return args
194
195
196if __name__ == "__main__":
197 prog_name = sys.argv[0]
198 args = parse_cmd_line(sys.argv[1:], prog_name)
199
200 # If the user asked to download the Coverity build tool then just do that
201 # and exit.
202 if args.get_coverity_tool:
203 # If running locally, use the commercial version of Coverity from the
204 # EUHPC cluster.
205 if args.mode == "offline":
206 print("To load the Coverity tools, use the following command:")
207 print("export PATH=/arm/tools/coverity/static-analysis/8.7.1/bin/:$PATH")
208 else:
209 get_coverity_tool()
210 sys.exit(0)
211
212 if args.tf is None:
213 print("ERROR: Please specify the Trusted Firmware sources using the --tf option.",
214 file=sys.stderr)
215 sys.exit(1)
216
217 # Get some important paths in the platform-ci scripts
218 tf_scripts_dir = os.path.abspath(os.path.dirname(prog_name))
219 tf_coverity_dir = os.path.join(os.path.normpath(
220 os.path.join(tf_scripts_dir, os.pardir, os.pardir)),"coverity")
221
222 if not args.build_cmd:
223 tf_build_script = os.path.join(tf_scripts_dir, "tf-cov-make")
224 args.build_cmd = tf_build_script + " " + args.tf
225
226 run_coverity_script = os.path.join(tf_coverity_dir, "run_coverity.sh")
227
228 ret = subprocess.call([run_coverity_script, "check_tools", args.mode])
229 if ret != 0:
230 sys.exit(1)
231
232 ret = subprocess.call([run_coverity_script, "configure"])
233 if ret != 0:
234 sys.exit(1)
235
236 ret = subprocess.call([run_coverity_script, "build", args.build_cmd])
237 if ret != 0:
238 sys.exit(1)
239
240 if args.mode == "online":
241 ret = subprocess.call([run_coverity_script, "package", args.output])
242 else:
243 for profile in args.analysis_profile:
244 ret = subprocess.call([run_coverity_script, "analyze",
245 args.output,
246 args.tf,
247 profile[0]])
248 if ret != 0:
249 break
250 if ret != 0:
251 print("An error occured (%d)." % ret, file=sys.stderr)
252 sys.exit(ret)
253
254 print("-----------------------------------------------------------------")
255 print("Results can be found in file '%s'" % args.output)
256 if args.mode == "online":
257 print("This tarball can be uploaded at Coverity Scan Online:" )
258 print("https://scan.coverity.com/projects/arm-software-arm-trusted-firmware/builds/new?tab=upload")
259 print("-----------------------------------------------------------------")
260
Madhukar Pappireddyd9df3562025-03-20 19:35:13 +0100261 print_coverage("cov-int", args.tf, coverity_tf_conf.exclude_paths, "tf_coverage.log")
262 with open("tf_coverage.log") as log_file:
263 for line in log_file:
264 print(line, end="")