| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/python | |
| 2 # Copyright (c) 2012 The Native Client 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 collections | |
| 7 import glob | |
| 8 import optparse | |
| 9 import re | |
| 10 import subprocess | |
| 11 import sys | |
| 12 | |
| 13 import test_format | |
| 14 | |
| 15 | |
| 16 NCVAL_VERDICT = { | |
| 17 '*** <input> is safe ***': True, | |
| 18 '*** <input> IS UNSAFE ***': False} | |
| 19 | |
| 20 | |
| 21 ValidatorResult = collections.namedtuple('ValidatorResult', 'verdict offsets') | |
| 22 | |
| 23 | |
| 24 def ParseNval(nval_content): | |
| 25 """Parse content of @nval section. | |
| 26 | |
| 27 Args: | |
| 28 nval_content: Content of @nval section (as produced by 32-bit ncval | |
| 29 with options --max_errors=-1 --detailed=false --cpuid-all). | |
| 30 | |
| 31 Returns: | |
| 32 ValidatorResult | |
| 33 """ | |
| 34 | |
| 35 lines = nval_content.split('\n') | |
| 36 last_line = lines.pop() | |
| 37 assert last_line == '' | |
| 38 verdict = NCVAL_VERDICT[lines.pop()] | |
| 39 | |
| 40 offsets = set() | |
| 41 for line in lines: | |
| 42 if re.match(r'.+ > .+ \(read overflow of .+ bytes\)', line): | |
| 43 # Add to offsets something that designedly can't occur in offsets | |
| 44 # produced by ParseRdfaOutput, so that difference will show up and | |
| 45 # corresponding test won't be missed. | |
| 46 offsets.add('read overflow') | |
| 47 continue | |
| 48 if line == 'ErrorSegmentation': | |
| 49 # Same here. | |
| 50 offsets.add('ErrorSegmentation') | |
| 51 continue | |
| 52 | |
| 53 # Parse error message of the form | |
| 54 # VALIDATOR: 4: Bad prefix usage | |
| 55 m = re.match(r'VALIDATOR: ([0-9a-f]+): (.*)$', line, re.IGNORECASE) | |
| 56 | |
| 57 assert m is not None, "can't parse %r" % line | |
| 58 | |
| 59 offset = int(m.group(1), 16) | |
| 60 offsets.add(offset) | |
| 61 | |
| 62 return ValidatorResult(verdict=verdict, offsets=offsets) | |
| 63 | |
| 64 | |
| 65 def ParseRval(rval_content): | |
| 66 """Parse content of @rval section. | |
| 67 | |
| 68 Args: | |
| 69 nval_content: Content of @rval section (as produced by 64-bit ncval | |
| 70 with options --max_errors=-1 --readwrite_sfi --detailed=false | |
| 71 --annotate=false --cpuid-all). | |
| 72 | |
| 73 Returns: | |
| 74 ValidatorResult | |
| 75 """ | |
| 76 | |
| 77 lines = rval_content.split('\n') | |
| 78 last_line = lines.pop() | |
| 79 assert last_line == '' | |
| 80 verdict = NCVAL_VERDICT[lines.pop()] | |
| 81 | |
| 82 offsets = set() | |
| 83 for prev_line, line in zip([None] + lines, lines): | |
| 84 if line.startswith('VALIDATOR: Checking jump targets:'): | |
| 85 continue | |
| 86 if line.startswith('VALIDATOR: Checking that basic blocks are aligned'): | |
| 87 continue | |
| 88 | |
| 89 # Skip disassembler output of the form | |
| 90 # VALIDATOR: 0000000000000003: 49 89 14 07 mov [%r15+%rax*1], %rdx | |
| 91 m = re.match(r'VALIDATOR: ([0-9a-f]+):', line, re.IGNORECASE) | |
| 92 if m is not None: | |
| 93 continue | |
| 94 | |
| 95 # Parse error message of the form | |
| 96 # VALIDATOR: ERROR: 20: Bad basic block alignment. | |
| 97 m = re.match(r'VALIDATOR: ERROR: ([0-9a-f]+): (.*)', line, re.IGNORECASE) | |
| 98 if m is not None: | |
| 99 offset = int(m.group(1), 16) | |
| 100 offsets.add(offset) | |
| 101 continue | |
| 102 | |
| 103 # Parse two-line error messages of the form | |
| 104 # VALIDATOR: 0000000000000003: 49 89 14 07 mov [%r15+%rax*1], %rdx | |
| 105 # VALIDATOR: ERROR: Invalid index register in memory offset | |
| 106 m = re.match(r'VALIDATOR: (ERROR|WARNING): .*$', line, re.IGNORECASE) | |
| 107 if m is not None: | |
| 108 message_type = m.group(1) | |
| 109 assert prev_line is not None, ( | |
| 110 "can't deduce error offset because line %r " | |
| 111 "is not preceded with disassembly" % line) | |
| 112 m2 = re.match(r'VALIDATOR: ([0-9a-f]+):', prev_line, re.IGNORECASE) | |
| 113 assert m2 is not None, "can't parse line %r preceding line %r" % ( | |
| 114 prev_line, | |
| 115 line) | |
| 116 offset = int(m2.group(1), 16) | |
| 117 if message_type != 'WARNING': | |
| 118 offsets.add(offset) | |
| 119 continue | |
| 120 | |
| 121 raise AssertionError("can't parse line %r" % line) | |
| 122 | |
| 123 return ValidatorResult(verdict=verdict, offsets=offsets) | |
| 124 | |
| 125 | |
| 126 RDFA_VERDICT = { | |
| 127 'return code: 0': True, | |
| 128 'return code: 1': False} | |
| 129 | |
| 130 | |
| 131 def ParseRdfaOutput(rdfa_content): | |
| 132 """Parse content of @rdfa_output section. | |
| 133 | |
| 134 Args: | |
| 135 rdfa_content: Content of @rdfa_output section in .test file. | |
| 136 | |
| 137 Returns: | |
| 138 ValidatorResult | |
| 139 """ | |
| 140 | |
| 141 lines = rdfa_content.split('\n') | |
| 142 assert lines[-1] == '' | |
| 143 verdict = RDFA_VERDICT[lines[-2]] | |
| 144 | |
| 145 offsets = set() | |
| 146 for line in lines[:-2]: | |
| 147 # Parse error message of the form | |
| 148 # 4: [1] DFA error in validator | |
| 149 m = re.match(r'([0-9a-f]+): \[\d+\] (.*)$', line, re.IGNORECASE) | |
| 150 assert m is not None, "can't parse %r" % line | |
| 151 offset = int(m.group(1), 16) | |
| 152 offsets.add(offset) | |
| 153 | |
| 154 return ValidatorResult(verdict=verdict, offsets=offsets) | |
| 155 | |
| 156 | |
| 157 def Compare(options, items_list, stats): | |
| 158 val_field = {32: 'nval', 64: 'rval'}[options.bits] | |
| 159 val_parser = {32: ParseNval, 64: ParseRval}[options.bits] | |
| 160 | |
| 161 info = dict(items_list) | |
| 162 if 'rdfa_output' not in info: | |
| 163 if val_field in info: | |
| 164 print ' rdfa_output section is missing' | |
| 165 stats['rdfa missing'] +=1 | |
| 166 else: | |
| 167 print ' both sections are missing' | |
| 168 stats['both missing'] += 1 | |
| 169 return items_list | |
| 170 if val_field not in info: | |
| 171 print ' @%s section is missing' % val_field | |
| 172 stats['val missing'] += 1 | |
| 173 return items_list | |
| 174 | |
| 175 val = val_parser(info[val_field]) | |
| 176 rdfa = ParseRdfaOutput(info['rdfa_output']) | |
| 177 | |
| 178 if val == rdfa: | |
| 179 stats['agree'] += 1 | |
| 180 if options.git: | |
| 181 # Stage the file for commit, so that files that pass can be | |
| 182 # committed with "git commit" (without -a or other arguments). | |
| 183 subprocess.check_call(['git', 'add', test_filename]) | |
| 184 | |
| 185 if 'validators_disagree' in info: | |
| 186 stats['spurious @validators_disagree'] += 1 | |
| 187 print (' warning: validators agree, but the section ' | |
| 188 '"@validators_disagree" is present') | |
| 189 else: | |
| 190 stats['disagree'] += 1 | |
| 191 if 'validators_disagree' in info: | |
| 192 print ' validators disagree, see @validators_disagree section' | |
| 193 else: | |
| 194 print ' validators disagree!' | |
| 195 stats['unexplained disagreements'] += 1 | |
| 196 | |
| 197 diff = ['TODO: explain this\n'] | |
| 198 if val.verdict != rdfa.verdict: | |
| 199 diff.append('old validator verdict: %s\n' % val.verdict) | |
| 200 diff.append('rdfa validator verdict: %s\n' % rdfa.verdict) | |
| 201 | |
| 202 set_diff = val.offsets - rdfa.offsets | |
| 203 if len(set_diff) > 0: | |
| 204 diff.append('errors reported by old validator but not by rdfa one:\n') | |
| 205 for offset in sorted(set_diff): | |
| 206 if isinstance(offset, int): | |
| 207 offset = hex(offset) | |
| 208 diff.append(' %s\n' % offset) | |
| 209 | |
| 210 set_diff = rdfa.offsets - val.offsets | |
| 211 if len(set_diff) > 0: | |
| 212 diff.append('errors reported by rdfa validator but not by old one:\n') | |
| 213 for offset in sorted(set_diff): | |
| 214 if isinstance(offset, int): | |
| 215 offset = hex(offset) | |
| 216 diff.append(' %s\n' % offset) | |
| 217 | |
| 218 items_list = items_list + [('validators_disagree', ''.join(diff))] | |
| 219 | |
| 220 return items_list | |
| 221 | |
| 222 | |
| 223 def main(args): | |
| 224 parser = optparse.OptionParser() | |
| 225 parser.add_option('--bits', | |
| 226 type=int, | |
| 227 help='The subarchitecture to run tests against: 32 or 64') | |
| 228 parser.add_option('--update', | |
| 229 default=False, | |
| 230 action='store_true', | |
| 231 help='When validators disagree, fill in ' | |
| 232 '@validators_disagree section (if not present)') | |
| 233 # TODO(shcherbina): Get rid of this option once most tests are committed. | |
| 234 parser.add_option('--git', | |
| 235 default=False, | |
| 236 action='store_true', | |
| 237 help='Add tests with no discrepancies to git index') | |
| 238 | |
| 239 options, args = parser.parse_args(args) | |
| 240 | |
| 241 if options.bits not in [32, 64]: | |
| 242 parser.error('specify --bits 32 or --bits 64') | |
| 243 | |
| 244 if len(args) == 0: | |
| 245 parser.error('No test files specified') | |
| 246 | |
| 247 stats = collections.defaultdict(int) | |
| 248 | |
| 249 for glob_expr in args: | |
| 250 test_files = sorted(glob.glob(glob_expr)) | |
| 251 if len(test_files) == 0: | |
| 252 raise AssertionError( | |
| 253 '%r matched no files, which was probably not intended' % glob_expr) | |
| 254 for test_file in test_files: | |
| 255 print 'Comparing', test_file | |
| 256 tests = test_format.LoadTestFile(test_file) | |
| 257 tests = [Compare(options, test, stats) for test in tests] | |
| 258 if options.update: | |
| 259 test_format.SaveTestFile(tests, test_file) | |
| 260 | |
| 261 print 'Stats:' | |
| 262 for key, value in stats.items(): | |
| 263 print ' %s: %d' %(key, value) | |
| 264 | |
| 265 if options.update: | |
| 266 if stats['unexplained disagreements'] > 0: | |
| 267 print '@validators_disagree sections were created' | |
| 268 else: | |
| 269 if stats['unexplained disagreements'] > 0: | |
| 270 sys.exit(1) | |
| 271 | |
| 272 | |
| 273 if __name__ == '__main__': | |
| 274 main(sys.argv[1:]) | |
| OLD | NEW |