Takuto Ikuta | 3dab32e0 | 2023-01-12 18:52:00 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
Avi Drissman | 73a09d1 | 2022-09-08 20:33:38 | [diff] [blame] | 2 | # Copyright 2018 The Chromium Authors |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 3 | # Use of this source code is governed by a BSD-style license that can be |
| 4 | # found in the LICENSE file. |
Sajjad Mirza | bf9e66a | 2019-03-07 21:49:06 | [diff] [blame] | 5 | """Removes code coverage flags from invocations of the Clang C/C++ compiler. |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 6 | |
Sajjad Mirza | bf9e66a | 2019-03-07 21:49:06 | [diff] [blame] | 7 | If the GN arg `use_clang_coverage=true`, this script will be invoked by default. |
| 8 | GN will add coverage instrumentation flags to almost all source files. |
| 9 | |
| 10 | This script is used to remove instrumentation flags from a subset of the source |
| 11 | files. By default, it will not remove flags from any files. If the option |
| 12 | --files-to-instrument is passed, this script will remove flags from all files |
| 13 | except the ones listed in --files-to-instrument. |
| 14 | |
| 15 | This script also contains hard-coded exclusion lists of files to never |
| 16 | instrument, indexed by target operating system. Files in these lists have their |
| 17 | flags removed in both modes. The OS can be selected with --target-os. |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 18 | |
Sajjad Mirza | 18f89f2 | 2019-12-20 21:18:12 | [diff] [blame] | 19 | This script also contains hard-coded force lists of files to always instrument, |
| 20 | indexed by target operating system. Files in these lists never have their flags |
| 21 | removed in either mode. The OS can be selected with --target-os. |
| 22 | |
| 23 | The order of precedence is: force list, exclusion list, --files-to-instrument. |
| 24 | |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 25 | The path to the coverage instrumentation input file should be relative to the |
| 26 | root build directory, and the file consists of multiple lines where each line |
| 27 | represents a path to a source file, and the specified paths must be relative to |
| 28 | the root build directory. e.g. ../../base/task/post_task.cc for build |
Sajjad Mirza | 18f89f2 | 2019-12-20 21:18:12 | [diff] [blame] | 29 | directory 'out/Release'. The paths should be written using OS-native path |
| 30 | separators for the current platform. |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 31 | |
| 32 | One caveat with this compiler wrapper is that it may introduce unexpected |
| 33 | behaviors in incremental builds when the file path to the coverage |
| 34 | instrumentation input file changes between consecutive runs, so callers of this |
| 35 | script are strongly advised to always use the same path such as |
| 36 | "${root_build_dir}/coverage_instrumentation_input.txt". |
| 37 | |
| 38 | It's worth noting on try job builders, if the contents of the instrumentation |
| 39 | file changes so that a file doesn't need to be instrumented any longer, it will |
| 40 | be recompiled automatically because if try job B runs after try job A, the files |
| 41 | that were instrumented in A will be updated (i.e., reverted to the checked in |
| 42 | version) in B, and so they'll be considered out of date by ninja and recompiled. |
| 43 | |
| 44 | Example usage: |
| 45 | clang_code_coverage_wrapper.py \\ |
| 46 | --files-to-instrument=coverage_instrumentation_input.txt |
Junji Watanabe | 9bd3364 | 2023-07-05 16:27:17 | [diff] [blame] | 47 | |
| 48 | Siso implements the same logic in |
| 49 | build/config/siso/clang_code_coverage_wrapper.star, which avoids the wrapper |
| 50 | invocations for remote execution and performance improvement. |
| 51 | Please update the Siso starlark file when updating this file. |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 52 | """ |
Richard Wang | 91cc90f | 2023-07-25 16:49:12 | [diff] [blame] | 53 | # LINT.IfChange |
Raul Tambre | 9e24293b | 2019-05-12 06:11:07 | [diff] [blame] | 54 | |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 55 | import argparse |
| 56 | import os |
| 57 | import subprocess |
| 58 | import sys |
| 59 | |
| 60 | # Flags used to enable coverage instrumentation. |
Sajjad Mirza | bf9e66a | 2019-03-07 21:49:06 | [diff] [blame] | 61 | # Flags should be listed in the same order that they are added in |
| 62 | # build/config/coverage/BUILD.gn |
Yuke Liao | 5eff782 | 2019-02-28 03:56:24 | [diff] [blame] | 63 | _COVERAGE_FLAGS = [ |
Sajjad Mirza | 18f89f2 | 2019-12-20 21:18:12 | [diff] [blame] | 64 | '-fprofile-instr-generate', |
| 65 | '-fcoverage-mapping', |
Alan Zhao | f8fa3130 | 2024-01-11 03:35:39 | [diff] [blame] | 66 | '-mllvm', |
| 67 | '-runtime-counter-relocation=true', |
Sajjad Mirza | 49c00e3 | 2019-03-01 22:46:52 | [diff] [blame] | 68 | # Following experimental flags remove unused header functions from the |
| 69 | # coverage mapping data embedded in the test binaries, and the reduction |
| 70 | # of binary size enables building Chrome's large unit test targets on |
| 71 | # MacOS. Please refer to crbug.com/796290 for more details. |
Sajjad Mirza | 18f89f2 | 2019-12-20 21:18:12 | [diff] [blame] | 72 | '-mllvm', |
| 73 | '-limited-coverage-experimental=true', |
Yuke Liao | 5eff782 | 2019-02-28 03:56:24 | [diff] [blame] | 74 | ] |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 75 | |
Sajjad Mirza | 750bd0b | 2019-10-16 23:39:51 | [diff] [blame] | 76 | # Files that should not be built with coverage flags by default. |
Junji Watanabe | 782802c | 2023-06-30 08:08:12 | [diff] [blame] | 77 | _DEFAULT_COVERAGE_EXCLUSION_LIST = [] |
Sajjad Mirza | 750bd0b | 2019-10-16 23:39:51 | [diff] [blame] | 78 | |
Sajjad Mirza | bf9e66a | 2019-03-07 21:49:06 | [diff] [blame] | 79 | # Map of exclusion lists indexed by target OS. |
| 80 | # If no target OS is defined, or one is defined that doesn't have a specific |
Sajjad Mirza | 750bd0b | 2019-10-16 23:39:51 | [diff] [blame] | 81 | # entry, use _DEFAULT_COVERAGE_EXCLUSION_LIST. |
Sajjad Mirza | bf9e66a | 2019-03-07 21:49:06 | [diff] [blame] | 82 | _COVERAGE_EXCLUSION_LIST_MAP = { |
Yun Liu | e643df75 | 2019-10-25 19:11:33 | [diff] [blame] | 83 | 'android': [ |
| 84 | # This file caused webview native library failed on arm64. |
| 85 | '../../device/gamepad/dualshock4_controller.cc', |
| 86 | ], |
Chong Gu | 1809f31 | 2021-02-17 18:12:40 | [diff] [blame] | 87 | 'fuchsia': [ |
Alison Gale | d965ba0 | 2024-04-26 21:50:54 | [diff] [blame^] | 88 | # TODO(crbug.com/40167659): These files caused clang to crash while |
Chong Gu | 1809f31 | 2021-02-17 18:12:40 | [diff] [blame] | 89 | # compiling them. |
Arthur Sonzogni | 0bcc023 | 2023-10-03 08:48:32 | [diff] [blame] | 90 | '../../base/allocator/partition_allocator/src/partition_alloc/pcscan.cc', |
Chong Gu | 1809f31 | 2021-02-17 18:12:40 | [diff] [blame] | 91 | '../../third_party/skia/src/core/SkOpts.cpp', |
| 92 | '../../third_party/skia/src/opts/SkOpts_hsw.cpp', |
| 93 | '../../third_party/skia/third_party/skcms/skcms.cc', |
| 94 | ], |
Yuke Liao | 8098d70 | 2019-08-05 23:57:41 | [diff] [blame] | 95 | 'linux': [ |
| 96 | # These files caused a static initializer to be generated, which |
| 97 | # shouldn't. |
| 98 | # TODO(crbug.com/990948): Remove when the bug is fixed. |
Sajjad Mirza | 750bd0b | 2019-10-16 23:39:51 | [diff] [blame] | 99 | '../../chrome/browser/media/router/providers/cast/cast_internal_message_util.cc', #pylint: disable=line-too-long |
mark a. foltz | 5cb6068 | 2022-10-05 17:43:12 | [diff] [blame] | 100 | '../../components/media_router/common/providers/cast/channel/cast_channel_enum.cc', #pylint: disable=line-too-long |
| 101 | '../../components/media_router/common/providers/cast/channel/cast_message_util.cc', #pylint: disable=line-too-long |
Zhaoyang Li | 7540893 | 2020-10-13 19:06:34 | [diff] [blame] | 102 | '../../components/media_router/common/providers/cast/cast_media_source.cc', #pylint: disable=line-too-long |
Yuke Liao | 40543c9f7 | 2020-01-07 18:27:04 | [diff] [blame] | 103 | '../../ui/events/keycodes/dom/keycode_converter.cc', |
Yuke Liao | 8098d70 | 2019-08-05 23:57:41 | [diff] [blame] | 104 | ], |
Sajjad Mirza | bf9e66a | 2019-03-07 21:49:06 | [diff] [blame] | 105 | 'chromeos': [ |
| 106 | # These files caused clang to crash while compiling them. They are |
| 107 | # excluded pending an investigation into the underlying compiler bug. |
| 108 | '../../third_party/webrtc/p2p/base/p2p_transport_channel.cc', |
| 109 | '../../third_party/icu/source/common/uts46.cpp', |
| 110 | '../../third_party/icu/source/common/ucnvmbcs.cpp', |
| 111 | '../../base/android/android_image_reader_compat.cc', |
Olivier Li | 0504bb0 | 2020-02-14 17:06:22 | [diff] [blame] | 112 | ], |
Sajjad Mirza | bf9e66a | 2019-03-07 21:49:06 | [diff] [blame] | 113 | } |
| 114 | |
Sajjad Mirza | 18f89f2 | 2019-12-20 21:18:12 | [diff] [blame] | 115 | # Map of force lists indexed by target OS. |
| 116 | _COVERAGE_FORCE_LIST_MAP = { |
Sebastien Marchand | bd02bc29e | 2020-03-11 15:53:36 | [diff] [blame] | 117 | # clang_profiling.cc refers to the symbol `__llvm_profile_dump` from the |
Sajjad Mirza | 18f89f2 | 2019-12-20 21:18:12 | [diff] [blame] | 118 | # profiling runtime. In a partial coverage build, it is possible for a |
Sebastien Marchand | bd02bc29e | 2020-03-11 15:53:36 | [diff] [blame] | 119 | # binary to include clang_profiling.cc but have no instrumented files, thus |
Sajjad Mirza | 18f89f2 | 2019-12-20 21:18:12 | [diff] [blame] | 120 | # causing an unresolved symbol error because the profiling runtime will not |
| 121 | # be linked in. Therefore we force coverage for this file to ensure that |
| 122 | # any target that includes it will also get the profiling runtime. |
Sebastien Marchand | bd02bc29e | 2020-03-11 15:53:36 | [diff] [blame] | 123 | 'win': [r'..\..\base\test\clang_profiling.cc'], |
Alison Gale | d965ba0 | 2024-04-26 21:50:54 | [diff] [blame^] | 124 | # TODO(crbug.com/40154378) We're seeing runtime LLVM errors in mac-rel when |
Sajjad Mirza | 72b3140d | 2020-11-25 16:45:03 | [diff] [blame] | 125 | # no files are changed, so we suspect that this is similar to the other |
| 126 | # problem with clang_profiling.cc on Windows. The TODO here is to force |
| 127 | # coverage for this specific file on ALL platforms, if it turns out to fix |
| 128 | # this issue on Mac as well. It's the only file that directly calls |
| 129 | # `__llvm_profile_dump` so it warrants some special treatment. |
| 130 | 'mac': ['../../base/test/clang_profiling.cc'], |
Sajjad Mirza | 18f89f2 | 2019-12-20 21:18:12 | [diff] [blame] | 131 | } |
Sajjad Mirza | bf9e66a | 2019-03-07 21:49:06 | [diff] [blame] | 132 | |
Sajjad Mirza | 750bd0b | 2019-10-16 23:39:51 | [diff] [blame] | 133 | |
Sajjad Mirza | bf9e66a | 2019-03-07 21:49:06 | [diff] [blame] | 134 | def _remove_flags_from_command(command): |
| 135 | # We need to remove the coverage flags for this file, but we only want to |
| 136 | # remove them if we see the exact sequence defined in _COVERAGE_FLAGS. |
| 137 | # That ensures that we only remove the flags added by GN when |
| 138 | # "use_clang_coverage" is true. Otherwise, we would remove flags set by |
| 139 | # other parts of the build system. |
| 140 | start_flag = _COVERAGE_FLAGS[0] |
| 141 | num_flags = len(_COVERAGE_FLAGS) |
| 142 | start_idx = 0 |
| 143 | try: |
| 144 | while True: |
| 145 | idx = command.index(start_flag, start_idx) |
Sajjad Mirza | 18f89f2 | 2019-12-20 21:18:12 | [diff] [blame] | 146 | if command[idx:idx + num_flags] == _COVERAGE_FLAGS: |
| 147 | del command[idx:idx + num_flags] |
Zhaoyang Li | 7540893 | 2020-10-13 19:06:34 | [diff] [blame] | 148 | # There can be multiple sets of _COVERAGE_FLAGS. All of these need to be |
| 149 | # removed. |
| 150 | start_idx = idx |
| 151 | else: |
| 152 | start_idx = idx + 1 |
Sajjad Mirza | bf9e66a | 2019-03-07 21:49:06 | [diff] [blame] | 153 | except ValueError: |
| 154 | pass |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 155 | |
Sajjad Mirza | 18f89f2 | 2019-12-20 21:18:12 | [diff] [blame] | 156 | |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 157 | def main(): |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 158 | arg_parser = argparse.ArgumentParser() |
| 159 | arg_parser.usage = __doc__ |
| 160 | arg_parser.add_argument( |
| 161 | '--files-to-instrument', |
| 162 | type=str, |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 163 | help='Path to a file that contains a list of file names to instrument.') |
Sajjad Mirza | bf9e66a | 2019-03-07 21:49:06 | [diff] [blame] | 164 | arg_parser.add_argument( |
Sajjad Mirza | 18f89f2 | 2019-12-20 21:18:12 | [diff] [blame] | 165 | '--target-os', required=False, help='The OS to compile for.') |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 166 | arg_parser.add_argument('args', nargs=argparse.REMAINDER) |
| 167 | parsed_args = arg_parser.parse_args() |
| 168 | |
Sajjad Mirza | bf9e66a | 2019-03-07 21:49:06 | [diff] [blame] | 169 | if (parsed_args.files_to_instrument and |
| 170 | not os.path.isfile(parsed_args.files_to_instrument)): |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 171 | raise Exception('Path to the coverage instrumentation file: "%s" doesn\'t ' |
| 172 | 'exist.' % parsed_args.files_to_instrument) |
| 173 | |
| 174 | compile_command = parsed_args.args |
Sajjad Mirza | bf9e66a | 2019-03-07 21:49:06 | [diff] [blame] | 175 | if not any('clang' in s for s in compile_command): |
| 176 | return subprocess.call(compile_command) |
| 177 | |
Sajjad Mirza | 750bd0b | 2019-10-16 23:39:51 | [diff] [blame] | 178 | target_os = parsed_args.target_os |
| 179 | |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 180 | try: |
| 181 | # The command is assumed to use Clang as the compiler, and the path to the |
| 182 | # source file is behind the -c argument, and the path to the source path is |
| 183 | # relative to the root build directory. For example: |
| 184 | # clang++ -fvisibility=hidden -c ../../base/files/file_path.cc -o \ |
| 185 | # obj/base/base/file_path.o |
Sajjad Mirza | 750bd0b | 2019-10-16 23:39:51 | [diff] [blame] | 186 | # On Windows, clang-cl.exe uses /c instead of -c. |
| 187 | source_flag = '/c' if target_os == 'win' else '-c' |
| 188 | source_flag_index = compile_command.index(source_flag) |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 189 | except ValueError: |
Sajjad Mirza | 750bd0b | 2019-10-16 23:39:51 | [diff] [blame] | 190 | print('%s argument is not found in the compile command.' % source_flag) |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 191 | raise |
| 192 | |
Sajjad Mirza | 750bd0b | 2019-10-16 23:39:51 | [diff] [blame] | 193 | if source_flag_index + 1 >= len(compile_command): |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 194 | raise Exception('Source file to be compiled is missing from the command.') |
| 195 | |
Sajjad Mirza | e677802c | 2019-12-18 21:51:59 | [diff] [blame] | 196 | # On Windows, filesystem paths should use '\', but GN creates build commands |
| 197 | # that use '/'. We invoke os.path.normpath to ensure that the path uses the |
| 198 | # correct separator for the current platform (i.e. '\' on Windows and '/' |
| 199 | # otherwise). |
| 200 | compile_source_file = os.path.normpath(compile_command[source_flag_index + 1]) |
Bruce Dawson | 09c1bd0 | 2021-06-26 00:06:11 | [diff] [blame] | 201 | extension = os.path.splitext(compile_source_file)[1] |
| 202 | if not extension in ['.c', '.cc', '.cpp', '.cxx', '.m', '.mm', '.S']: |
| 203 | raise Exception('Invalid source file %s found' % compile_source_file) |
Sajjad Mirza | 750bd0b | 2019-10-16 23:39:51 | [diff] [blame] | 204 | exclusion_list = _COVERAGE_EXCLUSION_LIST_MAP.get( |
| 205 | target_os, _DEFAULT_COVERAGE_EXCLUSION_LIST) |
Sajjad Mirza | 18f89f2 | 2019-12-20 21:18:12 | [diff] [blame] | 206 | force_list = _COVERAGE_FORCE_LIST_MAP.get(target_os, []) |
Sajjad Mirza | bf9e66a | 2019-03-07 21:49:06 | [diff] [blame] | 207 | |
Sajjad Mirza | 18f89f2 | 2019-12-20 21:18:12 | [diff] [blame] | 208 | should_remove_flags = False |
| 209 | if compile_source_file not in force_list: |
| 210 | if compile_source_file in exclusion_list: |
| 211 | should_remove_flags = True |
| 212 | elif parsed_args.files_to_instrument: |
| 213 | with open(parsed_args.files_to_instrument) as f: |
| 214 | if compile_source_file not in f.read(): |
| 215 | should_remove_flags = True |
| 216 | |
| 217 | if should_remove_flags: |
Sajjad Mirza | bf9e66a | 2019-03-07 21:49:06 | [diff] [blame] | 218 | _remove_flags_from_command(compile_command) |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 219 | |
| 220 | return subprocess.call(compile_command) |
| 221 | |
Sajjad Mirza | 18f89f2 | 2019-12-20 21:18:12 | [diff] [blame] | 222 | |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 223 | if __name__ == '__main__': |
| 224 | sys.exit(main()) |
Richard Wang | 91cc90f | 2023-07-25 16:49:12 | [diff] [blame] | 225 | |
| 226 | # LINT.ThenChange(/build/config/siso/clang_code_coverage_wrapper.star) |