mirror of
				https://sourceware.org/git/glibc.git
				synced 2025-11-03 20:53:13 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			195 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			195 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#!/usr/bin/python3
 | 
						|
# Extract information from C headers.
 | 
						|
# Copyright (C) 2018-2023 Free Software Foundation, Inc.
 | 
						|
# 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 collections
 | 
						|
import os.path
 | 
						|
import re
 | 
						|
import subprocess
 | 
						|
import tempfile
 | 
						|
 | 
						|
 | 
						|
def compute_c_consts(sym_data, cc):
 | 
						|
    """Compute the values of some C constants.
 | 
						|
 | 
						|
    The first argument is a list whose elements are either strings
 | 
						|
    (preprocessor directives, or the special string 'START' to
 | 
						|
    indicate this function should insert its initial boilerplate text
 | 
						|
    in the output there) or pairs of strings (a name and a C
 | 
						|
    expression for the corresponding value).  Preprocessor directives
 | 
						|
    in the middle of the list may be used to select which constants
 | 
						|
    end up being evaluated using which expressions.
 | 
						|
 | 
						|
    """
 | 
						|
    out_lines = []
 | 
						|
    for arg in sym_data:
 | 
						|
        if isinstance(arg, str):
 | 
						|
            if arg == 'START':
 | 
						|
                out_lines.append('void\ndummy (void)\n{')
 | 
						|
            else:
 | 
						|
                out_lines.append(arg)
 | 
						|
            continue
 | 
						|
        name = arg[0]
 | 
						|
        value = arg[1]
 | 
						|
        out_lines.append('asm ("/* @@@name@@@%s@@@value@@@%%0@@@end@@@ */" '
 | 
						|
                         ': : \"i\" ((long int) (%s)));'
 | 
						|
                         % (name, value))
 | 
						|
    out_lines.append('}')
 | 
						|
    out_lines.append('')
 | 
						|
    out_text = '\n'.join(out_lines)
 | 
						|
    with tempfile.TemporaryDirectory() as temp_dir:
 | 
						|
        c_file_name = os.path.join(temp_dir, 'test.c')
 | 
						|
        s_file_name = os.path.join(temp_dir, 'test.s')
 | 
						|
        with open(c_file_name, 'w') as c_file:
 | 
						|
            c_file.write(out_text)
 | 
						|
        # Compilation has to be from stdin to avoid the temporary file
 | 
						|
        # name being written into the generated dependencies.
 | 
						|
        cmd = ('%s -S -o %s -x c - < %s' % (cc, s_file_name, c_file_name))
 | 
						|
        subprocess.check_call(cmd, shell=True)
 | 
						|
        consts = {}
 | 
						|
        with open(s_file_name, 'r') as s_file:
 | 
						|
            for line in s_file:
 | 
						|
                match = re.search('@@@name@@@([^@]*)'
 | 
						|
                                  '@@@value@@@[^0-9Xxa-fA-F-]*'
 | 
						|
                                  '([0-9Xxa-fA-F-]+).*@@@end@@@', line)
 | 
						|
                if match:
 | 
						|
                    if (match.group(1) in consts
 | 
						|
                        and match.group(2) != consts[match.group(1)]):
 | 
						|
                        raise ValueError('duplicate constant %s'
 | 
						|
                                         % match.group(1))
 | 
						|
                    consts[match.group(1)] = match.group(2)
 | 
						|
        return consts
 | 
						|
 | 
						|
 | 
						|
def list_macros(source_text, cc):
 | 
						|
    """List the preprocessor macros defined by the given source code.
 | 
						|
 | 
						|
    The return value is a pair of dicts, the first one mapping macro
 | 
						|
    names to their expansions and the second one mapping macro names
 | 
						|
    to lists of their arguments, or to None for object-like macros.
 | 
						|
 | 
						|
    """
 | 
						|
    with tempfile.TemporaryDirectory() as temp_dir:
 | 
						|
        c_file_name = os.path.join(temp_dir, 'test.c')
 | 
						|
        i_file_name = os.path.join(temp_dir, 'test.i')
 | 
						|
        with open(c_file_name, 'w') as c_file:
 | 
						|
            c_file.write(source_text)
 | 
						|
        cmd = ('%s -E -dM -o %s %s' % (cc, i_file_name, c_file_name))
 | 
						|
        subprocess.check_call(cmd, shell=True)
 | 
						|
        macros_exp = {}
 | 
						|
        macros_args = {}
 | 
						|
        with open(i_file_name, 'r') as i_file:
 | 
						|
            for line in i_file:
 | 
						|
                match = re.fullmatch('#define ([0-9A-Za-z_]+)(.*)\n', line)
 | 
						|
                if not match:
 | 
						|
                    raise ValueError('bad -dM output line: %s' % line)
 | 
						|
                name = match.group(1)
 | 
						|
                value = match.group(2)
 | 
						|
                if value.startswith(' '):
 | 
						|
                    value = value[1:]
 | 
						|
                    args = None
 | 
						|
                elif value.startswith('('):
 | 
						|
                    match = re.fullmatch(r'\((.*?)\) (.*)', value)
 | 
						|
                    if not match:
 | 
						|
                        raise ValueError('bad -dM output line: %s' % line)
 | 
						|
                    args = match.group(1).split(',')
 | 
						|
                    value = match.group(2)
 | 
						|
                else:
 | 
						|
                    raise ValueError('bad -dM output line: %s' % line)
 | 
						|
                if name in macros_exp:
 | 
						|
                    raise ValueError('duplicate macro: %s' % line)
 | 
						|
                macros_exp[name] = value
 | 
						|
                macros_args[name] = args
 | 
						|
    return macros_exp, macros_args
 | 
						|
 | 
						|
 | 
						|
def compute_macro_consts(source_text, cc, macro_re, exclude_re=None):
 | 
						|
    """Compute the integer constant values of macros defined by source_text.
 | 
						|
 | 
						|
    Macros must match the regular expression macro_re, and if
 | 
						|
    exclude_re is defined they must not match exclude_re.  Values are
 | 
						|
    computed with compute_c_consts.
 | 
						|
 | 
						|
    """
 | 
						|
    macros_exp, macros_args = list_macros(source_text, cc)
 | 
						|
    macros_set = {m for m in macros_exp
 | 
						|
                  if (macros_args[m] is None
 | 
						|
                      and re.fullmatch(macro_re, m)
 | 
						|
                      and (exclude_re is None
 | 
						|
                           or not re.fullmatch(exclude_re, m)))}
 | 
						|
    sym_data = [source_text, 'START']
 | 
						|
    sym_data.extend(sorted((m, m) for m in macros_set))
 | 
						|
    return compute_c_consts(sym_data, cc)
 | 
						|
 | 
						|
 | 
						|
def compare_macro_consts(source_1, source_2, cc, macro_re, exclude_re=None,
 | 
						|
                         allow_extra_1=False, allow_extra_2=False):
 | 
						|
    """Compare the values of macros defined by two different sources.
 | 
						|
 | 
						|
    The sources would typically be includes of a glibc header and a
 | 
						|
    kernel header.  If allow_extra_1, the first source may define
 | 
						|
    extra macros (typically if the kernel headers are older than the
 | 
						|
    version glibc has taken definitions from); if allow_extra_2, the
 | 
						|
    second source may define extra macros (typically if the kernel
 | 
						|
    headers are newer than the version glibc has taken definitions
 | 
						|
    from).  Return 1 if there were any differences other than those
 | 
						|
    allowed, 0 if the macro values were the same apart from any
 | 
						|
    allowed differences.
 | 
						|
 | 
						|
    """
 | 
						|
    macros_1 = compute_macro_consts(source_1, cc, macro_re, exclude_re)
 | 
						|
    macros_2 = compute_macro_consts(source_2, cc, macro_re, exclude_re)
 | 
						|
    if macros_1 == macros_2:
 | 
						|
        return 0
 | 
						|
    print('First source:\n%s\n' % source_1)
 | 
						|
    print('Second source:\n%s\n' % source_2)
 | 
						|
    ret = 0
 | 
						|
    for name, value in sorted(macros_1.items()):
 | 
						|
        if name not in macros_2:
 | 
						|
            print('Only in first source: %s' % name)
 | 
						|
            if not allow_extra_1:
 | 
						|
                ret = 1
 | 
						|
        elif macros_1[name] != macros_2[name]:
 | 
						|
            print('Different values for %s: %s != %s'
 | 
						|
                  % (name, macros_1[name], macros_2[name]))
 | 
						|
            ret = 1
 | 
						|
    for name in sorted(macros_2.keys()):
 | 
						|
        if name not in macros_1:
 | 
						|
            print('Only in second source: %s' % name)
 | 
						|
            if not allow_extra_2:
 | 
						|
                ret = 1
 | 
						|
    return ret
 | 
						|
 | 
						|
CompileResult = collections.namedtuple("CompileResult", "returncode output")
 | 
						|
 | 
						|
def compile_c_snippet(snippet, cc, extra_cc_args=''):
 | 
						|
    """Compile and return whether the SNIPPET can be build with CC along
 | 
						|
       EXTRA_CC_ARGS compiler flags.  Return a CompileResult with RETURNCODE
 | 
						|
       being 0 for success, or the failure value and the compiler output.
 | 
						|
    """
 | 
						|
    with tempfile.TemporaryDirectory() as temp_dir:
 | 
						|
        c_file_name = os.path.join(temp_dir, 'test.c')
 | 
						|
        obj_file_name = os.path.join(temp_dir, 'test.o')
 | 
						|
        with open(c_file_name, 'w') as c_file:
 | 
						|
            c_file.write(snippet + '\n')
 | 
						|
        cmd = cc.split() + extra_cc_args.split() + ['-c', '-o', obj_file_name,
 | 
						|
                c_file_name]
 | 
						|
        r = subprocess.run(cmd, check=False, stdout=subprocess.PIPE,
 | 
						|
                stderr=subprocess.STDOUT)
 | 
						|
        return CompileResult(r.returncode, r.stdout)
 |