mirror of
				https://sourceware.org/git/glibc.git
				synced 2025-10-30 10:45:40 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			303 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			303 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/usr/bin/python3
 | |
| # Test that the ld.so --list-diagnostics output has the expected syntax.
 | |
| # Copyright (C) 2022-2024 Free Software Foundation, Inc.
 | |
| # Copyright The GNU Toolchain Authors.
 | |
| # This file is part of the GNU C Library.
 | |
| #
 | |
| # The GNU C Library is free software; you can redistribute it and/or
 | |
| # modify it under the terms of the GNU Lesser General Public
 | |
| # License as published by the Free Software Foundation; either
 | |
| # version 2.1 of the License, or (at your option) any later version.
 | |
| #
 | |
| # The GNU C Library is distributed in the hope that it will be useful,
 | |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | |
| # Lesser General Public License for more details.
 | |
| #
 | |
| # You should have received a copy of the GNU Lesser General Public
 | |
| # License along with the GNU C Library; if not, see
 | |
| # <https://www.gnu.org/licenses/>.
 | |
| 
 | |
| import argparse
 | |
| import collections
 | |
| import subprocess
 | |
| import sys
 | |
| 
 | |
| try:
 | |
|     subprocess.run
 | |
| except:
 | |
|     class _CompletedProcess:
 | |
|         def __init__(self, args, returncode, stdout=None, stderr=None):
 | |
|             self.args = args
 | |
|             self.returncode = returncode
 | |
|             self.stdout = stdout
 | |
|             self.stderr = stderr
 | |
| 
 | |
|     def _run(*popenargs, input=None, timeout=None, check=False, **kwargs):
 | |
|         assert(timeout is None)
 | |
|         with subprocess.Popen(*popenargs, **kwargs) as process:
 | |
|             try:
 | |
|                 stdout, stderr = process.communicate(input)
 | |
|             except:
 | |
|                 process.kill()
 | |
|                 process.wait()
 | |
|                 raise
 | |
|             returncode = process.poll()
 | |
|             if check and returncode:
 | |
|                 raise subprocess.CalledProcessError(returncode, popenargs)
 | |
|         return _CompletedProcess(popenargs, returncode, stdout, stderr)
 | |
| 
 | |
|     subprocess.run = _run
 | |
| 
 | |
| # Number of errors encountered.  Zero means no errors (test passes).
 | |
| errors = 0
 | |
| 
 | |
| def parse_line(line):
 | |
|     """Parse a line of --list-diagnostics output.
 | |
| 
 | |
|     This function returns a pair (SUBSCRIPTS, VALUE).  VALUE is either
 | |
|     a byte string or an integer.  SUBSCRIPT is a tuple of (LABEL,
 | |
|     INDEX) pairs, where LABEL is a field identifier (a string), and
 | |
|     INDEX is an integer or None, to indicate that this field is not
 | |
|     indexed.
 | |
| 
 | |
|     """
 | |
| 
 | |
|     # Extract the list of subscripts before the value.
 | |
|     idx = 0
 | |
|     subscripts = []
 | |
|     while line[idx] != '=':
 | |
|         start_idx = idx
 | |
| 
 | |
|         # Extract the label.
 | |
|         while line[idx] not in '[.=':
 | |
|             idx += 1
 | |
|         label = line[start_idx:idx]
 | |
| 
 | |
|         if line[idx] == '[':
 | |
|             # Subscript with a 0x index.
 | |
|             assert label
 | |
|             close_bracket = line.index(']', idx)
 | |
|             index = line[idx + 1:close_bracket]
 | |
|             assert index.startswith('0x')
 | |
|             index = int(index, 0)
 | |
|             subscripts.append((label, index))
 | |
|             idx = close_bracket + 1
 | |
|         else: # '.' or '='.
 | |
|             if label:
 | |
|                 subscripts.append((label, None))
 | |
|             if line[idx] == '.':
 | |
|                 idx += 1
 | |
| 
 | |
|     # The value is either a string or a 0x number.
 | |
|     value = line[idx + 1:]
 | |
|     if value[0] == '"':
 | |
|         # Decode the escaped string into a byte string.
 | |
|         assert value[-1] == '"'
 | |
|         idx = 1
 | |
|         result = []
 | |
|         while True:
 | |
|             ch = value[idx]
 | |
|             if ch == '\\':
 | |
|                 if value[idx + 1] in '"\\':
 | |
|                     result.append(ord(value[idx + 1]))
 | |
|                     idx += 2
 | |
|                 else:
 | |
|                     result.append(int(value[idx + 1:idx + 4], 8))
 | |
|                     idx += 4
 | |
|             elif ch == '"':
 | |
|                 assert idx == len(value) - 1
 | |
|                 break
 | |
|             else:
 | |
|                 result.append(ord(value[idx]))
 | |
|                 idx += 1
 | |
|         value = bytes(result)
 | |
|     else:
 | |
|         # Convert the value into an integer.
 | |
|         assert value.startswith('0x')
 | |
|         value = int(value, 0)
 | |
|     return (tuple(subscripts), value)
 | |
| 
 | |
| assert parse_line('a.b[0x1]=0x2') == ((('a', None), ('b', 1)), 2)
 | |
| assert parse_line(r'b[0x3]="four\040\"\\"') == ((('b', 3),), b'four \"\\')
 | |
| 
 | |
| # ABNF for a line of --list-diagnostics output.
 | |
| diagnostics_abnf = r"""
 | |
| HEXDIG = %x30-39 / %x61-6f ; lowercase a-f only
 | |
| ALPHA = %x41-5a / %x61-7a / %x7f ; letters and underscore
 | |
| ALPHA-NUMERIC = ALPHA / %x30-39 / "_"
 | |
| DQUOTE = %x22 ; "
 | |
| 
 | |
| ; Numbers are always hexadecimal and use a 0x prefix.
 | |
| hex-value-prefix = %x30 %x78
 | |
| hex-value = hex-value-prefix 1*HEXDIG
 | |
| 
 | |
| ; Strings use octal escape sequences and \\, \".
 | |
| string-char = %x20-21 / %x23-5c / %x5d-7e ; printable but not "\
 | |
| string-quoted-octal = %x30-33 2*2%x30-37
 | |
| string-quoted = "\" ("\" / DQUOTE / string-quoted-octal)
 | |
| string-value = DQUOTE *(string-char / string-quoted) DQUOTE
 | |
| 
 | |
| value = hex-value / string-value
 | |
| 
 | |
| label = ALPHA *ALPHA-NUMERIC
 | |
| index = "[" hex-value "]"
 | |
| subscript = label [index]
 | |
| 
 | |
| line = subscript *("." subscript) "=" value
 | |
| """
 | |
| 
 | |
| def check_consistency_with_manual(manual_path):
 | |
|     """Verify that the code fragments in the manual match this script.
 | |
| 
 | |
|     The code fragments are duplicated to clarify the dual license.
 | |
|     """
 | |
| 
 | |
|     global errors
 | |
| 
 | |
|     def extract_lines(path, start_line, end_line, skip_lines=()):
 | |
|         result = []
 | |
|         with open(path) as inp:
 | |
|             capturing = False
 | |
|             for line in inp:
 | |
|                 if line.strip() == start_line:
 | |
|                     capturing = True
 | |
|                 elif not capturing or line.strip() in skip_lines:
 | |
|                     continue
 | |
|                 elif line.strip() == end_line:
 | |
|                     capturing = False
 | |
|                 else:
 | |
|                     result.append(line)
 | |
|         if not result:
 | |
|             raise ValueError('{!r} not found in {!r}'.format(start_line, path))
 | |
|         if capturing:
 | |
|             raise ValueError('{!r} not found in {!r}'.format(end_line, path))
 | |
|         return result
 | |
| 
 | |
|     def check(name, manual, script):
 | |
|         global errors
 | |
| 
 | |
|         if manual == script:
 | |
|             return
 | |
|         print('error: {} fragment in manual is different'.format(name))
 | |
|         import difflib
 | |
|         sys.stdout.writelines(difflib.unified_diff(
 | |
|             manual, script, fromfile='manual', tofile='script'))
 | |
|         errors += 1
 | |
| 
 | |
|     manual_abnf = extract_lines(manual_path,
 | |
|                                 '@c ABNF-START', '@end smallexample',
 | |
|                                 skip_lines=('@smallexample',))
 | |
|     check('ABNF', diagnostics_abnf.splitlines(keepends=True)[1:], manual_abnf)
 | |
| 
 | |
| # If the abnf module can be imported, run an additional check that the
 | |
| # 'line' production from the ABNF grammar matches --list-diagnostics
 | |
| # output lines.
 | |
| try:
 | |
|     import abnf
 | |
| except ImportError:
 | |
|     abnf = None
 | |
|     print('info: skipping ABNF validation because the abnf module is missing')
 | |
| 
 | |
| if abnf is not None:
 | |
|     class Grammar(abnf.Rule):
 | |
|         pass
 | |
| 
 | |
|     Grammar.load_grammar(diagnostics_abnf)
 | |
| 
 | |
|     def parse_abnf(line):
 | |
|         global errors
 | |
| 
 | |
|         # Just verify that the line parses.
 | |
|         try:
 | |
|             Grammar('line').parse_all(line)
 | |
|         except abnf.ParseError:
 | |
|             print('error: ABNF parse error:', repr(line))
 | |
|             errors += 1
 | |
| else:
 | |
|     def parse_abnf(line):
 | |
|         pass
 | |
| 
 | |
| 
 | |
| def parse_diagnostics(cmd):
 | |
|     global errors
 | |
|     diag_out = subprocess.run(cmd, stdout=subprocess.PIPE, check=True,
 | |
|                               universal_newlines=True, shell=True).stdout
 | |
|     if diag_out[-1] != '\n':
 | |
|         print('error: ld.so output does not end in newline')
 | |
|         errors += 1
 | |
| 
 | |
|     PathType = collections.namedtuple('PathType',
 | |
|                                       'has_index value_type original_line')
 | |
|     # Mapping tuples of labels to PathType values.
 | |
|     path_types = {}
 | |
| 
 | |
|     seen_subscripts = {}
 | |
| 
 | |
|     for line in diag_out.splitlines():
 | |
|         parse_abnf(line)
 | |
|         subscripts, value = parse_line(line)
 | |
| 
 | |
|         # Check for duplicates.
 | |
|         if subscripts in seen_subscripts:
 | |
|             print('error: duplicate value assignment:', repr(line))
 | |
|             print('  previous line:,', repr(seen_subscripts[line]))
 | |
|             errors += 1
 | |
|         else:
 | |
|             seen_subscripts[subscripts] = line
 | |
| 
 | |
|         # Compare types against the previously seen labels.
 | |
|         labels = tuple([label for label, index in subscripts])
 | |
|         has_index = tuple([index is not None for label, index in subscripts])
 | |
|         value_type = type(value)
 | |
|         if labels in path_types:
 | |
|             previous_type = path_types[labels]
 | |
|             if has_index != previous_type.has_index:
 | |
|                 print('error: line has mismatch of indexing:', repr(line))
 | |
|                 print('  index types:', has_index)
 | |
|                 print('  previous:   ', previous_type.has_index)
 | |
|                 print('  previous line:', repr(previous_type.original_line))
 | |
|                 errors += 1
 | |
|             if value_type != previous_type.value_type:
 | |
|                 print('error: line has mismatch of value type:', repr(line))
 | |
|                 print('  value type:', value_type.__name__)
 | |
|                 print('  previous:  ', previous_type.value_type.__name__)
 | |
|                 print('  previous line:', repr(previous_type.original_line))
 | |
|                 errors += 1
 | |
|         else:
 | |
|             path_types[labels] = PathType(has_index, value_type, line)
 | |
| 
 | |
|         # Check that this line does not add indexing to a previous value.
 | |
|         for idx in range(1, len(subscripts) - 1):
 | |
|             if subscripts[:idx] in path_types:
 | |
|                 print('error: line assigns to atomic value:', repr(line))
 | |
|                 print('  previous line:', repr(previous_type.original_line))
 | |
|                 errors += 1
 | |
| 
 | |
|     if errors:
 | |
|         sys.exit(1)
 | |
| 
 | |
| def get_parser():
 | |
|     parser = argparse.ArgumentParser(description=__doc__)
 | |
|     parser.add_argument('--manual',
 | |
|                         help='path to .texi file for consistency checks')
 | |
|     parser.add_argument('command',
 | |
|                         help='comand to run')
 | |
|     return parser
 | |
| 
 | |
| 
 | |
| def main(argv):
 | |
|     parser = get_parser()
 | |
|     opts = parser.parse_args(argv)
 | |
| 
 | |
|     if opts.manual:
 | |
|         check_consistency_with_manual(opts.manual)
 | |
| 
 | |
|     parse_diagnostics(opts.command)
 | |
| 
 | |
|     if errors:
 | |
|         sys.exit(1)
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     main(sys.argv[1:])
 |