blob: fa8e2f383ed709a16649330eee8f614510753d21 [file] [log] [blame]
Adrian Taylor997418932024-12-20 15:21:321#!/usr/bin/env python3
2# Copyright 2024 The Chromium Authors
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5"""Provides a local HTML report of the ClusterFuzz explorations
6by a given fuzzer.
7
8 * Example usage: view_fuzz_coverage.py --fuzzer my_fuzzer_binary
9"""
10
11import argparse
12import os
13import subprocess
14import sys
15import tempfile
16import pathlib
17
18script_dir = os.path.dirname(os.path.realpath(__file__))
19chromium_src_dir = os.path.dirname(os.path.dirname(script_dir))
20
21# These may evolve over time, so if this script doesn't work, you may
22# need to adjust these. In an ideal world we'd look these up from LUCI
23# infrastructure but we're intentionally making a local script somewhat
24# equivalentt to LUCI infrastructure, so for now let's not rely on that.
25gn_args = """
26dcheck_always_on = false
27enable_mojom_fuzzer = true
28ffmpeg_branding = "ChromeOS"
29is_component_build = false
30is_debug = false
31pdf_enable_xfa = true
32proprietary_codecs = true
33use_clang_coverage = true
34use_libfuzzer = true
35use_remoteexec = true
36symbol_level = 2
37"""
38
39
40def _ParseCommandArguments():
41 """Adds and parses relevant arguments for tool comands.
42
43 Returns:
44 A dictionary representing the arguments.
45 """
46 arg_parser = argparse.ArgumentParser()
47 arg_parser.usage = __doc__
48
49 arg_parser.add_argument('--fuzzer',
50 required=True,
51 type=str,
52 help='Fuzzer binary name.')
53 arg_parser.add_argument('--build-dir',
54 default=os.path.join(chromium_src_dir, 'out',
55 'coverage'),
56 help='Where to build fuzzers.')
57 arg_parser.add_argument('--html-dir',
58 default=os.path.join(chromium_src_dir, 'out',
59 'coverage-html'),
60 help='Where to put HTML report.')
61 arg_parser.add_argument(
62 '--retain-build-dir',
63 action='store_true',
64 help=
65 'Avoid cleaning the build dir (may result in multiple fuzzers being analyzed).'
66 )
67 args = arg_parser.parse_args()
68 return args
69
70
71def step(name):
72 """Print a banner for the upcoming task.."""
73 print("==== " + name + " ====:")
74
75
76def check_call(args, *, cwd=None, shell=False):
77 """Equivalent to subprocess.check_call but logs command."""
78 print(" ".join(args))
79 subprocess.check_call(args, cwd=cwd, shell=shell)
80
81
82def Main():
83 args = _ParseCommandArguments()
84
85 os.makedirs(args.build_dir, exist_ok=True)
86 os.makedirs(args.html_dir, exist_ok=True)
87
88 step("Writing gn args")
89 gn_args_file = os.path.join(args.build_dir, "args.gn")
90 with open(gn_args_file, "w") as f:
91 f.write(gn_args)
92
93 if not args.retain_build_dir:
94 step("gn clean")
95 check_call(["gn", "clean", args.build_dir], cwd=chromium_src_dir)
96 step("gn gen")
97 check_call(["gn", "gen", args.build_dir], cwd=chromium_src_dir)
98 step("autoninja")
99 check_call(["autoninja", "-C", args.build_dir, args.fuzzer])
100 corpora_dir = tempfile.TemporaryDirectory()
101 step("Download corpora")
102 check_call([
103 sys.executable,
104 os.path.join(script_dir, "download_fuzz_corpora.py"), "--download-dir",
105 corpora_dir.name, "--build-dir", args.build_dir
106 ])
107 individual_profdata_dir = tempfile.TemporaryDirectory()
108 step(
109 "Running fuzzers (can take a while - NB you might need a valid DISPLAY set for some fuzzers)"
110 )
111 check_call([
112 sys.executable,
113 os.path.join(script_dir, "run_all_fuzzers.py"), "--fuzzer-binaries-dir",
114 args.build_dir, "--fuzzer-corpora-dir", corpora_dir.name,
115 "--profdata-outdir", individual_profdata_dir.name
116 ])
117 step("Merging profdata")
118 merged_profdata_dir = tempfile.TemporaryDirectory()
119 merged_profdata_file = os.path.join(merged_profdata_dir.name, "out.profdata")
120 llvm_dir = os.path.join(chromium_src_dir, "third_party", "llvm-build",
121 "Release+Asserts", "bin")
122 check_call([
123 sys.executable,
124 os.path.join(script_dir, "merge_all_profdata.py"), "--profdata-dir",
125 individual_profdata_dir.name, "--outfile", merged_profdata_file,
126 "--llvm-profdata",
127 os.path.join(llvm_dir, "llvm-profdata")
128 ])
129 step("Generating HTML")
130 check_call([
131 os.path.join(llvm_dir, "llvm-cov"), "show", args.fuzzer, "-format=html",
132 "-instr-profile", merged_profdata_file, "-output-dir", args.html_dir
133 ],
134 cwd=args.build_dir)
135 uri = pathlib.Path(os.path.join(args.html_dir, "index.html")).as_uri()
136 print("Report URI " + uri)
137 step("Opening HTML in Chrome")
138 check_call(["google-chrome-stable", uri], shell=True)
139
140
141if __name__ == '__main__':
142 sys.exit(Main())