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