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