Tom Anderson | 352981c25 | 2018-12-01 00:36:45 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # Copyright 2018 The Chromium Authors. All rights reserved. |
| 3 | # Use of this source code is governed by a BSD-style license that can be |
| 4 | # found in the LICENSE file. |
| 5 | |
| 6 | import json |
| 7 | import os |
| 8 | import re |
| 9 | import subprocess |
| 10 | import sys |
| 11 | |
| 12 | import common |
| 13 | |
Lei Zhang | bdbca914 | 2020-09-21 06:22:12 | [diff] [blame] | 14 | # A list of files that are allowed to have static initializers. |
Yuke Liao | a1ad0ec | 2019-06-12 20:10:11 | [diff] [blame] | 15 | # If something adds a static initializer, revert it. We don't accept regressions |
| 16 | # in static initializers. |
Lei Zhang | bdbca914 | 2020-09-21 06:22:12 | [diff] [blame] | 17 | _LINUX_SI_FILE_ALLOWLIST = { |
Yuke Liao | a1ad0ec | 2019-06-12 20:10:11 | [diff] [blame] | 18 | 'chrome': [ |
Hans Wennborg | 18026ae | 2019-08-22 19:07:46 | [diff] [blame] | 19 | 'InstrProfilingRuntime.cpp', # Only in coverage builds, not production. |
Yuke Liao | a1ad0ec | 2019-06-12 20:10:11 | [diff] [blame] | 20 | 'atomicops_internals_x86.cc', # TODO(crbug.com/973551): Remove. |
| 21 | 'debugallocation_shim.cc', # TODO(crbug.com/973552): Remove. |
| 22 | 'iostream.cpp', # TODO(crbug.com/973554): Remove. |
| 23 | 'spinlock.cc', # TODO(crbug.com/973556): Remove. |
| 24 | ], |
| 25 | 'nacl_helper_bootstrap': [], |
Tom Anderson | 352981c25 | 2018-12-01 00:36:45 | [diff] [blame] | 26 | } |
Lei Zhang | bdbca914 | 2020-09-21 06:22:12 | [diff] [blame] | 27 | _LINUX_SI_FILE_ALLOWLIST['nacl_helper'] = _LINUX_SI_FILE_ALLOWLIST['chrome'] |
Tom Anderson | 352981c25 | 2018-12-01 00:36:45 | [diff] [blame] | 28 | |
Lei Zhang | 81f5dd1 | 2020-09-24 12:59:28 | [diff] [blame] | 29 | # The lists for Chrome OS are conceptually the same as the Linux ones above. |
| 30 | # If something adds a static initializer, revert it. We don't accept regressions |
| 31 | # in static initializers. |
| 32 | _CROS_SI_FILE_ALLOWLIST = { |
| 33 | 'chrome': [ |
| 34 | 'InstrProfilingRuntime.cpp', # Only in coverage builds, not production. |
| 35 | 'atomicops_internals_x86.cc', # TODO(crbug.com/973551): Remove. |
| 36 | 'debugallocation_shim.cc', # TODO(crbug.com/973552): Remove. |
| 37 | 'iostream.cpp', # TODO(crbug.com/973554): Remove. |
| 38 | 'spinlock.cc', # TODO(crbug.com/973556): Remove. |
Lei Zhang | 81f5dd1 | 2020-09-24 12:59:28 | [diff] [blame] | 39 | 'int256.cc', # TODO(crbug.com/537099): Remove. |
Lei Zhang | 81f5dd1 | 2020-09-24 12:59:28 | [diff] [blame] | 40 | 'rpc.pb.cc', # TODO(crbug.com/537099): Remove. |
Lei Zhang | 81f5dd1 | 2020-09-24 12:59:28 | [diff] [blame] | 41 | ], |
| 42 | 'nacl_helper_bootstrap': [], |
| 43 | } |
| 44 | _CROS_SI_FILE_ALLOWLIST['nacl_helper'] = _LINUX_SI_FILE_ALLOWLIST['chrome'] |
| 45 | |
Lei Zhang | bdbca914 | 2020-09-21 06:22:12 | [diff] [blame] | 46 | # Mac can use this list when a dsym is available, otherwise it will fall back |
Sajjad Mirza | 26561f3 | 2020-03-18 17:55:20 | [diff] [blame] | 47 | # to checking the count. |
Lei Zhang | bdbca914 | 2020-09-21 06:22:12 | [diff] [blame] | 48 | _MAC_SI_FILE_ALLOWLIST = [ |
Sajjad Mirza | 26561f3 | 2020-03-18 17:55:20 | [diff] [blame] | 49 | 'InstrProfilingRuntime.cpp', # Only in coverage builds, not in production. |
| 50 | 'sysinfo.cc', # Only in coverage builds, not in production. |
| 51 | 'iostream.cpp', # Used to setup std::cin/cout/cerr. |
| 52 | ] |
| 53 | |
Tom Anderson | d9d71d7 | 2019-01-18 17:58:26 | [diff] [blame] | 54 | # A static initializer is needed on Mac for libc++ to set up std::cin/cout/cerr |
Sajjad Mirza | 26561f3 | 2020-03-18 17:55:20 | [diff] [blame] | 55 | # before main() runs. Coverage CQ will have a dsym so only iostream.cpp needs |
| 56 | # to be counted here. |
| 57 | FALLBACK_EXPECTED_MAC_SI_COUNT = 1 |
Tom Anderson | 352981c25 | 2018-12-01 00:36:45 | [diff] [blame] | 58 | |
| 59 | |
| 60 | def run_process(command): |
| 61 | p = subprocess.Popen(command, stdout=subprocess.PIPE) |
| 62 | stdout = p.communicate()[0] |
| 63 | if p.returncode != 0: |
| 64 | raise Exception( |
| 65 | 'ERROR from command "%s": %d' % (' '.join(command), p.returncode)) |
| 66 | return stdout |
| 67 | |
| 68 | |
| 69 | def main_mac(src_dir): |
| 70 | base_names = ('Chromium', 'Google Chrome') |
| 71 | ret = 0 |
| 72 | for base_name in base_names: |
| 73 | app_bundle = base_name + '.app' |
| 74 | framework_name = base_name + ' Framework' |
| 75 | framework_bundle = framework_name + '.framework' |
| 76 | framework_dsym_bundle = framework_bundle + '.dSYM' |
| 77 | framework_unstripped_name = framework_name + '.unstripped' |
| 78 | chromium_executable = os.path.join(app_bundle, 'Contents', 'MacOS', |
| 79 | base_name) |
| 80 | chromium_framework_executable = os.path.join(framework_bundle, |
| 81 | framework_name) |
| 82 | chromium_framework_dsym = os.path.join(framework_dsym_bundle, 'Contents', |
| 83 | 'Resources', 'DWARF', framework_name) |
| 84 | if os.path.exists(chromium_executable): |
| 85 | # Count the number of files with at least one static initializer. |
| 86 | si_count = 0 |
| 87 | # Find the __DATA,__mod_init_func section. |
Stephanie Kim | 10dd25ba | 2020-08-18 20:26:57 | [diff] [blame] | 88 | |
| 89 | # If the checkout uses the hermetic xcode binaries, then otool must be |
| 90 | # directly invoked. The indirection via /usr/bin/otool won't work unless |
| 91 | # there's an actual system install of Xcode. |
| 92 | hermetic_xcode_path = os.path.join(src_dir, 'build', 'mac_files', |
| 93 | 'xcode_binaries') |
| 94 | if os.path.exists(hermetic_xcode_path): |
| 95 | otool_path = os.path.join(hermetic_xcode_path, 'Contents', 'Developer', |
| 96 | 'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin', 'otool') |
| 97 | else: |
| 98 | otool_path = 'otool' |
| 99 | |
| 100 | stdout = run_process([otool_path, '-l', chromium_framework_executable]) |
Tom Anderson | 352981c25 | 2018-12-01 00:36:45 | [diff] [blame] | 101 | section_index = stdout.find('sectname __mod_init_func') |
| 102 | if section_index != -1: |
| 103 | # If the section exists, the "size" line must follow it. |
| 104 | initializers_s = re.search('size 0x([0-9a-f]+)', |
| 105 | stdout[section_index:]).group(1) |
| 106 | word_size = 8 # Assume 64 bit |
| 107 | si_count = int(initializers_s, 16) / word_size |
| 108 | |
| 109 | # Print the list of static initializers. |
Sajjad Mirza | 26561f3 | 2020-03-18 17:55:20 | [diff] [blame] | 110 | if si_count > 0: |
Tom Anderson | 352981c25 | 2018-12-01 00:36:45 | [diff] [blame] | 111 | # First look for a dSYM to get information about the initializers. If |
| 112 | # one is not present, check if there is an unstripped copy of the build |
| 113 | # output. |
| 114 | mac_tools_path = os.path.join(src_dir, 'tools', 'mac') |
| 115 | if os.path.exists(chromium_framework_dsym): |
| 116 | dump_static_initializers = os.path.join( |
| 117 | mac_tools_path, 'dump-static-initializers.py') |
| 118 | stdout = run_process( |
| 119 | [dump_static_initializers, chromium_framework_dsym]) |
Sajjad Mirza | 26561f3 | 2020-03-18 17:55:20 | [diff] [blame] | 120 | for line in stdout: |
| 121 | if re.match('0x[0-9a-f]+', line) and not any( |
Lei Zhang | bdbca914 | 2020-09-21 06:22:12 | [diff] [blame] | 122 | f in line for f in _MAC_SI_FILE_ALLOWLIST): |
Sajjad Mirza | 26561f3 | 2020-03-18 17:55:20 | [diff] [blame] | 123 | ret = 1 |
| 124 | print 'Found invalid static initializer: {}'.format(line) |
Tom Anderson | 352981c25 | 2018-12-01 00:36:45 | [diff] [blame] | 125 | print stdout |
Sajjad Mirza | 26561f3 | 2020-03-18 17:55:20 | [diff] [blame] | 126 | elif si_count > FALLBACK_EXPECTED_MAC_SI_COUNT: |
| 127 | print('Expected <= %d static initializers in %s, but found %d' % |
| 128 | (FALLBACK_EXPECTED_MAC_SI_COUNT, chromium_framework_executable, |
| 129 | si_count)) |
Erik Staab | 9b93c64 | 2020-09-16 20:17:56 | [diff] [blame] | 130 | ret = 1 |
Tom Anderson | 352981c25 | 2018-12-01 00:36:45 | [diff] [blame] | 131 | show_mod_init_func = os.path.join(mac_tools_path, |
| 132 | 'show_mod_init_func.py') |
| 133 | args = [show_mod_init_func] |
| 134 | if os.path.exists(framework_unstripped_name): |
| 135 | args.append(framework_unstripped_name) |
| 136 | else: |
| 137 | print '# Warning: Falling back to potentially stripped output.' |
| 138 | args.append(chromium_framework_executable) |
Stephanie Kim | 10dd25ba | 2020-08-18 20:26:57 | [diff] [blame] | 139 | |
| 140 | if os.path.exists(hermetic_xcode_path): |
| 141 | args.extend(['--xcode-path', hermetic_xcode_path]) |
| 142 | |
Tom Anderson | 352981c25 | 2018-12-01 00:36:45 | [diff] [blame] | 143 | stdout = run_process(args) |
| 144 | print stdout |
| 145 | return ret |
| 146 | |
| 147 | |
Lei Zhang | 81f5dd1 | 2020-09-24 12:59:28 | [diff] [blame] | 148 | def main_linux(src_dir, is_chromeos): |
Tom Anderson | 352981c25 | 2018-12-01 00:36:45 | [diff] [blame] | 149 | ret = 0 |
Lei Zhang | 81f5dd1 | 2020-09-24 12:59:28 | [diff] [blame] | 150 | allowlist = _CROS_SI_FILE_ALLOWLIST if is_chromeos else \ |
| 151 | _LINUX_SI_FILE_ALLOWLIST |
| 152 | for binary_name in allowlist: |
Tom Anderson | 352981c25 | 2018-12-01 00:36:45 | [diff] [blame] | 153 | if not os.path.exists(binary_name): |
| 154 | continue |
Tom Anderson | 352981c25 | 2018-12-01 00:36:45 | [diff] [blame] | 155 | |
Yuke Liao | a1ad0ec | 2019-06-12 20:10:11 | [diff] [blame] | 156 | dump_static_initializers = os.path.join(src_dir, 'tools', 'linux', |
| 157 | 'dump-static-initializers.py') |
| 158 | stdout = run_process([dump_static_initializers, '-d', binary_name]) |
| 159 | # The output has the following format: |
| 160 | # First lines: '# <file_name> <si_name>' |
| 161 | # Last line: '# Found <num> static initializers in <num> files.' |
| 162 | # |
| 163 | # For example: |
| 164 | # # spinlock.cc GetSystemCPUsCount() |
| 165 | # # spinlock.cc adaptive_spin_count |
| 166 | # # Found 2 static initializers in 1 files. |
| 167 | |
| 168 | files_with_si = set() |
| 169 | for line in stdout.splitlines()[:-1]: |
| 170 | parts = line.split(' ', 2) |
| 171 | assert len(parts) == 3 and parts[0] == '#' |
| 172 | |
| 173 | files_with_si.add(parts[1]) |
| 174 | |
| 175 | for f in files_with_si: |
Lei Zhang | 81f5dd1 | 2020-09-24 12:59:28 | [diff] [blame] | 176 | if f not in allowlist[binary_name]: |
Yuke Liao | a1ad0ec | 2019-06-12 20:10:11 | [diff] [blame] | 177 | ret = 1 |
| 178 | print('Error: file "%s" is not expected to have static initializers in' |
| 179 | ' binary "%s"') % (f, binary_name) |
| 180 | |
| 181 | print '\n# Static initializers in %s:' % binary_name |
| 182 | print stdout |
| 183 | |
Tom Anderson | 352981c25 | 2018-12-01 00:36:45 | [diff] [blame] | 184 | return ret |
| 185 | |
| 186 | |
| 187 | def main_run(args): |
| 188 | if args.build_config_fs != 'Release': |
| 189 | raise Exception('Only release builds are supported') |
| 190 | |
| 191 | src_dir = args.paths['checkout'] |
| 192 | build_dir = os.path.join(src_dir, 'out', args.build_config_fs) |
| 193 | os.chdir(build_dir) |
| 194 | |
| 195 | if sys.platform.startswith('darwin'): |
| 196 | rc = main_mac(src_dir) |
| 197 | elif sys.platform == 'linux2': |
Lei Zhang | 81f5dd1 | 2020-09-24 12:59:28 | [diff] [blame] | 198 | is_chromeos = 'buildername' in args.properties and \ |
| 199 | 'chromeos' in args.properties['buildername'] |
| 200 | rc = main_linux(src_dir, is_chromeos) |
Tom Anderson | 352981c25 | 2018-12-01 00:36:45 | [diff] [blame] | 201 | else: |
| 202 | sys.stderr.write('Unsupported platform %s.\n' % repr(sys.platform)) |
| 203 | return 2 |
| 204 | |
| 205 | json.dump({ |
| 206 | 'valid': rc == 0, |
| 207 | 'failures': [], |
| 208 | }, args.output) |
| 209 | |
| 210 | return rc |
| 211 | |
| 212 | |
| 213 | def main_compile_targets(args): |
| 214 | if sys.platform.startswith('darwin'): |
| 215 | compile_targets = ['chrome'] |
| 216 | elif sys.platform == 'linux2': |
| 217 | compile_targets = ['chrome', 'nacl_helper', 'nacl_helper_bootstrap'] |
| 218 | else: |
| 219 | compile_targets = [] |
| 220 | |
| 221 | json.dump(compile_targets, args.output) |
| 222 | |
| 223 | return 0 |
| 224 | |
| 225 | |
| 226 | if __name__ == '__main__': |
| 227 | funcs = { |
| 228 | 'run': main_run, |
| 229 | 'compile_targets': main_compile_targets, |
| 230 | } |
| 231 | sys.exit(common.run_script(sys.argv[1:], funcs)) |