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