OLD | NEW |
(Empty) | |
| 1 # Copyright 2016 the V8 project authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. |
| 4 |
| 5 """ |
| 6 Suppressions for V8 correctness fuzzer failures. |
| 7 |
| 8 We support three types of suppressions: |
| 9 1. Ignore test case by pattern. |
| 10 Map a regular expression to a bug entry. A new failure will be reported |
| 11 when the pattern matches a JS test case. |
| 12 Subsequent matches will be recoreded under the first failure. |
| 13 |
| 14 2. Ignore test run by output pattern: |
| 15 Map a regular expression to a bug entry. A new failure will be reported |
| 16 when the pattern matches the output of a particular run. |
| 17 Subsequent matches will be recoreded under the first failure. |
| 18 |
| 19 3. Relax line-to-line comparisons with expressions of lines to ignore and |
| 20 lines to be normalized (i.e. ignore only portions of lines). |
| 21 These are not tied to bugs, be careful to not silently switch off this tool! |
| 22 |
| 23 Alternatively, think about adding a behavior change to v8_suppressions.js |
| 24 to silence a particular class of problems. |
| 25 """ |
| 26 |
| 27 import itertools |
| 28 import re |
| 29 |
| 30 # Max line length for regular experessions checking for lines to ignore. |
| 31 MAX_LINE_LENGTH = 512 |
| 32 |
| 33 # For ignoring lines before carets and to ignore caret positions. |
| 34 CARET_RE = re.compile(r'^\s*\^\s*$') |
| 35 |
| 36 # Ignore by test case pattern. Map from bug->regexp. |
| 37 # Regular expressions are assumed to be compiled. We use regexp.match. |
| 38 IGNORE_TEST_CASES = { |
| 39 'crbug.com/662907': |
| 40 re.compile(r'.*new Array.*\[\d+\] =.*' |
| 41 r'((Array)|(Object)).prototype.__defineSetter__.*', re.S), |
| 42 |
| 43 'crbug.com/663340': |
| 44 re.compile(r'.*\.shift\(\).*', re.S), |
| 45 |
| 46 'crbug.com/666308': |
| 47 re.compile(r'.*End stripped down and modified version.*' |
| 48 r'\.prototype.*instanceof.*.*', re.S), |
| 49 } |
| 50 |
| 51 # Ignore by output pattern. Map from config->bug->regexp. Config '' is used |
| 52 # to match all configurations. Otherwise use either a compiler configuration, |
| 53 # e.g. fullcode or validate_asm or an architecture, e.g. x64 or ia32 or a |
| 54 # comma-separated combination, e.g. x64,fullcode, for more specific |
| 55 # suppressions. |
| 56 # Bug is preferred to be a crbug.com/XYZ, but can be any short distinguishable |
| 57 # label. |
| 58 # Regular expressions are assumed to be compiled. We use regexp.search. |
| 59 IGNORE_OUTPUT = { |
| 60 '': { |
| 61 'crbug.com/664068': |
| 62 re.compile(r'RangeError', re.S), |
| 63 |
| 64 'crbug.com/669017': |
| 65 re.compile(r'SyntaxError', re.S), |
| 66 }, |
| 67 'validate_asm': { |
| 68 'validate_asm': |
| 69 re.compile(r'TypeError'), |
| 70 }, |
| 71 } |
| 72 |
| 73 # Lines matching any of the following regular expressions will be ignored |
| 74 # if appearing on both sides. The capturing groups need to match exactly. |
| 75 # Use uncompiled regular expressions - they'll be compiled later. |
| 76 ALLOWED_LINE_DIFFS = [ |
| 77 # Ignore caret position in stack traces. |
| 78 r'^\s*\^\s*$', |
| 79 |
| 80 # Ignore some stack trace headers as messages might not match. |
| 81 r'^(.*)TypeError: .* is not a function$', |
| 82 r'^(.*)TypeError: .* is not a constructor$', |
| 83 r'^(.*)TypeError: (.*) is not .*$', |
| 84 r'^(.*)ReferenceError: .* is not defined$', |
| 85 r'^(.*):\d+: ReferenceError: .* is not defined$', |
| 86 |
| 87 # These are rarely needed. It includes some cases above. |
| 88 r'^\w*Error: .* is not .*$', |
| 89 r'^(.*) \w*Error: .* is not .*$', |
| 90 r'^(.*):\d+: \w*Error: .* is not .*$', |
| 91 |
| 92 # Some test cases just print the message. |
| 93 r'^.* is not a function(.*)$', |
| 94 r'^(.*) is not a .*$', |
| 95 |
| 96 # crbug.com/669017 |
| 97 r'^(.*)SyntaxError: .*$', |
| 98 |
| 99 # Ignore lines of stack traces as character positions might not match. |
| 100 r'^ at (?:new )?([^:]*):\d+:\d+(.*)$', |
| 101 r'^(.*):\d+:(.*)$', |
| 102 |
| 103 # crbug.com/662840 |
| 104 r"^.*(?:Trying to access ')?(\w*)(?:(?:' through proxy)|" |
| 105 r"(?: is not defined))$", |
| 106 ] |
| 107 |
| 108 # Lines matching any of the following regular expressions will be ignored. |
| 109 # Use uncompiled regular expressions - they'll be compiled later. |
| 110 IGNORE_LINES = [ |
| 111 r'^Validation of asm\.js module failed: .+$', |
| 112 r'^.*:\d+: Invalid asm.js: .*$', |
| 113 r'^Warning: unknown flag .*$', |
| 114 r'^Warning: .+ is deprecated.*$', |
| 115 r'^Try --help for options$', |
| 116 ] |
| 117 |
| 118 |
| 119 ############################################################################### |
| 120 # Implementation - you should not need to change anything below this point. |
| 121 |
| 122 # Compile regular expressions. |
| 123 ALLOWED_LINE_DIFFS = [re.compile(exp) for exp in ALLOWED_LINE_DIFFS] |
| 124 IGNORE_LINES = [re.compile(exp) for exp in IGNORE_LINES] |
| 125 |
| 126 |
| 127 def line_pairs(lines): |
| 128 return itertools.izip_longest( |
| 129 lines, itertools.islice(lines, 1, None), fillvalue=None) |
| 130 |
| 131 |
| 132 def caret_match(line1, line2): |
| 133 if (not line1 or |
| 134 not line2 or |
| 135 len(line1) > MAX_LINE_LENGTH or |
| 136 len(line2) > MAX_LINE_LENGTH): |
| 137 return False |
| 138 return bool(CARET_RE.match(line1) and CARET_RE.match(line2)) |
| 139 |
| 140 |
| 141 def short_line_output(line): |
| 142 if len(line) <= MAX_LINE_LENGTH: |
| 143 # Avoid copying. |
| 144 return line |
| 145 return line[0:MAX_LINE_LENGTH] + '...' |
| 146 |
| 147 |
| 148 def ignore_by_regexp(line1, line2, allowed): |
| 149 if len(line1) > MAX_LINE_LENGTH or len(line2) > MAX_LINE_LENGTH: |
| 150 return False |
| 151 for exp in allowed: |
| 152 match1 = exp.match(line1) |
| 153 match2 = exp.match(line2) |
| 154 if match1 and match2: |
| 155 # If there are groups in the regexp, ensure the groups matched the same |
| 156 # things. |
| 157 if match1.groups() == match2.groups(): # tuple comparison |
| 158 return True |
| 159 return False |
| 160 |
| 161 |
| 162 def diff_output(output1, output2, allowed, ignore1, ignore2): |
| 163 def useful_line(ignore): |
| 164 def fun(line): |
| 165 return all(not e.match(line) for e in ignore) |
| 166 return fun |
| 167 |
| 168 lines1 = filter(useful_line(ignore1), output1) |
| 169 lines2 = filter(useful_line(ignore2), output2) |
| 170 |
| 171 for ((line1, lookahead1), (line2, lookahead2)) in itertools.izip_longest( |
| 172 line_pairs(lines1), line_pairs(lines2), fillvalue=(None, None)): |
| 173 |
| 174 # Only one of the two iterators should run out. |
| 175 assert not (line1 is None and line2 is None) |
| 176 |
| 177 # One iterator ends earlier. |
| 178 if line1 is None: |
| 179 return '+ %s' % short_line_output(line2) |
| 180 if line2 is None: |
| 181 return '- %s' % short_line_output(line1) |
| 182 |
| 183 # If lines are equal, no further checks are necessary. |
| 184 if line1 == line2: |
| 185 continue |
| 186 |
| 187 # Look ahead. If next line is a caret, ignore this line. |
| 188 if caret_match(lookahead1, lookahead2): |
| 189 continue |
| 190 |
| 191 # Check if a regexp allows these lines to be different. |
| 192 if ignore_by_regexp(line1, line2, allowed): |
| 193 continue |
| 194 |
| 195 # Lines are different. |
| 196 return '- %s\n+ %s' % (short_line_output(line1), short_line_output(line2)) |
| 197 |
| 198 # No difference found. |
| 199 return None |
| 200 |
| 201 |
| 202 def get_suppression(arch1, config1, arch2, config2): |
| 203 return V8Suppression(arch1, config1, arch2, config2) |
| 204 |
| 205 |
| 206 class Suppression(object): |
| 207 def diff(self, output1, output2): |
| 208 return None |
| 209 |
| 210 def ignore(self, testcase): |
| 211 return False |
| 212 |
| 213 def ignore_by_output1(self, output): |
| 214 return False |
| 215 |
| 216 def ignore_by_output2(self, output): |
| 217 return False |
| 218 |
| 219 |
| 220 class V8Suppression(Suppression): |
| 221 def __init__(self, arch1, config1, arch2, config2): |
| 222 self.arch1 = arch1 |
| 223 self.config1 = config1 |
| 224 self.arch2 = arch2 |
| 225 self.config2 = config2 |
| 226 |
| 227 def diff(self, output1, output2): |
| 228 return diff_output( |
| 229 output1.splitlines(), |
| 230 output2.splitlines(), |
| 231 ALLOWED_LINE_DIFFS, |
| 232 IGNORE_LINES, |
| 233 IGNORE_LINES, |
| 234 ) |
| 235 |
| 236 def ignore(self, testcase): |
| 237 for bug, exp in IGNORE_TEST_CASES.iteritems(): |
| 238 if exp.match(testcase): |
| 239 return bug |
| 240 return False |
| 241 |
| 242 def ignore_by_output1(self, output): |
| 243 return self.ignore_by_output(output, self.arch1, self.config1) |
| 244 |
| 245 def ignore_by_output2(self, output): |
| 246 return self.ignore_by_output(output, self.arch2, self.config2) |
| 247 |
| 248 def ignore_by_output(self, output, arch, config): |
| 249 def check(mapping): |
| 250 for bug, exp in mapping.iteritems(): |
| 251 if exp.search(output): |
| 252 return bug |
| 253 return None |
| 254 bug = check(IGNORE_OUTPUT.get('', {})) |
| 255 if bug: |
| 256 return bug |
| 257 bug = check(IGNORE_OUTPUT.get(arch, {})) |
| 258 if bug: |
| 259 return bug |
| 260 bug = check(IGNORE_OUTPUT.get(config, {})) |
| 261 if bug: |
| 262 return bug |
| 263 bug = check(IGNORE_OUTPUT.get('%s,%s' % (arch, config), {})) |
| 264 if bug: |
| 265 return bug |
| 266 return None |
OLD | NEW |