forked from mirrors/gecko-dev
		
	 dcfef841a7
			
		
	
	
		dcfef841a7
		
	
	
	
	
		
			
			MozReview-Commit-ID: AZdcEWyVV6e --HG-- extra : rebase_source : b1c45028c8d46be5ba590a27a2f9f20e248a26b1
		
			
				
	
	
		
			116 lines
		
	
	
	
		
			5.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			116 lines
		
	
	
	
		
			5.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # This Source Code Form is subject to the terms of the Mozilla Public
 | |
| # License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
| # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | |
| 
 | |
| from __future__ import print_function, unicode_literals
 | |
| 
 | |
| import logging
 | |
| import re
 | |
| 
 | |
| 
 | |
| class OutputHandler(object):
 | |
|     '''
 | |
|     A class for handling Valgrind output.
 | |
| 
 | |
|     Valgrind errors look like this:
 | |
| 
 | |
|     ==60741== 40 (24 direct, 16 indirect) bytes in 1 blocks are definitely lost in loss record 2,746 of 5,235
 | |
|     ==60741==    at 0x4C26B43: calloc (vg_replace_malloc.c:593)
 | |
|     ==60741==    by 0x63AEF65: PR_Calloc (prmem.c:443)
 | |
|     ==60741==    by 0x69F236E: PORT_ZAlloc_Util (secport.c:117)
 | |
|     ==60741==    by 0x69F1336: SECITEM_AllocItem_Util (secitem.c:28)
 | |
|     ==60741==    by 0xA04280B: ffi_call_unix64 (in /builds/slave/m-in-l64-valgrind-000000000000/objdir/toolkit/library/libxul.so)
 | |
|     ==60741==    by 0xA042443: ffi_call (ffi64.c:485)
 | |
| 
 | |
|     For each such error, this class extracts most or all of the first (error
 | |
|     kind) line, plus the function name in each of the first few stack entries.
 | |
|     With this data it constructs and prints a TEST-UNEXPECTED-FAIL message that
 | |
|     TBPL will highlight.
 | |
| 
 | |
|     It buffers these lines from which text is extracted so that the
 | |
|     TEST-UNEXPECTED-FAIL message can be printed before the full error.
 | |
| 
 | |
|     Parsing the Valgrind output isn't ideal, and it may break in the future if
 | |
|     Valgrind changes the format of the messages, or introduces new error kinds.
 | |
|     To protect against this, we also count how many lines containing
 | |
|     "<insert_a_suppression_name_here>" are seen. Thanks to the use of
 | |
|     --gen-suppressions=yes, exactly one of these lines is present per error. If
 | |
|     the count of these lines doesn't match the error count found during
 | |
|     parsing, then the parsing has missed one or more errors and we can fail
 | |
|     appropriately.
 | |
|     '''  # NOQA: E501
 | |
| 
 | |
|     def __init__(self, logger):
 | |
|         # The regexps in this list match all of Valgrind's errors. Note that
 | |
|         # Valgrind is English-only, so we don't have to worry about
 | |
|         # localization.
 | |
|         self.logger = logger
 | |
|         self.re_error = \
 | |
|             r'==\d+== (' + \
 | |
|             r'(Use of uninitialised value of size \d+)|' + \
 | |
|             r'(Conditional jump or move depends on uninitialised value\(s\))|' + \
 | |
|             r'(Syscall param .* contains uninitialised byte\(s\))|' + \
 | |
|             r'(Syscall param .* points to (unaddressable|uninitialised) byte\(s\))|' + \
 | |
|             r'((Unaddressable|Uninitialised) byte\(s\) found during client check request)|' + \
 | |
|             r'(Invalid free\(\) / delete / delete\[\] / realloc\(\))|' + \
 | |
|             r'(Mismatched free\(\) / delete / delete \[\])|' + \
 | |
|             r'(Invalid (read|write) of size \d+)|' + \
 | |
|             r'(Jump to the invalid address stated on the next line)|' + \
 | |
|             r'(Source and destination overlap in .*)|' + \
 | |
|             r'(.* bytes in .* blocks are .* lost)' + \
 | |
|             r')'
 | |
|         # Match identifer chars, plus ':' for namespaces, and '\?' in order to
 | |
|         # match "???" which Valgrind sometimes produces.
 | |
|         self.re_stack_entry = r'^==\d+==.*0x[A-Z0-9]+: ([A-Za-z0-9_:\?]+)'
 | |
|         self.re_suppression = r' *<insert_a_suppression_name_here>'
 | |
|         self.error_count = 0
 | |
|         self.suppression_count = 0
 | |
|         self.number_of_stack_entries_to_get = 0
 | |
|         self.curr_error = None
 | |
|         self.curr_location = None
 | |
|         self.buffered_lines = None
 | |
| 
 | |
|     def log(self, line):
 | |
|         self.logger(logging.INFO, 'valgrind-output', {'line': line}, '{line}')
 | |
| 
 | |
|     def __call__(self, line):
 | |
|         if self.number_of_stack_entries_to_get == 0:
 | |
|             # Look for the start of a Valgrind error.
 | |
|             m = re.search(self.re_error, line)
 | |
|             if m:
 | |
|                 self.error_count += 1
 | |
|                 self.number_of_stack_entries_to_get = 4
 | |
|                 self.curr_error = m.group(1)
 | |
|                 self.curr_location = ""
 | |
|                 self.buffered_lines = [line]
 | |
|             else:
 | |
|                 self.log(line)
 | |
| 
 | |
|         else:
 | |
|             # We've recently found a Valgrind error, and are now extracting
 | |
|             # details from the first few stack entries.
 | |
|             self.buffered_lines.append(line)
 | |
|             m = re.match(self.re_stack_entry, line)
 | |
|             if m:
 | |
|                 self.curr_location += m.group(1)
 | |
|             else:
 | |
|                 self.curr_location += '?!?'
 | |
| 
 | |
|             self.number_of_stack_entries_to_get -= 1
 | |
|             if self.number_of_stack_entries_to_get != 0:
 | |
|                 self.curr_location += ' / '
 | |
|             else:
 | |
|                 # We've finished getting the first few stack entries. Print the
 | |
|                 # failure message and the buffered lines, and then reset state.
 | |
|                 self.logger(logging.ERROR, 'valgrind-error-msg',
 | |
|                             {'error': self.curr_error,
 | |
|                              'location': self.curr_location},
 | |
|                             'TEST-UNEXPECTED-FAIL | valgrind-test | {error} at {location}')
 | |
|                 for b in self.buffered_lines:
 | |
|                     self.log(b)
 | |
|                 self.curr_error = None
 | |
|                 self.curr_location = None
 | |
|                 self.buffered_lines = None
 | |
| 
 | |
|         if re.match(self.re_suppression, line):
 | |
|             self.suppression_count += 1
 |