PageRenderTime 174ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/deps/v8/tools/foozzie/v8_suppressions.py

https://gitlab.com/jbergstroem/node.js
Python | 347 lines | 224 code | 34 blank | 89 comment | 14 complexity | 387c95186e43baec37553c0a834e6245 MD5 | raw file
  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. Suppressions for V8 correctness fuzzer failures.
  6. We support three types of suppressions:
  7. 1. Ignore test case by pattern.
  8. Map a regular expression to a bug entry. A new failure will be reported
  9. when the pattern matches a JS test case.
  10. Subsequent matches will be recoreded under the first failure.
  11. 2. Ignore test run by output pattern:
  12. Map a regular expression to a bug entry. A new failure will be reported
  13. when the pattern matches the output of a particular run.
  14. Subsequent matches will be recoreded under the first failure.
  15. 3. Relax line-to-line comparisons with expressions of lines to ignore and
  16. lines to be normalized (i.e. ignore only portions of lines).
  17. These are not tied to bugs, be careful to not silently switch off this tool!
  18. Alternatively, think about adding a behavior change to v8_suppressions.js
  19. to silence a particular class of problems.
  20. """
  21. import itertools
  22. import re
  23. # Max line length for regular experessions checking for lines to ignore.
  24. MAX_LINE_LENGTH = 512
  25. # For ignoring lines before carets and to ignore caret positions.
  26. CARET_RE = re.compile(r'^\s*\^\s*$')
  27. # Ignore by original source files. Map from bug->list of relative file paths in
  28. # V8, e.g. '/v8/test/mjsunit/d8-performance-now.js' including /v8/. A test will
  29. # be suppressed if one of the files below was used to mutate the test.
  30. IGNORE_SOURCES = {
  31. # This contains a usage of f.arguments that often fires.
  32. 'crbug.com/662424': [
  33. '/v8/test/mjsunit/bugs/bug-222.js',
  34. '/v8/test/mjsunit/bugs/bug-941049.js',
  35. '/v8/test/mjsunit/regress/regress-crbug-668795.js',
  36. '/v8/test/mjsunit/regress/regress-1079.js',
  37. '/v8/test/mjsunit/regress/regress-2989.js',
  38. ],
  39. 'crbug.com/681088': [
  40. '/v8/test/mjsunit/asm/asm-validation.js',
  41. '/v8/test/mjsunit/asm/b5528-comma.js',
  42. '/v8/test/mjsunit/asm/pointer-masking.js',
  43. '/v8/test/mjsunit/compiler/regress-443744.js',
  44. '/v8/test/mjsunit/regress/regress-599719.js',
  45. '/v8/test/mjsunit/regress/wasm/regression-647649.js',
  46. '/v8/test/mjsunit/wasm/asm-wasm.js',
  47. '/v8/test/mjsunit/wasm/asm-wasm-deopt.js',
  48. '/v8/test/mjsunit/wasm/asm-wasm-heap.js',
  49. '/v8/test/mjsunit/wasm/asm-wasm-literals.js',
  50. '/v8/test/mjsunit/wasm/asm-wasm-stack.js',
  51. ],
  52. 'crbug.com/681241': [
  53. '/v8/test/mjsunit/regress/regress-617526.js',
  54. '/v8/test/mjsunit/regress/wasm/regression-02862.js',
  55. ],
  56. 'crbug.com/688159': [
  57. '/v8/test/mjsunit/es7/exponentiation-operator.js',
  58. ],
  59. # TODO(machenbach): Implement blacklisting files for particular configs only,
  60. # here ignition_eager.
  61. 'crbug.com/691589': [
  62. '/v8/test/mjsunit/regress/regress-1200351.js',
  63. ],
  64. 'crbug.com/691587': [
  65. '/v8/test/mjsunit/asm/regress-674089.js',
  66. ],
  67. }
  68. # Ignore by test case pattern. Map from bug->regexp.
  69. # Regular expressions are assumed to be compiled. We use regexp.match.
  70. # Make sure the code doesn't match in the preamble portion of the test case
  71. # (i.e. in the modified inlined mjsunit.js). You can reference the comment
  72. # between the two parts like so:
  73. # 'crbug.com/666308':
  74. # re.compile(r'.*End stripped down and modified version.*'
  75. # r'\.prototype.*instanceof.*.*', re.S)
  76. # TODO(machenbach): Insert a JS sentinel between the two parts, because
  77. # comments are stripped during minimization.
  78. IGNORE_TEST_CASES = {
  79. }
  80. # Ignore by output pattern. Map from config->bug->regexp. Config '' is used
  81. # to match all configurations. Otherwise use either a compiler configuration,
  82. # e.g. fullcode or validate_asm or an architecture, e.g. x64 or ia32 or a
  83. # comma-separated combination, e.g. x64,fullcode, for more specific
  84. # suppressions.
  85. # Bug is preferred to be a crbug.com/XYZ, but can be any short distinguishable
  86. # label.
  87. # Regular expressions are assumed to be compiled. We use regexp.search.
  88. IGNORE_OUTPUT = {
  89. '': {
  90. 'crbug.com/664068':
  91. re.compile(r'RangeError(?!: byte length)', re.S),
  92. 'crbug.com/667678':
  93. re.compile(r'\[native code\]', re.S),
  94. 'crbug.com/681806':
  95. re.compile(r'WebAssembly\.Instance', re.S),
  96. 'crbug.com/681088':
  97. re.compile(r'TypeError: Cannot read property \w+ of undefined', re.S),
  98. 'crbug.com/689877':
  99. re.compile(r'^.*SyntaxError: .*Stack overflow$', re.M),
  100. },
  101. 'validate_asm': {
  102. 'validate_asm':
  103. re.compile(r'TypeError'),
  104. },
  105. }
  106. # Lines matching any of the following regular expressions will be ignored
  107. # if appearing on both sides. The capturing groups need to match exactly.
  108. # Use uncompiled regular expressions - they'll be compiled later.
  109. ALLOWED_LINE_DIFFS = [
  110. # Ignore caret position in stack traces.
  111. r'^\s*\^\s*$',
  112. # Ignore some stack trace headers as messages might not match.
  113. r'^(.*)TypeError: .* is not a function$',
  114. r'^(.*)TypeError: .* is not a constructor$',
  115. r'^(.*)TypeError: (.*) is not .*$',
  116. r'^(.*)ReferenceError: .* is not defined$',
  117. r'^(.*):\d+: ReferenceError: .* is not defined$',
  118. # These are rarely needed. It includes some cases above.
  119. r'^\w*Error: .* is not .*$',
  120. r'^(.*) \w*Error: .* is not .*$',
  121. r'^(.*):\d+: \w*Error: .* is not .*$',
  122. # Some test cases just print the message.
  123. r'^.* is not a function(.*)$',
  124. r'^(.*) is not a .*$',
  125. # crbug.com/662840
  126. r"^.*(?:Trying to access ')?(\w*)(?:(?:' through proxy)|"
  127. r"(?: is not defined))$",
  128. # crbug.com/680064. This subsumes one of the above expressions.
  129. r'^(.*)TypeError: .* function$',
  130. # crbug.com/664068
  131. r'^(.*)(?:Array buffer allocation failed|Invalid array buffer length)(.*)$',
  132. ]
  133. # Lines matching any of the following regular expressions will be ignored.
  134. # Use uncompiled regular expressions - they'll be compiled later.
  135. IGNORE_LINES = [
  136. r'^Warning: unknown flag .*$',
  137. r'^Warning: .+ is deprecated.*$',
  138. r'^Try --help for options$',
  139. # crbug.com/705962
  140. r'^\s\[0x[0-9a-f]+\]$',
  141. ]
  142. ###############################################################################
  143. # Implementation - you should not need to change anything below this point.
  144. # Compile regular expressions.
  145. ALLOWED_LINE_DIFFS = [re.compile(exp) for exp in ALLOWED_LINE_DIFFS]
  146. IGNORE_LINES = [re.compile(exp) for exp in IGNORE_LINES]
  147. ORIGINAL_SOURCE_PREFIX = 'v8-foozzie source: '
  148. def line_pairs(lines):
  149. return itertools.izip_longest(
  150. lines, itertools.islice(lines, 1, None), fillvalue=None)
  151. def caret_match(line1, line2):
  152. if (not line1 or
  153. not line2 or
  154. len(line1) > MAX_LINE_LENGTH or
  155. len(line2) > MAX_LINE_LENGTH):
  156. return False
  157. return bool(CARET_RE.match(line1) and CARET_RE.match(line2))
  158. def short_line_output(line):
  159. if len(line) <= MAX_LINE_LENGTH:
  160. # Avoid copying.
  161. return line
  162. return line[0:MAX_LINE_LENGTH] + '...'
  163. def ignore_by_regexp(line1, line2, allowed):
  164. if len(line1) > MAX_LINE_LENGTH or len(line2) > MAX_LINE_LENGTH:
  165. return False
  166. for exp in allowed:
  167. match1 = exp.match(line1)
  168. match2 = exp.match(line2)
  169. if match1 and match2:
  170. # If there are groups in the regexp, ensure the groups matched the same
  171. # things.
  172. if match1.groups() == match2.groups(): # tuple comparison
  173. return True
  174. return False
  175. def diff_output(output1, output2, allowed, ignore1, ignore2):
  176. """Returns a tuple (difference, source).
  177. The difference is None if there's no difference, otherwise a string
  178. with a readable diff.
  179. The source is the last source output within the test case, or None if no
  180. such output existed.
  181. """
  182. def useful_line(ignore):
  183. def fun(line):
  184. return all(not e.match(line) for e in ignore)
  185. return fun
  186. lines1 = filter(useful_line(ignore1), output1)
  187. lines2 = filter(useful_line(ignore2), output2)
  188. # This keeps track where we are in the original source file of the fuzz
  189. # test case.
  190. source = None
  191. for ((line1, lookahead1), (line2, lookahead2)) in itertools.izip_longest(
  192. line_pairs(lines1), line_pairs(lines2), fillvalue=(None, None)):
  193. # Only one of the two iterators should run out.
  194. assert not (line1 is None and line2 is None)
  195. # One iterator ends earlier.
  196. if line1 is None:
  197. return '+ %s' % short_line_output(line2), source
  198. if line2 is None:
  199. return '- %s' % short_line_output(line1), source
  200. # If lines are equal, no further checks are necessary.
  201. if line1 == line2:
  202. # Instrumented original-source-file output must be equal in both
  203. # versions. It only makes sense to update it here when both lines
  204. # are equal.
  205. if line1.startswith(ORIGINAL_SOURCE_PREFIX):
  206. source = line1[len(ORIGINAL_SOURCE_PREFIX):]
  207. continue
  208. # Look ahead. If next line is a caret, ignore this line.
  209. if caret_match(lookahead1, lookahead2):
  210. continue
  211. # Check if a regexp allows these lines to be different.
  212. if ignore_by_regexp(line1, line2, allowed):
  213. continue
  214. # Lines are different.
  215. return (
  216. '- %s\n+ %s' % (short_line_output(line1), short_line_output(line2)),
  217. source,
  218. )
  219. # No difference found.
  220. return None, source
  221. def get_suppression(arch1, config1, arch2, config2):
  222. return V8Suppression(arch1, config1, arch2, config2)
  223. class Suppression(object):
  224. def diff(self, output1, output2):
  225. return None
  226. def ignore_by_metadata(self, metadata):
  227. return False
  228. def ignore_by_content(self, testcase):
  229. return False
  230. def ignore_by_output1(self, output):
  231. return False
  232. def ignore_by_output2(self, output):
  233. return False
  234. class V8Suppression(Suppression):
  235. def __init__(self, arch1, config1, arch2, config2):
  236. self.arch1 = arch1
  237. self.config1 = config1
  238. self.arch2 = arch2
  239. self.config2 = config2
  240. def diff(self, output1, output2):
  241. return diff_output(
  242. output1.splitlines(),
  243. output2.splitlines(),
  244. ALLOWED_LINE_DIFFS,
  245. IGNORE_LINES,
  246. IGNORE_LINES,
  247. )
  248. def ignore_by_content(self, testcase):
  249. for bug, exp in IGNORE_TEST_CASES.iteritems():
  250. if exp.match(testcase):
  251. return bug
  252. return False
  253. def ignore_by_metadata(self, metadata):
  254. for bug, sources in IGNORE_SOURCES.iteritems():
  255. for source in sources:
  256. if source in metadata['sources']:
  257. return bug
  258. return False
  259. def ignore_by_output1(self, output):
  260. return self.ignore_by_output(output, self.arch1, self.config1)
  261. def ignore_by_output2(self, output):
  262. return self.ignore_by_output(output, self.arch2, self.config2)
  263. def ignore_by_output(self, output, arch, config):
  264. def check(mapping):
  265. for bug, exp in mapping.iteritems():
  266. if exp.search(output):
  267. return bug
  268. return None
  269. bug = check(IGNORE_OUTPUT.get('', {}))
  270. if bug:
  271. return bug
  272. bug = check(IGNORE_OUTPUT.get(arch, {}))
  273. if bug:
  274. return bug
  275. bug = check(IGNORE_OUTPUT.get(config, {}))
  276. if bug:
  277. return bug
  278. bug = check(IGNORE_OUTPUT.get('%s,%s' % (arch, config), {}))
  279. if bug:
  280. return bug
  281. return None