OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python |
| 2 # Copyright 2016 the V8 project 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 """ |
| 7 V8 correctness fuzzer launcher script. |
| 8 """ |
| 9 |
| 10 import argparse |
| 11 import itertools |
| 12 import os |
| 13 import re |
| 14 import sys |
| 15 import traceback |
| 16 |
| 17 import v8_commands |
| 18 import v8_suppressions |
| 19 |
| 20 CONFIGS = dict( |
| 21 default=[], |
| 22 validate_asm=['--validate-asm'], # Maybe add , '--disable-asm-warnings' |
| 23 fullcode=['--nocrankshaft', '--turbo-filter=~'], |
| 24 noturbo=['--turbo-filter=~', '--noturbo-asm'], |
| 25 noturbo_opt=['--always-opt', '--turbo-filter=~', '--noturbo-asm'], |
| 26 ignition_staging=['--ignition-staging'], |
| 27 ignition_turbo=['--ignition-staging', '--turbo'], |
| 28 ignition_turbo_opt=['--ignition-staging', '--turbo', '--always-opt'], |
| 29 ) |
| 30 |
| 31 # Timeout in seconds for one d8 run. |
| 32 TIMEOUT = 3 |
| 33 |
| 34 # Return codes. |
| 35 RETURN_PASS = 0 |
| 36 RETURN_FAILURE = 2 |
| 37 |
| 38 BASE_PATH = os.path.dirname(os.path.abspath(__file__)) |
| 39 PREAMBLE = [ |
| 40 os.path.join(BASE_PATH, 'v8_mock.js'), |
| 41 os.path.join(BASE_PATH, 'v8_suppressions.js'), |
| 42 ] |
| 43 |
| 44 FLAGS = ['--abort_on_stack_overflow', '--expose-gc', '--allow-natives-syntax', |
| 45 '--invoke-weak-callbacks', '--omit-quit', '--es-staging'] |
| 46 |
| 47 SUPPORTED_ARCHS = ['ia32', 'x64', 'arm', 'arm64'] |
| 48 |
| 49 # Output for suppressed failure case. |
| 50 FAILURE_HEADER_TEMPLATE = """ |
| 51 # |
| 52 # V8 correctness failure |
| 53 # V8 correctness configs: %(configs)s |
| 54 # V8 correctness sources: %(sources)s |
| 55 # V8 correctness suppression: %(suppression)s""".strip() |
| 56 |
| 57 # Extended output for failure case. The 'CHECK' is for the minimizer. |
| 58 FAILURE_TEMPLATE = FAILURE_HEADER_TEMPLATE + """ |
| 59 # |
| 60 # CHECK |
| 61 # |
| 62 # Compared %(first_config_label)s with %(second_config_label)s |
| 63 # |
| 64 # Flags of %(first_config_label)s: |
| 65 %(first_config_flags)s |
| 66 # Flags of %(second_config_label)s: |
| 67 %(second_config_flags)s |
| 68 # |
| 69 # Difference: |
| 70 %(difference)s |
| 71 # |
| 72 ### Start of configuration %(first_config_label)s: |
| 73 %(first_config_output)s |
| 74 ### End of configuration %(first_config_label)s |
| 75 # |
| 76 ### Start of configuration %(second_config_label)s: |
| 77 %(second_config_output)s |
| 78 ### End of configuration %(second_config_label)s |
| 79 """.strip() |
| 80 |
| 81 |
| 82 def parse_args(): |
| 83 parser = argparse.ArgumentParser() |
| 84 parser.add_argument( |
| 85 '--random-seed', type=int, required=True, |
| 86 help='random seed passed to both runs') |
| 87 parser.add_argument( |
| 88 '--first-arch', help='first architecture', default='x64') |
| 89 parser.add_argument( |
| 90 '--second-arch', help='second architecture', default='x64') |
| 91 parser.add_argument( |
| 92 '--first-config', help='first configuration', default='fullcode') |
| 93 parser.add_argument( |
| 94 '--second-config', help='second configuration', default='fullcode') |
| 95 parser.add_argument( |
| 96 '--first-d8', default='d8', |
| 97 help='optional path to first d8 executable, ' |
| 98 'default: bundled in the same directory as this script') |
| 99 parser.add_argument( |
| 100 '--second-d8', |
| 101 help='optional path to second d8 executable, default: same as first') |
| 102 parser.add_argument('testcase', help='path to test case') |
| 103 options = parser.parse_args() |
| 104 |
| 105 # Ensure we make a sane comparison. |
| 106 assert (options.first_arch != options.second_arch or |
| 107 options.first_config != options.second_config) , ( |
| 108 'Need either arch or config difference.') |
| 109 assert options.first_arch in SUPPORTED_ARCHS |
| 110 assert options.second_arch in SUPPORTED_ARCHS |
| 111 assert options.first_config in CONFIGS |
| 112 assert options.second_config in CONFIGS |
| 113 |
| 114 # Ensure we have a test case. |
| 115 assert (os.path.exists(options.testcase) and |
| 116 os.path.isfile(options.testcase)), ( |
| 117 'Test case %s doesn\'t exist' % options.testcase) |
| 118 |
| 119 # Use first d8 as default for second d8. |
| 120 options.second_d8 = options.second_d8 or options.first_d8 |
| 121 |
| 122 # Ensure absolute paths. |
| 123 options.first_d8 = os.path.abspath(options.first_d8) |
| 124 options.second_d8 = os.path.abspath(options.second_d8) |
| 125 |
| 126 # Ensure executables exist. |
| 127 assert os.path.exists(options.first_d8) |
| 128 assert os.path.exists(options.second_d8) |
| 129 |
| 130 # Ensure we use different executables when we claim we compare |
| 131 # different architectures. |
| 132 # TODO(machenbach): Infer arch from gn's build output. |
| 133 if options.first_arch != options.second_arch: |
| 134 assert options.first_d8 != options.second_d8 |
| 135 |
| 136 return options |
| 137 |
| 138 |
| 139 def test_pattern_bailout(testcase, ignore_fun): |
| 140 """Print failure state and return if ignore_fun matches testcase.""" |
| 141 with open(testcase) as f: |
| 142 bug = (ignore_fun(f.read()) or '').strip() |
| 143 if bug: |
| 144 print FAILURE_HEADER_TEMPLATE % dict( |
| 145 configs='', sources='', suppression=bug) |
| 146 return True |
| 147 return False |
| 148 |
| 149 |
| 150 def pass_bailout(output, step_number): |
| 151 """Print info and return if in timeout or crash pass states.""" |
| 152 if output.HasTimedOut(): |
| 153 # Dashed output, so that no other clusterfuzz tools can match the |
| 154 # words timeout or crash. |
| 155 print '# V8 correctness - T-I-M-E-O-U-T %d' % step_number |
| 156 return True |
| 157 if output.HasCrashed(): |
| 158 print '# V8 correctness - C-R-A-S-H %d' % step_number |
| 159 return True |
| 160 return False |
| 161 |
| 162 |
| 163 def fail_bailout(output, ignore_by_output_fun): |
| 164 """Print failure state and return if ignore_by_output_fun matches output.""" |
| 165 bug = (ignore_by_output_fun(output.stdout) or '').strip() |
| 166 if bug: |
| 167 print FAILURE_HEADER_TEMPLATE % dict( |
| 168 configs='', sources='', suppression=bug) |
| 169 return True |
| 170 return False |
| 171 |
| 172 |
| 173 def main(): |
| 174 options = parse_args() |
| 175 |
| 176 # Suppressions are architecture and configuration specific. |
| 177 suppress = v8_suppressions.get_suppression( |
| 178 options.first_arch, options.first_config, |
| 179 options.second_arch, options.second_config, |
| 180 ) |
| 181 |
| 182 if test_pattern_bailout(options.testcase, suppress.ignore): |
| 183 return RETURN_FAILURE |
| 184 |
| 185 common_flags = FLAGS + ['--random-seed', str(options.random_seed)] |
| 186 first_config_flags = common_flags + CONFIGS[options.first_config] |
| 187 second_config_flags = common_flags + CONFIGS[options.second_config] |
| 188 |
| 189 def run_d8(d8, config_flags): |
| 190 return v8_commands.Execute( |
| 191 [d8] + config_flags + PREAMBLE + [options.testcase], |
| 192 cwd=os.path.dirname(options.testcase), |
| 193 timeout=TIMEOUT, |
| 194 ) |
| 195 |
| 196 first_config_output = run_d8(options.first_d8, first_config_flags) |
| 197 |
| 198 # Early bailout based on first run's output. |
| 199 if pass_bailout(first_config_output, 1): |
| 200 return RETURN_PASS |
| 201 if fail_bailout(first_config_output, suppress.ignore_by_output1): |
| 202 return RETURN_FAILURE |
| 203 |
| 204 second_config_output = run_d8(options.second_d8, second_config_flags) |
| 205 |
| 206 # Bailout based on second run's output. |
| 207 if pass_bailout(second_config_output, 2): |
| 208 return RETURN_PASS |
| 209 if fail_bailout(second_config_output, suppress.ignore_by_output2): |
| 210 return RETURN_FAILURE |
| 211 |
| 212 difference = suppress.diff( |
| 213 first_config_output.stdout, second_config_output.stdout) |
| 214 if difference: |
| 215 # The first three entries will be parsed by clusterfuzz. Format changes |
| 216 # will require changes on the clusterfuzz side. |
| 217 first_config_label = '%s,%s' % (options.first_arch, options.first_config) |
| 218 second_config_label = '%s,%s' % (options.second_arch, options.second_config) |
| 219 print FAILURE_TEMPLATE % dict( |
| 220 configs='%s:%s' % (first_config_label, second_config_label), |
| 221 sources='', # TODO |
| 222 suppression='', # We can't tie bugs to differences. |
| 223 first_config_label=first_config_label, |
| 224 second_config_label=second_config_label, |
| 225 first_config_flags=' '.join(first_config_flags), |
| 226 second_config_flags=' '.join(second_config_flags), |
| 227 first_config_output=first_config_output.stdout, |
| 228 second_config_output=second_config_output.stdout, |
| 229 difference=difference, |
| 230 ) |
| 231 return RETURN_FAILURE |
| 232 |
| 233 # TODO(machenbach): Figure out if we could also return a bug in case there's |
| 234 # no difference, but one of the line suppressions has matched - and without |
| 235 # the match there would be a difference. |
| 236 |
| 237 print '# V8 correctness - pass' |
| 238 return RETURN_PASS |
| 239 |
| 240 |
| 241 if __name__ == "__main__": |
| 242 try: |
| 243 result = main() |
| 244 except SystemExit: |
| 245 # Make sure clusterfuzz reports internal errors and wrong usage. |
| 246 # Use one label for all internal and usage errors. |
| 247 print FAILURE_HEADER_TEMPLATE % dict( |
| 248 configs='', sources='', suppression='wrong_usage') |
| 249 result = RETURN_FAILURE |
| 250 except Exception as e: |
| 251 print FAILURE_HEADER_TEMPLATE % dict( |
| 252 configs='', sources='', suppression='internal_error') |
| 253 print '# Internal error: %s' % e |
| 254 traceback.print_exc(file=sys.stdout) |
| 255 result = RETURN_FAILURE |
| 256 |
| 257 sys.exit(result) |
OLD | NEW |