Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 1 | #!/usr/bin/env python |
Rebekah Potter | 184a8200 | 2022-09-08 01:08:54 | [diff] [blame] | 2 | # Copyright 2018 The Chromium Authors. All rights reserved. |
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 |
| 47 | """ |
| 48 | |
Raul Tambre | 9e24293b | 2019-05-12 06:11:07 | [diff] [blame] | 49 | from __future__ import print_function |
| 50 | |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 51 | import argparse |
| 52 | import os |
| 53 | import subprocess |
| 54 | import sys |
| 55 | |
| 56 | # Flags used to enable coverage instrumentation. |
Sajjad Mirza | bf9e66a | 2019-03-07 21:49:06 | [diff] [blame] | 57 | # Flags should be listed in the same order that they are added in |
| 58 | # build/config/coverage/BUILD.gn |
Yuke Liao | 5eff782 | 2019-02-28 03:56:24 | [diff] [blame] | 59 | _COVERAGE_FLAGS = [ |
Sajjad Mirza | 18f89f2 | 2019-12-20 21:18:12 | [diff] [blame] | 60 | '-fprofile-instr-generate', |
| 61 | '-fcoverage-mapping', |
Sajjad Mirza | 49c00e3 | 2019-03-01 22:46:52 | [diff] [blame] | 62 | # Following experimental flags remove unused header functions from the |
| 63 | # coverage mapping data embedded in the test binaries, and the reduction |
| 64 | # of binary size enables building Chrome's large unit test targets on |
| 65 | # MacOS. Please refer to crbug.com/796290 for more details. |
Sajjad Mirza | 18f89f2 | 2019-12-20 21:18:12 | [diff] [blame] | 66 | '-mllvm', |
| 67 | '-limited-coverage-experimental=true', |
Yuke Liao | 5eff782 | 2019-02-28 03:56:24 | [diff] [blame] | 68 | ] |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 69 | |
Sajjad Mirza | 750bd0b | 2019-10-16 23:39:51 | [diff] [blame] | 70 | # Files that should not be built with coverage flags by default. |
Olivier Li | 13a2ca0 | 2020-02-14 01:06:42 | [diff] [blame] | 71 | _DEFAULT_COVERAGE_EXCLUSION_LIST = [ |
| 72 | # TODO(crbug.com/1051561): angle_unittests affected by coverage. |
| 73 | '../../base/message_loop/message_pump_default.cc', |
| 74 | '../../base/message_loop/message_pump_libevent.cc', |
| 75 | '../../base/message_loop/message_pump_win.cc', |
| 76 | '../../base/task/sequence_manager/thread_controller_with_message_pump_impl.cc', #pylint: disable=line-too-long |
| 77 | ] |
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': [ |
| 88 | # TODO(crbug.com/1174725): These files caused clang to crash while |
| 89 | # compiling them. |
| 90 | '../../base/allocator/partition_allocator/pcscan.cc', |
| 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 |
Yuke Liao | 8098d70 | 2019-08-05 23:57:41 | [diff] [blame] | 100 | '../../components/cast_channel/cast_channel_enum.cc', |
Sajjad Mirza | 750bd0b | 2019-10-16 23:39:51 | [diff] [blame] | 101 | '../../components/cast_channel/cast_message_util.cc', |
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', |
Sajjad Mirza | 836033f | 2020-02-14 02:37:39 | [diff] [blame] | 104 | # TODO(crbug.com/1051561): angle_unittests affected by coverage. |
| 105 | '../../base/message_loop/message_pump_default.cc', |
| 106 | '../../base/message_loop/message_pump_libevent.cc', |
| 107 | '../../base/message_loop/message_pump_win.cc', |
| 108 | '../../base/task/sequence_manager/thread_controller_with_message_pump_impl.cc', #pylint: disable=line-too-long |
Yuke Liao | 8098d70 | 2019-08-05 23:57:41 | [diff] [blame] | 109 | ], |
Sajjad Mirza | bf9e66a | 2019-03-07 21:49:06 | [diff] [blame] | 110 | 'chromeos': [ |
| 111 | # These files caused clang to crash while compiling them. They are |
| 112 | # excluded pending an investigation into the underlying compiler bug. |
| 113 | '../../third_party/webrtc/p2p/base/p2p_transport_channel.cc', |
| 114 | '../../third_party/icu/source/common/uts46.cpp', |
| 115 | '../../third_party/icu/source/common/ucnvmbcs.cpp', |
| 116 | '../../base/android/android_image_reader_compat.cc', |
Sajjad Mirza | 836033f | 2020-02-14 02:37:39 | [diff] [blame] | 117 | # TODO(crbug.com/1051561): angle_unittests affected by coverage. |
| 118 | '../../base/message_loop/message_pump_default.cc', |
| 119 | '../../base/message_loop/message_pump_libevent.cc', |
| 120 | '../../base/message_loop/message_pump_win.cc', |
| 121 | '../../base/task/sequence_manager/thread_controller_with_message_pump_impl.cc', #pylint: disable=line-too-long |
Sajjad Mirza | 750bd0b | 2019-10-16 23:39:51 | [diff] [blame] | 122 | ], |
Olivier Li | 0504bb0 | 2020-02-14 17:06:22 | [diff] [blame] | 123 | 'win': [ |
| 124 | # TODO(crbug.com/1051561): angle_unittests affected by coverage. |
| 125 | '../../base/message_loop/message_pump_default.cc', |
| 126 | '../../base/message_loop/message_pump_libevent.cc', |
| 127 | '../../base/message_loop/message_pump_win.cc', |
| 128 | '../../base/task/sequence_manager/thread_controller_with_message_pump_impl.cc', #pylint: disable=line-too-long |
| 129 | ], |
Sajjad Mirza | bf9e66a | 2019-03-07 21:49:06 | [diff] [blame] | 130 | } |
| 131 | |
Sajjad Mirza | 18f89f2 | 2019-12-20 21:18:12 | [diff] [blame] | 132 | # Map of force lists indexed by target OS. |
| 133 | _COVERAGE_FORCE_LIST_MAP = { |
Sebastien Marchand | bd02bc29e | 2020-03-11 15:53:36 | [diff] [blame] | 134 | # clang_profiling.cc refers to the symbol `__llvm_profile_dump` from the |
Sajjad Mirza | 18f89f2 | 2019-12-20 21:18:12 | [diff] [blame] | 135 | # profiling runtime. In a partial coverage build, it is possible for a |
Sebastien Marchand | bd02bc29e | 2020-03-11 15:53:36 | [diff] [blame] | 136 | # binary to include clang_profiling.cc but have no instrumented files, thus |
Sajjad Mirza | 18f89f2 | 2019-12-20 21:18:12 | [diff] [blame] | 137 | # causing an unresolved symbol error because the profiling runtime will not |
| 138 | # be linked in. Therefore we force coverage for this file to ensure that |
| 139 | # any target that includes it will also get the profiling runtime. |
Sebastien Marchand | bd02bc29e | 2020-03-11 15:53:36 | [diff] [blame] | 140 | 'win': [r'..\..\base\test\clang_profiling.cc'], |
Sajjad Mirza | 72b3140d | 2020-11-25 16:45:03 | [diff] [blame] | 141 | # TODO(crbug.com/1141727) We're seeing runtime LLVM errors in mac-rel when |
| 142 | # no files are changed, so we suspect that this is similar to the other |
| 143 | # problem with clang_profiling.cc on Windows. The TODO here is to force |
| 144 | # coverage for this specific file on ALL platforms, if it turns out to fix |
| 145 | # this issue on Mac as well. It's the only file that directly calls |
| 146 | # `__llvm_profile_dump` so it warrants some special treatment. |
| 147 | 'mac': ['../../base/test/clang_profiling.cc'], |
Sajjad Mirza | 18f89f2 | 2019-12-20 21:18:12 | [diff] [blame] | 148 | } |
Sajjad Mirza | bf9e66a | 2019-03-07 21:49:06 | [diff] [blame] | 149 | |
Sajjad Mirza | 750bd0b | 2019-10-16 23:39:51 | [diff] [blame] | 150 | |
Sajjad Mirza | bf9e66a | 2019-03-07 21:49:06 | [diff] [blame] | 151 | def _remove_flags_from_command(command): |
| 152 | # We need to remove the coverage flags for this file, but we only want to |
| 153 | # remove them if we see the exact sequence defined in _COVERAGE_FLAGS. |
| 154 | # That ensures that we only remove the flags added by GN when |
| 155 | # "use_clang_coverage" is true. Otherwise, we would remove flags set by |
| 156 | # other parts of the build system. |
| 157 | start_flag = _COVERAGE_FLAGS[0] |
| 158 | num_flags = len(_COVERAGE_FLAGS) |
| 159 | start_idx = 0 |
| 160 | try: |
| 161 | while True: |
| 162 | idx = command.index(start_flag, start_idx) |
Sajjad Mirza | 18f89f2 | 2019-12-20 21:18:12 | [diff] [blame] | 163 | if command[idx:idx + num_flags] == _COVERAGE_FLAGS: |
| 164 | del command[idx:idx + num_flags] |
Zhaoyang Li | 7540893 | 2020-10-13 19:06:34 | [diff] [blame] | 165 | # There can be multiple sets of _COVERAGE_FLAGS. All of these need to be |
| 166 | # removed. |
| 167 | start_idx = idx |
| 168 | else: |
| 169 | start_idx = idx + 1 |
Sajjad Mirza | bf9e66a | 2019-03-07 21:49:06 | [diff] [blame] | 170 | except ValueError: |
| 171 | pass |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 172 | |
Sajjad Mirza | 18f89f2 | 2019-12-20 21:18:12 | [diff] [blame] | 173 | |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 174 | def main(): |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 175 | arg_parser = argparse.ArgumentParser() |
| 176 | arg_parser.usage = __doc__ |
| 177 | arg_parser.add_argument( |
| 178 | '--files-to-instrument', |
| 179 | type=str, |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 180 | 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] | 181 | arg_parser.add_argument( |
Sajjad Mirza | 18f89f2 | 2019-12-20 21:18:12 | [diff] [blame] | 182 | '--target-os', required=False, help='The OS to compile for.') |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 183 | arg_parser.add_argument('args', nargs=argparse.REMAINDER) |
| 184 | parsed_args = arg_parser.parse_args() |
| 185 | |
Sajjad Mirza | bf9e66a | 2019-03-07 21:49:06 | [diff] [blame] | 186 | if (parsed_args.files_to_instrument and |
| 187 | not os.path.isfile(parsed_args.files_to_instrument)): |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 188 | raise Exception('Path to the coverage instrumentation file: "%s" doesn\'t ' |
| 189 | 'exist.' % parsed_args.files_to_instrument) |
| 190 | |
| 191 | compile_command = parsed_args.args |
Sajjad Mirza | bf9e66a | 2019-03-07 21:49:06 | [diff] [blame] | 192 | if not any('clang' in s for s in compile_command): |
| 193 | return subprocess.call(compile_command) |
| 194 | |
Sajjad Mirza | 750bd0b | 2019-10-16 23:39:51 | [diff] [blame] | 195 | target_os = parsed_args.target_os |
| 196 | |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 197 | try: |
| 198 | # The command is assumed to use Clang as the compiler, and the path to the |
| 199 | # source file is behind the -c argument, and the path to the source path is |
| 200 | # relative to the root build directory. For example: |
| 201 | # clang++ -fvisibility=hidden -c ../../base/files/file_path.cc -o \ |
| 202 | # obj/base/base/file_path.o |
Sajjad Mirza | 750bd0b | 2019-10-16 23:39:51 | [diff] [blame] | 203 | # On Windows, clang-cl.exe uses /c instead of -c. |
| 204 | source_flag = '/c' if target_os == 'win' else '-c' |
| 205 | source_flag_index = compile_command.index(source_flag) |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 206 | except ValueError: |
Sajjad Mirza | 750bd0b | 2019-10-16 23:39:51 | [diff] [blame] | 207 | print('%s argument is not found in the compile command.' % source_flag) |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 208 | raise |
| 209 | |
Sajjad Mirza | 750bd0b | 2019-10-16 23:39:51 | [diff] [blame] | 210 | if source_flag_index + 1 >= len(compile_command): |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 211 | raise Exception('Source file to be compiled is missing from the command.') |
| 212 | |
Sajjad Mirza | e677802c | 2019-12-18 21:51:59 | [diff] [blame] | 213 | # On Windows, filesystem paths should use '\', but GN creates build commands |
| 214 | # that use '/'. We invoke os.path.normpath to ensure that the path uses the |
| 215 | # correct separator for the current platform (i.e. '\' on Windows and '/' |
| 216 | # otherwise). |
| 217 | compile_source_file = os.path.normpath(compile_command[source_flag_index + 1]) |
Bruce Dawson | 09c1bd0 | 2021-06-26 00:06:11 | [diff] [blame] | 218 | extension = os.path.splitext(compile_source_file)[1] |
| 219 | if not extension in ['.c', '.cc', '.cpp', '.cxx', '.m', '.mm', '.S']: |
| 220 | raise Exception('Invalid source file %s found' % compile_source_file) |
Sajjad Mirza | 750bd0b | 2019-10-16 23:39:51 | [diff] [blame] | 221 | exclusion_list = _COVERAGE_EXCLUSION_LIST_MAP.get( |
| 222 | target_os, _DEFAULT_COVERAGE_EXCLUSION_LIST) |
Sajjad Mirza | 18f89f2 | 2019-12-20 21:18:12 | [diff] [blame] | 223 | force_list = _COVERAGE_FORCE_LIST_MAP.get(target_os, []) |
Sajjad Mirza | bf9e66a | 2019-03-07 21:49:06 | [diff] [blame] | 224 | |
Sajjad Mirza | 18f89f2 | 2019-12-20 21:18:12 | [diff] [blame] | 225 | should_remove_flags = False |
| 226 | if compile_source_file not in force_list: |
| 227 | if compile_source_file in exclusion_list: |
| 228 | should_remove_flags = True |
| 229 | elif parsed_args.files_to_instrument: |
| 230 | with open(parsed_args.files_to_instrument) as f: |
| 231 | if compile_source_file not in f.read(): |
| 232 | should_remove_flags = True |
| 233 | |
| 234 | if should_remove_flags: |
Sajjad Mirza | bf9e66a | 2019-03-07 21:49:06 | [diff] [blame] | 235 | _remove_flags_from_command(compile_command) |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 236 | |
| 237 | return subprocess.call(compile_command) |
| 238 | |
Sajjad Mirza | 18f89f2 | 2019-12-20 21:18:12 | [diff] [blame] | 239 | |
Yuke Liao | bb571bd6 | 2018-10-31 21:51:52 | [diff] [blame] | 240 | if __name__ == '__main__': |
| 241 | sys.exit(main()) |